mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
Reduce auto-mode-service.ts from 1308 to 516 lines (60% reduction) by extracting reusable functionality into shared packages and services: - Add feature prompt builders to @automaker/prompts (buildFeaturePrompt, buildFollowUpPrompt, buildContinuationPrompt, extractTitleFromDescription) - Add planning prompts and task parsing to @automaker/prompts - Add stream processor utilities to @automaker/utils (sleep, processStream) - Add git commit utilities to @automaker/git-utils (commitAll, hasUncommittedChanges) - Create ProjectAnalyzer service for project analysis - Create FeatureVerificationService for verify/commit operations - Extend FeatureLoader with updateStatus, updatePlanSpec, getPending methods - Expand FeatureStatus type to include all used statuses - Add PlanSpec and ParsedTask types to @automaker/types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
164 lines
4.2 KiB
TypeScript
164 lines
4.2 KiB
TypeScript
/**
|
|
* Feature Verification Service - Handles verification and commit operations
|
|
*
|
|
* Provides functionality to verify feature implementations (lint, typecheck, test, build)
|
|
* and commit changes to git.
|
|
*/
|
|
|
|
import { createLogger } from '@automaker/utils';
|
|
import {
|
|
runVerificationChecks,
|
|
hasUncommittedChanges,
|
|
commitAll,
|
|
shortHash,
|
|
} from '@automaker/git-utils';
|
|
import { extractTitleFromDescription } from '@automaker/prompts';
|
|
import { getFeatureDir } from '@automaker/platform';
|
|
import * as secureFs from '../../lib/secure-fs.js';
|
|
import path from 'path';
|
|
import type { EventEmitter } from '../../lib/events.js';
|
|
import type { Feature } from '@automaker/types';
|
|
|
|
const logger = createLogger('FeatureVerification');
|
|
|
|
export interface VerificationResult {
|
|
success: boolean;
|
|
failedCheck?: string;
|
|
}
|
|
|
|
export interface CommitResult {
|
|
hash: string | null;
|
|
shortHash?: string;
|
|
}
|
|
|
|
export class FeatureVerificationService {
|
|
private events: EventEmitter;
|
|
|
|
constructor(events: EventEmitter) {
|
|
this.events = events;
|
|
}
|
|
|
|
/**
|
|
* Resolve the working directory for a feature (checks for worktree)
|
|
*/
|
|
async resolveWorkDir(projectPath: string, featureId: string): Promise<string> {
|
|
const worktreePath = path.join(projectPath, '.worktrees', featureId);
|
|
|
|
try {
|
|
await secureFs.access(worktreePath);
|
|
return worktreePath;
|
|
} catch {
|
|
return projectPath;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify a feature's implementation by running checks
|
|
*/
|
|
async verify(projectPath: string, featureId: string): Promise<VerificationResult> {
|
|
const workDir = await this.resolveWorkDir(projectPath, featureId);
|
|
|
|
const result = await runVerificationChecks(workDir);
|
|
|
|
if (result.success) {
|
|
this.emitEvent('auto_mode_feature_complete', {
|
|
featureId,
|
|
passes: true,
|
|
message: 'All verification checks passed',
|
|
});
|
|
} else {
|
|
this.emitEvent('auto_mode_feature_complete', {
|
|
featureId,
|
|
passes: false,
|
|
message: `Verification failed: ${result.failedCheck}`,
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Commit feature changes
|
|
*/
|
|
async commit(
|
|
projectPath: string,
|
|
featureId: string,
|
|
feature: Feature | null,
|
|
providedWorktreePath?: string
|
|
): Promise<CommitResult> {
|
|
let workDir = projectPath;
|
|
|
|
if (providedWorktreePath) {
|
|
try {
|
|
await secureFs.access(providedWorktreePath);
|
|
workDir = providedWorktreePath;
|
|
} catch {
|
|
// Use project path
|
|
}
|
|
} else {
|
|
workDir = await this.resolveWorkDir(projectPath, featureId);
|
|
}
|
|
|
|
// Check for changes
|
|
const hasChanges = await hasUncommittedChanges(workDir);
|
|
if (!hasChanges) {
|
|
return { hash: null };
|
|
}
|
|
|
|
// Build commit message
|
|
const title = feature
|
|
? extractTitleFromDescription(feature.description)
|
|
: `Feature ${featureId}`;
|
|
const commitMessage = `feat: ${title}\n\nImplemented by Automaker auto-mode`;
|
|
|
|
// Commit changes
|
|
const hash = await commitAll(workDir, commitMessage);
|
|
|
|
if (hash) {
|
|
const short = shortHash(hash);
|
|
this.emitEvent('auto_mode_feature_complete', {
|
|
featureId,
|
|
passes: true,
|
|
message: `Changes committed: ${short}`,
|
|
});
|
|
return { hash, shortHash: short };
|
|
}
|
|
|
|
logger.error(`Commit failed for ${featureId}`);
|
|
return { hash: null };
|
|
}
|
|
|
|
/**
|
|
* Check if context (agent-output.md) exists for a feature
|
|
*/
|
|
async contextExists(projectPath: string, featureId: string): Promise<boolean> {
|
|
const featureDir = getFeatureDir(projectPath, featureId);
|
|
const contextPath = path.join(featureDir, 'agent-output.md');
|
|
|
|
try {
|
|
await secureFs.access(contextPath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load existing context for a feature
|
|
*/
|
|
async loadContext(projectPath: string, featureId: string): Promise<string | null> {
|
|
const featureDir = getFeatureDir(projectPath, featureId);
|
|
const contextPath = path.join(featureDir, 'agent-output.md');
|
|
|
|
try {
|
|
return (await secureFs.readFile(contextPath, 'utf-8')) as string;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private emitEvent(eventType: string, data: Record<string, unknown>): void {
|
|
this.events.emit('auto-mode:event', { type: eventType, ...data });
|
|
}
|
|
}
|