Phase システムをエージェントに注入する
This commit is contained in:
parent
bed836f08b
commit
62de1ede3c
@ -503,6 +503,121 @@ describe('instruction-builder', () => {
|
||||
|
||||
expect(result).toContain('- Step Iteration: 3(このステップの実行回数)');
|
||||
});
|
||||
|
||||
it('should include workflow structure when workflowSteps is provided', () => {
|
||||
const step = createMinimalStep('Do work');
|
||||
step.name = 'implement';
|
||||
const context = createMinimalContext({
|
||||
language: 'en',
|
||||
workflowSteps: [
|
||||
{ name: 'plan' },
|
||||
{ name: 'implement' },
|
||||
{ name: 'review' },
|
||||
],
|
||||
currentStepIndex: 1,
|
||||
});
|
||||
|
||||
const result = buildInstruction(step, context);
|
||||
|
||||
expect(result).toContain('This workflow consists of 3 steps:');
|
||||
expect(result).toContain('- Step 1: plan');
|
||||
expect(result).toContain('- Step 2: implement');
|
||||
expect(result).toContain('← current');
|
||||
expect(result).toContain('- Step 3: review');
|
||||
});
|
||||
|
||||
it('should mark current step with marker', () => {
|
||||
const step = createMinimalStep('Do work');
|
||||
step.name = 'plan';
|
||||
const context = createMinimalContext({
|
||||
language: 'en',
|
||||
workflowSteps: [
|
||||
{ name: 'plan' },
|
||||
{ name: 'implement' },
|
||||
],
|
||||
currentStepIndex: 0,
|
||||
});
|
||||
|
||||
const result = buildInstruction(step, context);
|
||||
|
||||
expect(result).toContain('- Step 1: plan ← current');
|
||||
expect(result).not.toContain('- Step 2: implement ← current');
|
||||
});
|
||||
|
||||
it('should include description in parentheses when provided', () => {
|
||||
const step = createMinimalStep('Do work');
|
||||
step.name = 'plan';
|
||||
const context = createMinimalContext({
|
||||
language: 'ja',
|
||||
workflowSteps: [
|
||||
{ name: 'plan', description: 'タスクを分析し実装計画を作成する' },
|
||||
{ name: 'implement' },
|
||||
],
|
||||
currentStepIndex: 0,
|
||||
});
|
||||
|
||||
const result = buildInstruction(step, context);
|
||||
|
||||
expect(result).toContain('- Step 1: plan(タスクを分析し実装計画を作成する) ← 現在');
|
||||
});
|
||||
|
||||
it('should skip workflow structure when workflowSteps is not provided', () => {
|
||||
const step = createMinimalStep('Do work');
|
||||
const context = createMinimalContext({ language: 'en' });
|
||||
|
||||
const result = buildInstruction(step, context);
|
||||
|
||||
expect(result).not.toContain('This workflow consists of');
|
||||
});
|
||||
|
||||
it('should skip workflow structure when workflowSteps is empty', () => {
|
||||
const step = createMinimalStep('Do work');
|
||||
const context = createMinimalContext({
|
||||
language: 'en',
|
||||
workflowSteps: [],
|
||||
currentStepIndex: -1,
|
||||
});
|
||||
|
||||
const result = buildInstruction(step, context);
|
||||
|
||||
expect(result).not.toContain('This workflow consists of');
|
||||
});
|
||||
|
||||
it('should render workflow structure in Japanese', () => {
|
||||
const step = createMinimalStep('Do work');
|
||||
step.name = 'plan';
|
||||
const context = createMinimalContext({
|
||||
language: 'ja',
|
||||
workflowSteps: [
|
||||
{ name: 'plan' },
|
||||
{ name: 'implement' },
|
||||
],
|
||||
currentStepIndex: 0,
|
||||
});
|
||||
|
||||
const result = buildInstruction(step, context);
|
||||
|
||||
expect(result).toContain('このワークフローは2ステップで構成されています:');
|
||||
expect(result).toContain('← 現在');
|
||||
});
|
||||
|
||||
it('should not show current marker when currentStepIndex is -1', () => {
|
||||
const step = createMinimalStep('Do work');
|
||||
step.name = 'sub-step';
|
||||
const context = createMinimalContext({
|
||||
language: 'en',
|
||||
workflowSteps: [
|
||||
{ name: 'plan' },
|
||||
{ name: 'implement' },
|
||||
],
|
||||
currentStepIndex: -1,
|
||||
});
|
||||
|
||||
const result = buildInstruction(step, context);
|
||||
|
||||
expect(result).toContain('This workflow consists of 2 steps:');
|
||||
expect(result).not.toContain('← current');
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildInstruction report-free (phase separation)', () => {
|
||||
|
||||
@ -133,6 +133,7 @@ export const ParallelSubStepRawSchema = z.object({
|
||||
/** Workflow step schema - raw YAML format */
|
||||
export const WorkflowStepRawSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
description: z.string().optional(),
|
||||
/** Agent is required for normal steps, optional for parallel container steps */
|
||||
agent: z.string().optional(),
|
||||
/** Session handling for this step */
|
||||
|
||||
@ -53,6 +53,8 @@ export interface ReportObjectConfig {
|
||||
/** Single step in a workflow */
|
||||
export interface WorkflowStep {
|
||||
name: string;
|
||||
/** Brief description of this step's role in the workflow */
|
||||
description?: string;
|
||||
/** Agent name, path, or inline prompt as specified in workflow YAML. Undefined when step runs without an agent. */
|
||||
agent?: string;
|
||||
/** Session handling for this step */
|
||||
|
||||
@ -31,6 +31,7 @@ export interface StepExecutorDeps {
|
||||
readonly getReportDir: () => string;
|
||||
readonly getLanguage: () => Language | undefined;
|
||||
readonly getInteractive: () => boolean;
|
||||
readonly getWorkflowSteps: () => ReadonlyArray<{ name: string; description?: string }>;
|
||||
readonly detectRuleIndex: (content: string, stepName: string) => number;
|
||||
readonly callAiJudge: (
|
||||
agentOutput: string,
|
||||
@ -52,6 +53,7 @@ export class StepExecutor {
|
||||
task: string,
|
||||
maxIterations: number,
|
||||
): string {
|
||||
const workflowSteps = this.deps.getWorkflowSteps();
|
||||
return new InstructionBuilder(step, {
|
||||
task,
|
||||
iteration: state.iteration,
|
||||
@ -64,6 +66,8 @@ export class StepExecutor {
|
||||
reportDir: join(this.deps.getProjectCwd(), this.deps.getReportDir()),
|
||||
language: this.deps.getLanguage(),
|
||||
interactive: this.deps.getInteractive(),
|
||||
workflowSteps,
|
||||
currentStepIndex: workflowSteps.findIndex(s => s.name === step.name),
|
||||
}).build();
|
||||
}
|
||||
|
||||
|
||||
@ -101,6 +101,7 @@ export class WorkflowEngine extends EventEmitter {
|
||||
getReportDir: () => this.reportDir,
|
||||
getLanguage: () => this.options.language,
|
||||
getInteractive: () => this.options.interactive === true,
|
||||
getWorkflowSteps: () => this.config.steps.map(s => ({ name: s.name, description: s.description })),
|
||||
detectRuleIndex: this.detectRuleIndex,
|
||||
callAiJudge: this.callAiJudge,
|
||||
});
|
||||
|
||||
@ -26,6 +26,8 @@ export function isReportObjectConfig(report: string | ReportConfig[] | ReportObj
|
||||
/** Shape of localized section strings */
|
||||
interface SectionStrings {
|
||||
workflowContext: string;
|
||||
workflowStructure: string;
|
||||
currentStepMarker: string;
|
||||
iteration: string;
|
||||
iterationWorkflowWide: string;
|
||||
stepIteration: string;
|
||||
@ -127,12 +129,23 @@ export class InstructionBuilder {
|
||||
|
||||
private renderWorkflowContext(language: Language): string {
|
||||
const s = getPromptObject<SectionStrings>('instruction.sections', language);
|
||||
const lines: string[] = [
|
||||
s.workflowContext,
|
||||
`- ${s.iteration}: ${this.context.iteration}/${this.context.maxIterations}${s.iterationWorkflowWide}`,
|
||||
`- ${s.stepIteration}: ${this.context.stepIteration}${s.stepIterationTimes}`,
|
||||
`- ${s.step}: ${this.step.name}`,
|
||||
];
|
||||
const lines: string[] = [s.workflowContext];
|
||||
|
||||
// Workflow structure (if workflow steps info is available)
|
||||
if (this.context.workflowSteps && this.context.workflowSteps.length > 0) {
|
||||
lines.push(s.workflowStructure.replace('{count}', String(this.context.workflowSteps.length)));
|
||||
this.context.workflowSteps.forEach((ws, index) => {
|
||||
const isCurrent = index === this.context.currentStepIndex;
|
||||
const marker = isCurrent ? ` ← ${s.currentStepMarker}` : '';
|
||||
const desc = ws.description ? `(${ws.description})` : '';
|
||||
lines.push(`- Step ${index + 1}: ${ws.name}${desc}${marker}`);
|
||||
});
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push(`- ${s.iteration}: ${this.context.iteration}/${this.context.maxIterations}${s.iterationWorkflowWide}`);
|
||||
lines.push(`- ${s.stepIteration}: ${this.context.stepIteration}${s.stepIterationTimes}`);
|
||||
lines.push(`- ${s.step}: ${this.step.name}`);
|
||||
|
||||
// If step has report config, include Report Directory path and phase note
|
||||
if (this.step.report && this.context.reportDir) {
|
||||
|
||||
@ -34,6 +34,10 @@ export interface InstructionContext {
|
||||
language?: Language;
|
||||
/** Whether interactive-only rules are enabled */
|
||||
interactive?: boolean;
|
||||
/** Top-level workflow steps for workflow structure display */
|
||||
workflowSteps?: ReadonlyArray<{ name: string; description?: string }>;
|
||||
/** Index of the current step in workflowSteps (0-based) */
|
||||
currentStepIndex?: number;
|
||||
}
|
||||
|
||||
/** Execution environment metadata prepended to agent instructions */
|
||||
|
||||
@ -185,6 +185,7 @@ function normalizeStepFromRaw(step: RawStep, workflowDir: string): WorkflowStep
|
||||
|
||||
const result: WorkflowStep = {
|
||||
name: step.name,
|
||||
description: step.description,
|
||||
agent: agentSpec,
|
||||
session: step.session,
|
||||
agentDisplayName: step.agent_name || (agentSpec ? extractAgentDisplayName(agentSpec) : step.name),
|
||||
|
||||
@ -133,6 +133,8 @@ instruction:
|
||||
|
||||
sections:
|
||||
workflowContext: "## Workflow Context"
|
||||
workflowStructure: "This workflow consists of {count} steps:"
|
||||
currentStepMarker: "current"
|
||||
iteration: "Iteration"
|
||||
iterationWorkflowWide: "(workflow-wide)"
|
||||
stepIteration: "Step Iteration"
|
||||
|
||||
@ -146,6 +146,8 @@ instruction:
|
||||
|
||||
sections:
|
||||
workflowContext: "## Workflow Context"
|
||||
workflowStructure: "このワークフローは{count}ステップで構成されています:"
|
||||
currentStepMarker: "現在"
|
||||
iteration: "Iteration"
|
||||
iterationWorkflowWide: "(ワークフロー全体)"
|
||||
stepIteration: "Step Iteration"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user