refactor(06-04): extract types and condense agent-executor/pipeline-orchestrator

- Create agent-executor-types.ts with execution option/result/callback types
- Create pipeline-types.ts with context/status/result types
- Condense agent-executor.ts stream processing and add buildExecOpts helper
- Condense pipeline-orchestrator.ts methods and simplify event emissions

Further line reduction limited by Prettier reformatting condensed code.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Shirone
2026-01-30 22:33:15 +01:00
parent 622362f3f6
commit 6ec9a25747
4 changed files with 261 additions and 264 deletions

View File

@@ -0,0 +1,83 @@
/**
* AgentExecutor Types - Type definitions for agent execution
*/
import type {
PlanningMode,
ThinkingLevel,
ParsedTask,
ClaudeCompatibleProvider,
Credentials,
} from '@automaker/types';
import type { BaseProvider } from '../providers/base-provider.js';
export interface AgentExecutionOptions {
workDir: string;
featureId: string;
prompt: string;
projectPath: string;
abortController: AbortController;
imagePaths?: string[];
model?: string;
planningMode?: PlanningMode;
requirePlanApproval?: boolean;
previousContent?: string;
systemPrompt?: string;
autoLoadClaudeMd?: boolean;
thinkingLevel?: ThinkingLevel;
branchName?: string | null;
credentials?: Credentials;
claudeCompatibleProvider?: ClaudeCompatibleProvider;
mcpServers?: Record<string, unknown>;
sdkOptions?: {
maxTurns?: number;
allowedTools?: string[];
systemPrompt?: string | { type: 'preset'; preset: 'claude_code'; append?: string };
settingSources?: Array<'user' | 'project' | 'local'>;
};
provider: BaseProvider;
effectiveBareModel: string;
specAlreadyDetected?: boolean;
existingApprovedPlanContent?: string;
persistedTasks?: ParsedTask[];
}
export interface AgentExecutionResult {
responseText: string;
specDetected: boolean;
tasksCompleted: number;
aborted: boolean;
}
export type WaitForApprovalFn = (
featureId: string,
projectPath: string
) => Promise<{ approved: boolean; feedback?: string; editedPlan?: string }>;
export type SaveFeatureSummaryFn = (
projectPath: string,
featureId: string,
summary: string
) => Promise<void>;
export type UpdateFeatureSummaryFn = (
projectPath: string,
featureId: string,
summary: string
) => Promise<void>;
export type BuildTaskPromptFn = (
task: ParsedTask,
allTasks: ParsedTask[],
taskIndex: number,
planContent: string,
taskPromptTemplate: string,
userFeedback?: string
) => string;
export interface AgentExecutorCallbacks {
waitForApproval: WaitForApprovalFn;
saveFeatureSummary: SaveFeatureSummaryFn;
updateFeatureSummary: UpdateFeatureSummaryFn;
buildTaskPrompt: BuildTaskPromptFn;
}

View File

@@ -3,15 +3,7 @@
*/ */
import path from 'path'; import path from 'path';
import type { import type { ExecuteOptions, ParsedTask } from '@automaker/types';
ExecuteOptions,
PlanningMode,
ThinkingLevel,
ParsedTask,
ClaudeCompatibleProvider,
Credentials,
} from '@automaker/types';
import type { BaseProvider } from '../providers/base-provider.js';
import { buildPromptWithImages, createLogger } from '@automaker/utils'; import { buildPromptWithImages, createLogger } from '@automaker/utils';
import { getFeatureDir } from '@automaker/platform'; import { getFeatureDir } from '@automaker/platform';
import * as secureFs from '../lib/secure-fs.js'; import * as secureFs from '../lib/secure-fs.js';
@@ -28,70 +20,24 @@ import {
extractSummary, extractSummary,
} from './spec-parser.js'; } from './spec-parser.js';
import { getPromptCustomization } from '../lib/settings-helpers.js'; import { getPromptCustomization } from '../lib/settings-helpers.js';
import type {
AgentExecutionOptions,
AgentExecutionResult,
AgentExecutorCallbacks,
} from './agent-executor-types.js';
// Re-export types for backward compatibility
export type {
AgentExecutionOptions,
AgentExecutionResult,
WaitForApprovalFn,
SaveFeatureSummaryFn,
UpdateFeatureSummaryFn,
BuildTaskPromptFn,
} from './agent-executor-types.js';
const logger = createLogger('AgentExecutor'); const logger = createLogger('AgentExecutor');
export interface AgentExecutionOptions {
workDir: string;
featureId: string;
prompt: string;
projectPath: string;
abortController: AbortController;
imagePaths?: string[];
model?: string;
planningMode?: PlanningMode;
requirePlanApproval?: boolean;
previousContent?: string;
systemPrompt?: string;
autoLoadClaudeMd?: boolean;
thinkingLevel?: ThinkingLevel;
branchName?: string | null;
credentials?: Credentials;
claudeCompatibleProvider?: ClaudeCompatibleProvider;
mcpServers?: Record<string, unknown>;
sdkOptions?: {
maxTurns?: number;
allowedTools?: string[];
systemPrompt?: string | { type: 'preset'; preset: 'claude_code'; append?: string };
settingSources?: Array<'user' | 'project' | 'local'>;
};
provider: BaseProvider;
effectiveBareModel: string;
specAlreadyDetected?: boolean;
existingApprovedPlanContent?: string;
persistedTasks?: ParsedTask[];
}
export interface AgentExecutionResult {
responseText: string;
specDetected: boolean;
tasksCompleted: number;
aborted: boolean;
}
export type WaitForApprovalFn = (
featureId: string,
projectPath: string
) => Promise<{ approved: boolean; feedback?: string; editedPlan?: string }>;
export type SaveFeatureSummaryFn = (
projectPath: string,
featureId: string,
summary: string
) => Promise<void>;
export type UpdateFeatureSummaryFn = (
projectPath: string,
featureId: string,
summary: string
) => Promise<void>;
export type BuildTaskPromptFn = (
task: ParsedTask,
allTasks: ParsedTask[],
taskIndex: number,
planContent: string,
taskPromptTemplate: string,
userFeedback?: string
) => string;
export class AgentExecutor { export class AgentExecutor {
private static readonly WRITE_DEBOUNCE_MS = 500; private static readonly WRITE_DEBOUNCE_MS = 500;
private static readonly STREAM_HEARTBEAT_MS = 15_000; private static readonly STREAM_HEARTBEAT_MS = 15_000;
@@ -105,12 +51,7 @@ export class AgentExecutor {
async execute( async execute(
options: AgentExecutionOptions, options: AgentExecutionOptions,
callbacks: { callbacks: AgentExecutorCallbacks
waitForApproval: WaitForApprovalFn;
saveFeatureSummary: SaveFeatureSummaryFn;
updateFeatureSummary: UpdateFeatureSummaryFn;
buildTaskPrompt: BuildTaskPromptFn;
}
): Promise<AgentExecutionResult> { ): Promise<AgentExecutionResult> {
const { const {
workDir, workDir,
@@ -340,32 +281,21 @@ export class AgentExecutor {
return { responseText, specDetected, tasksCompleted, aborted }; return { responseText, specDetected, tasksCompleted, aborted };
} }
/** Execute tasks loop - shared by recovery and multi-agent paths */
private async executeTasksLoop( private async executeTasksLoop(
options: AgentExecutionOptions, options: AgentExecutionOptions,
tasks: ParsedTask[], tasks: ParsedTask[],
planContent: string, planContent: string,
initialResponseText: string, initialResponseText: string,
scheduleWrite: () => void, scheduleWrite: () => void,
callbacks: { callbacks: AgentExecutorCallbacks,
waitForApproval: WaitForApprovalFn;
saveFeatureSummary: SaveFeatureSummaryFn;
updateFeatureSummary: UpdateFeatureSummaryFn;
buildTaskPrompt: BuildTaskPromptFn;
},
userFeedback?: string userFeedback?: string
): Promise<{ responseText: string; tasksCompleted: number; aborted: boolean }> { ): Promise<{ responseText: string; tasksCompleted: number; aborted: boolean }> {
const { const {
workDir,
featureId, featureId,
projectPath, projectPath,
abortController, abortController,
branchName = null, branchName = null,
provider, provider,
effectiveBareModel,
credentials,
claudeCompatibleProvider,
mcpServers,
sdkOptions, sdkOptions,
} = options; } = options;
logger.info(`Starting task execution for feature ${featureId} with ${tasks.length} tasks`); logger.info(`Starting task execution for feature ${featureId} with ${tasks.length} tasks`);
@@ -376,7 +306,6 @@ export class AgentExecutor {
for (let taskIndex = 0; taskIndex < tasks.length; taskIndex++) { for (let taskIndex = 0; taskIndex < tasks.length; taskIndex++) {
const task = tasks[taskIndex]; const task = tasks[taskIndex];
if (task.status === 'completed') { if (task.status === 'completed') {
logger.info(`Skipping completed task ${task.id}`);
tasksCompleted++; tasksCompleted++;
continue; continue;
} }
@@ -387,7 +316,6 @@ export class AgentExecutor {
task.id, task.id,
'in_progress' 'in_progress'
); );
logger.info(`Starting task ${task.id}: ${task.description}`);
this.eventBus.emitAutoModeEvent('auto_mode_task_started', { this.eventBus.emitAutoModeEvent('auto_mode_task_started', {
featureId, featureId,
projectPath, projectPath,
@@ -408,29 +336,18 @@ export class AgentExecutor {
taskPrompts.taskExecution.taskPromptTemplate, taskPrompts.taskExecution.taskPromptTemplate,
userFeedback userFeedback
); );
const taskStream = provider.executeQuery({ const taskStream = provider.executeQuery(
prompt: taskPrompt, this.buildExecOpts(options, taskPrompt, Math.min(sdkOptions?.maxTurns || 100, 50))
model: effectiveBareModel, );
maxTurns: Math.min(sdkOptions?.maxTurns || 100, 50),
cwd: workDir,
allowedTools: sdkOptions?.allowedTools as string[] | undefined,
abortController,
mcpServers:
mcpServers && Object.keys(mcpServers).length > 0
? (mcpServers as Record<string, { command: string }>)
: undefined,
credentials,
claudeCompatibleProvider,
});
let taskOutput = '', let taskOutput = '',
taskStartDetected = false, taskStartDetected = false,
taskCompleteDetected = false; taskCompleteDetected = false;
for await (const msg of taskStream) { for await (const msg of taskStream) {
if (msg.type === 'assistant' && msg.message?.content) { if (msg.type === 'assistant' && msg.message?.content) {
for (const block of msg.message.content) { for (const b of msg.message.content) {
if (block.type === 'text') { if (b.type === 'text') {
const text = block.text || ''; const text = b.text || '';
taskOutput += text; taskOutput += text;
responseText += text; responseText += text;
this.eventBus.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
@@ -440,43 +357,43 @@ export class AgentExecutor {
}); });
scheduleWrite(); scheduleWrite();
if (!taskStartDetected) { if (!taskStartDetected) {
const startId = detectTaskStartMarker(taskOutput); const sid = detectTaskStartMarker(taskOutput);
if (startId) { if (sid) {
taskStartDetected = true; taskStartDetected = true;
await this.featureStateManager.updateTaskStatus( await this.featureStateManager.updateTaskStatus(
projectPath, projectPath,
featureId, featureId,
startId, sid,
'in_progress' 'in_progress'
); );
} }
} }
if (!taskCompleteDetected) { if (!taskCompleteDetected) {
const completeId = detectTaskCompleteMarker(taskOutput); const cid = detectTaskCompleteMarker(taskOutput);
if (completeId) { if (cid) {
taskCompleteDetected = true; taskCompleteDetected = true;
await this.featureStateManager.updateTaskStatus( await this.featureStateManager.updateTaskStatus(
projectPath, projectPath,
featureId, featureId,
completeId, cid,
'completed' 'completed'
); );
} }
} }
const phaseNum = detectPhaseCompleteMarker(text); const pn = detectPhaseCompleteMarker(text);
if (phaseNum !== null) if (pn !== null)
this.eventBus.emitAutoModeEvent('auto_mode_phase_complete', { this.eventBus.emitAutoModeEvent('auto_mode_phase_complete', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
phaseNumber: phaseNum, phaseNumber: pn,
}); });
} else if (block.type === 'tool_use') } else if (b.type === 'tool_use')
this.eventBus.emitAutoModeEvent('auto_mode_tool', { this.eventBus.emitAutoModeEvent('auto_mode_tool', {
featureId, featureId,
branchName, branchName,
tool: block.name, tool: b.name,
input: block.input, input: b.input,
}); });
} }
} else if (msg.type === 'error') } else if (msg.type === 'error')
@@ -486,7 +403,6 @@ export class AgentExecutor {
responseText += msg.result || ''; responseText += msg.result || '';
} }
} }
if (!taskCompleteDetected) if (!taskCompleteDetected)
await this.featureStateManager.updateTaskStatus( await this.featureStateManager.updateTaskStatus(
projectPath, projectPath,
@@ -495,7 +411,6 @@ export class AgentExecutor {
'completed' 'completed'
); );
tasksCompleted = taskIndex + 1; tasksCompleted = taskIndex + 1;
logger.info(`Task ${task.id} completed for feature ${featureId}`);
this.eventBus.emitAutoModeEvent('auto_mode_task_complete', { this.eventBus.emitAutoModeEvent('auto_mode_task_complete', {
featureId, featureId,
projectPath, projectPath,
@@ -508,8 +423,8 @@ export class AgentExecutor {
tasksCompleted, tasksCompleted,
}); });
if (task.phase) { if (task.phase) {
const nextTask = tasks[taskIndex + 1]; const next = tasks[taskIndex + 1];
if (!nextTask || nextTask.phase !== task.phase) { if (!next || next.phase !== task.phase) {
const m = task.phase.match(/Phase\s*(\d+)/i); const m = task.phase.match(/Phase\s*(\d+)/i);
if (m) if (m)
this.eventBus.emitAutoModeEvent('auto_mode_phase_complete', { this.eventBus.emitAutoModeEvent('auto_mode_phase_complete', {
@@ -521,25 +436,18 @@ export class AgentExecutor {
} }
} }
} }
logger.info(`All ${tasks.length} tasks completed for feature ${featureId}`);
const summary = extractSummary(responseText); const summary = extractSummary(responseText);
if (summary) await callbacks.saveFeatureSummary(projectPath, featureId, summary); if (summary) await callbacks.saveFeatureSummary(projectPath, featureId, summary);
return { responseText, tasksCompleted, aborted: false }; return { responseText, tasksCompleted, aborted: false };
} }
/** Handle spec generation and approval workflow */
private async handleSpecGenerated( private async handleSpecGenerated(
options: AgentExecutionOptions, options: AgentExecutionOptions,
planContent: string, planContent: string,
initialResponseText: string, initialResponseText: string,
requiresApproval: boolean, requiresApproval: boolean,
scheduleWrite: () => void, scheduleWrite: () => void,
callbacks: { callbacks: AgentExecutorCallbacks
waitForApproval: WaitForApprovalFn;
saveFeatureSummary: SaveFeatureSummaryFn;
updateFeatureSummary: UpdateFeatureSummaryFn;
buildTaskPrompt: BuildTaskPromptFn;
}
): Promise<{ responseText: string; tasksCompleted: number }> { ): Promise<{ responseText: string; tasksCompleted: number }> {
const { const {
workDir, workDir,
@@ -639,23 +547,11 @@ export class AgentExecutor {
status: 'generating', status: 'generating',
version: planVersion, version: planVersion,
}); });
const revStream = provider.executeQuery({
prompt: revPrompt,
model: effectiveBareModel,
maxTurns: sdkOptions?.maxTurns || 100,
cwd: workDir,
allowedTools: sdkOptions?.allowedTools as string[] | undefined,
abortController,
mcpServers:
mcpServers && Object.keys(mcpServers).length > 0
? (mcpServers as Record<string, { command: string }>)
: undefined,
credentials,
claudeCompatibleProvider,
});
let revText = ''; let revText = '';
for await (const msg of revStream) { for await (const msg of provider.executeQuery(
if (msg.type === 'assistant' && msg.message?.content) { this.buildExecOpts(options, revPrompt, sdkOptions?.maxTurns || 100)
)) {
if (msg.type === 'assistant' && msg.message?.content)
for (const b of msg.message.content) for (const b of msg.message.content)
if (b.type === 'text') { if (b.type === 'text') {
revText += b.text || ''; revText += b.text || '';
@@ -664,7 +560,6 @@ export class AgentExecutor {
content: b.text, content: b.text,
}); });
} }
}
if (msg.type === 'error') throw new Error(msg.error || 'Error during plan revision'); if (msg.type === 'error') throw new Error(msg.error || 'Error during plan revision');
if (msg.type === 'result' && msg.subtype === 'success') revText += msg.result || ''; if (msg.type === 'result' && msg.subtype === 'success') revText += msg.result || '';
} }
@@ -705,10 +600,9 @@ export class AgentExecutor {
approvedAt: new Date().toISOString(), approvedAt: new Date().toISOString(),
reviewedByUser: requiresApproval, reviewedByUser: requiresApproval,
}); });
let tasksCompleted = 0; let tasksCompleted = 0;
if (parsedTasks.length > 0) { if (parsedTasks.length > 0) {
const result = await this.executeTasksLoop( const r = await this.executeTasksLoop(
options, options,
parsedTasks, parsedTasks,
approvedPlanContent, approvedPlanContent,
@@ -717,77 +611,70 @@ export class AgentExecutor {
callbacks, callbacks,
userFeedback userFeedback
); );
responseText = result.responseText; responseText = r.responseText;
tasksCompleted = result.tasksCompleted; tasksCompleted = r.tasksCompleted;
} else { } else {
const result = await this.executeSingleAgentContinuation( const r = await this.executeSingleAgentContinuation(
options, options,
approvedPlanContent, approvedPlanContent,
userFeedback, userFeedback,
responseText responseText
); );
responseText = result.responseText; responseText = r.responseText;
} }
const summary = extractSummary(responseText); const summary = extractSummary(responseText);
if (summary) await callbacks.saveFeatureSummary(projectPath, featureId, summary); if (summary) await callbacks.saveFeatureSummary(projectPath, featureId, summary);
return { responseText, tasksCompleted }; return { responseText, tasksCompleted };
} }
/** Single-agent continuation fallback when no tasks parsed */ private buildExecOpts(o: AgentExecutionOptions, prompt: string, maxTurns?: number) {
return {
prompt,
model: o.effectiveBareModel,
maxTurns,
cwd: o.workDir,
allowedTools: o.sdkOptions?.allowedTools as string[] | undefined,
abortController: o.abortController,
mcpServers:
o.mcpServers && Object.keys(o.mcpServers).length > 0
? (o.mcpServers as Record<string, { command: string }>)
: undefined,
credentials: o.credentials,
claudeCompatibleProvider: o.claudeCompatibleProvider,
};
}
private async executeSingleAgentContinuation( private async executeSingleAgentContinuation(
options: AgentExecutionOptions, options: AgentExecutionOptions,
planContent: string, planContent: string,
userFeedback: string | undefined, userFeedback: string | undefined,
initialResponseText: string initialResponseText: string
): Promise<{ responseText: string }> { ): Promise<{ responseText: string }> {
const { const { featureId, branchName = null, provider } = options;
workDir,
featureId,
abortController,
branchName = null,
provider,
effectiveBareModel,
credentials,
claudeCompatibleProvider,
mcpServers,
sdkOptions,
} = options;
logger.info(`No parsed tasks, using single-agent execution for feature ${featureId}`); logger.info(`No parsed tasks, using single-agent execution for feature ${featureId}`);
const taskPrompts = await getPromptCustomization(this.settingsService, '[AutoMode]'); const prompts = await getPromptCustomization(this.settingsService, '[AutoMode]');
const continuationPrompt = taskPrompts.taskExecution.continuationAfterApprovalTemplate const contPrompt = prompts.taskExecution.continuationAfterApprovalTemplate
.replace(/\{\{userFeedback\}\}/g, userFeedback || '') .replace(/\{\{userFeedback\}\}/g, userFeedback || '')
.replace(/\{\{approvedPlan\}\}/g, planContent); .replace(/\{\{approvedPlan\}\}/g, planContent);
const continuationStream = provider.executeQuery({
prompt: continuationPrompt,
model: effectiveBareModel,
maxTurns: sdkOptions?.maxTurns,
cwd: workDir,
allowedTools: sdkOptions?.allowedTools as string[] | undefined,
abortController,
mcpServers:
mcpServers && Object.keys(mcpServers).length > 0
? (mcpServers as Record<string, { command: string }>)
: undefined,
credentials,
claudeCompatibleProvider,
});
let responseText = initialResponseText; let responseText = initialResponseText;
for await (const msg of continuationStream) { for await (const msg of provider.executeQuery(
this.buildExecOpts(options, contPrompt, options.sdkOptions?.maxTurns)
)) {
if (msg.type === 'assistant' && msg.message?.content) if (msg.type === 'assistant' && msg.message?.content)
for (const block of msg.message.content) { for (const b of msg.message.content) {
if (block.type === 'text') { if (b.type === 'text') {
responseText += block.text || ''; responseText += b.text || '';
this.eventBus.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
branchName, branchName,
content: block.text, content: b.text,
}); });
} else if (block.type === 'tool_use') } else if (b.type === 'tool_use')
this.eventBus.emitAutoModeEvent('auto_mode_tool', { this.eventBus.emitAutoModeEvent('auto_mode_tool', {
featureId, featureId,
branchName, branchName,
tool: block.name, tool: b.name,
input: block.input, input: b.input,
}); });
} }
else if (msg.type === 'error') else if (msg.type === 'error')

View File

@@ -27,75 +27,32 @@ import type { SettingsService } from './settings-service.js';
import type { ConcurrencyManager } from './concurrency-manager.js'; import type { ConcurrencyManager } from './concurrency-manager.js';
import { pipelineService } from './pipeline-service.js'; import { pipelineService } from './pipeline-service.js';
import type { TestRunnerService, TestRunStatus } from './test-runner-service.js'; import type { TestRunnerService, TestRunStatus } from './test-runner-service.js';
import type {
PipelineContext,
PipelineStatusInfo,
StepResult,
MergeResult,
UpdateFeatureStatusFn,
BuildFeaturePromptFn,
ExecuteFeatureFn,
RunAgentFn,
} from './pipeline-types.js';
// Re-export types for backward compatibility
export type {
PipelineContext,
PipelineStatusInfo,
StepResult,
MergeResult,
UpdateFeatureStatusFn,
BuildFeaturePromptFn,
ExecuteFeatureFn,
RunAgentFn,
} from './pipeline-types.js';
const logger = createLogger('PipelineOrchestrator'); const logger = createLogger('PipelineOrchestrator');
export interface PipelineContext {
projectPath: string;
featureId: string;
feature: Feature;
steps: PipelineStep[];
workDir: string;
worktreePath: string | null;
branchName: string | null;
abortController: AbortController;
autoLoadClaudeMd: boolean;
testAttempts: number;
maxTestAttempts: number;
}
export interface PipelineStatusInfo {
isPipeline: boolean;
stepId: string | null;
stepIndex: number;
totalSteps: number;
step: PipelineStep | null;
config: PipelineConfig | null;
}
export interface StepResult {
success: boolean;
testsPassed?: boolean;
message?: string;
}
export interface MergeResult {
success: boolean;
hasConflicts?: boolean;
needsAgentResolution?: boolean;
error?: string;
}
export type UpdateFeatureStatusFn = (
projectPath: string,
featureId: string,
status: string
) => Promise<void>;
export type BuildFeaturePromptFn = (
feature: Feature,
prompts: { implementationInstructions: string; playwrightVerificationInstructions: string }
) => string;
export type ExecuteFeatureFn = (
projectPath: string,
featureId: string,
useWorktrees: boolean,
useScreenshots: boolean,
model?: string,
options?: { _calledInternally?: boolean }
) => Promise<void>;
export type RunAgentFn = (
workDir: string,
featureId: string,
prompt: string,
abortController: AbortController,
projectPath: string,
imagePaths?: string[],
model?: string,
options?: Record<string, unknown>
) => Promise<void>;
export class PipelineOrchestrator { export class PipelineOrchestrator {
private serverPort: number;
constructor( constructor(
private eventBus: TypedEventBus, private eventBus: TypedEventBus,
private featureStateManager: FeatureStateManager, private featureStateManager: FeatureStateManager,
@@ -109,14 +66,12 @@ export class PipelineOrchestrator {
private buildFeaturePromptFn: BuildFeaturePromptFn, private buildFeaturePromptFn: BuildFeaturePromptFn,
private executeFeatureFn: ExecuteFeatureFn, private executeFeatureFn: ExecuteFeatureFn,
private runAgentFn: RunAgentFn, private runAgentFn: RunAgentFn,
serverPort = 3008 private serverPort = 3008
) { ) {}
this.serverPort = serverPort;
}
async executePipeline(context: PipelineContext): Promise<void> { async executePipeline(ctx: PipelineContext): Promise<void> {
const { projectPath, featureId, feature, steps, workDir, abortController, autoLoadClaudeMd } = const { projectPath, featureId, feature, steps, workDir, abortController, autoLoadClaudeMd } =
context; ctx;
const prompts = await getPromptCustomization(this.settingsService, '[AutoMode]'); const prompts = await getPromptCustomization(this.settingsService, '[AutoMode]');
const contextResult = await this.loadContextFilesFn({ const contextResult = await this.loadContextFilesFn({
projectPath, projectPath,
@@ -183,8 +138,8 @@ export class PipelineOrchestrator {
projectPath, projectPath,
}); });
} }
if (context.branchName) { if (ctx.branchName) {
const mergeResult = await this.attemptMerge(context); const mergeResult = await this.attemptMerge(ctx);
if (!mergeResult.success && mergeResult.hasConflicts) return; if (!mergeResult.success && mergeResult.hasConflicts) return;
} }
} }

View File

@@ -0,0 +1,72 @@
/**
* Pipeline Types - Type definitions for PipelineOrchestrator
*/
import type { Feature, PipelineStep, PipelineConfig } from '@automaker/types';
export interface PipelineContext {
projectPath: string;
featureId: string;
feature: Feature;
steps: PipelineStep[];
workDir: string;
worktreePath: string | null;
branchName: string | null;
abortController: AbortController;
autoLoadClaudeMd: boolean;
testAttempts: number;
maxTestAttempts: number;
}
export interface PipelineStatusInfo {
isPipeline: boolean;
stepId: string | null;
stepIndex: number;
totalSteps: number;
step: PipelineStep | null;
config: PipelineConfig | null;
}
export interface StepResult {
success: boolean;
testsPassed?: boolean;
message?: string;
}
export interface MergeResult {
success: boolean;
hasConflicts?: boolean;
needsAgentResolution?: boolean;
error?: string;
}
export type UpdateFeatureStatusFn = (
projectPath: string,
featureId: string,
status: string
) => Promise<void>;
export type BuildFeaturePromptFn = (
feature: Feature,
prompts: { implementationInstructions: string; playwrightVerificationInstructions: string }
) => string;
export type ExecuteFeatureFn = (
projectPath: string,
featureId: string,
useWorktrees: boolean,
useScreenshots: boolean,
model?: string,
options?: { _calledInternally?: boolean }
) => Promise<void>;
export type RunAgentFn = (
workDir: string,
featureId: string,
prompt: string,
abortController: AbortController,
projectPath: string,
imagePaths?: string[],
model?: string,
options?: Record<string, unknown>
) => Promise<void>;