mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
refactor: extract auto-mode-service into modular services
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>
This commit is contained in:
@@ -4,8 +4,9 @@
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import type { Feature } from '@automaker/types';
|
||||
import type { Feature, PlanSpec, FeatureStatus } from '@automaker/types';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { resolveDependencies, areDependenciesSatisfied } from '@automaker/dependency-resolver';
|
||||
import * as secureFs from '../lib/secure-fs.js';
|
||||
import {
|
||||
getFeaturesDir,
|
||||
@@ -381,4 +382,115 @@ export class FeatureLoader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if agent output exists for a feature
|
||||
*/
|
||||
async hasAgentOutput(projectPath: string, featureId: string): Promise<boolean> {
|
||||
try {
|
||||
const agentOutputPath = this.getAgentOutputPath(projectPath, featureId);
|
||||
await secureFs.access(agentOutputPath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update feature status with proper timestamp handling
|
||||
* Used by auto-mode to update feature status during execution
|
||||
*/
|
||||
async updateStatus(
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
status: FeatureStatus
|
||||
): Promise<Feature | null> {
|
||||
try {
|
||||
const featureJsonPath = this.getFeatureJsonPath(projectPath, featureId);
|
||||
const content = (await secureFs.readFile(featureJsonPath, 'utf-8')) as string;
|
||||
const feature = JSON.parse(content) as Feature;
|
||||
|
||||
feature.status = status;
|
||||
feature.updatedAt = new Date().toISOString();
|
||||
|
||||
// Handle justFinishedAt for waiting_approval status
|
||||
if (status === 'waiting_approval') {
|
||||
feature.justFinishedAt = new Date().toISOString();
|
||||
} else {
|
||||
feature.justFinishedAt = undefined;
|
||||
}
|
||||
|
||||
await secureFs.writeFile(featureJsonPath, JSON.stringify(feature, null, 2));
|
||||
return feature;
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||
return null;
|
||||
}
|
||||
logger.error(`[FeatureLoader] Failed to update status for ${featureId}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update feature plan specification
|
||||
* Handles version incrementing and timestamp management
|
||||
*/
|
||||
async updatePlanSpec(
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
updates: Partial<PlanSpec>
|
||||
): Promise<Feature | null> {
|
||||
try {
|
||||
const featureJsonPath = this.getFeatureJsonPath(projectPath, featureId);
|
||||
const content = (await secureFs.readFile(featureJsonPath, 'utf-8')) as string;
|
||||
const feature = JSON.parse(content) as Feature;
|
||||
|
||||
// Initialize planSpec if not present
|
||||
if (!feature.planSpec) {
|
||||
feature.planSpec = { status: 'pending', version: 1, reviewedByUser: false };
|
||||
}
|
||||
|
||||
// Increment version if content changed
|
||||
if (updates.content && updates.content !== feature.planSpec.content) {
|
||||
feature.planSpec.version = (feature.planSpec.version || 0) + 1;
|
||||
}
|
||||
|
||||
// Merge updates
|
||||
Object.assign(feature.planSpec, updates);
|
||||
feature.updatedAt = new Date().toISOString();
|
||||
|
||||
await secureFs.writeFile(featureJsonPath, JSON.stringify(feature, null, 2));
|
||||
return feature;
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||
return null;
|
||||
}
|
||||
logger.error(`[FeatureLoader] Failed to update planSpec for ${featureId}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get features that are pending and ready to execute
|
||||
* Filters by status and resolves dependencies
|
||||
*/
|
||||
async getPending(projectPath: string): Promise<Feature[]> {
|
||||
try {
|
||||
const allFeatures = await this.getAll(projectPath);
|
||||
const pendingFeatures = allFeatures.filter((f) =>
|
||||
['pending', 'ready', 'backlog'].includes(f.status)
|
||||
);
|
||||
|
||||
// Resolve dependencies and order features
|
||||
const { orderedFeatures } = resolveDependencies(pendingFeatures);
|
||||
|
||||
// Filter to features whose dependencies are satisfied
|
||||
return orderedFeatures.filter((feature: Feature) =>
|
||||
areDependenciesSatisfied(feature, allFeatures)
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('[FeatureLoader] Failed to get pending features:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user