hotfix: 過去のprevious_responseがバインドされてしまう問題
This commit is contained in:
parent
1b0a84601d
commit
9159f071f4
@ -8,7 +8,9 @@ You are responsible for instruction creation in TAKT's interactive mode. Convert
|
|||||||
## Requirements
|
## Requirements
|
||||||
- Output only the final task instruction (no preamble).
|
- Output only the final task instruction (no preamble).
|
||||||
- Be specific about scope and targets (files/modules) if mentioned.
|
- Be specific about scope and targets (files/modules) if mentioned.
|
||||||
- Preserve user-provided constraints and "do not" instructions.
|
- Preserve user-provided constraints and "do not" instructions **only if explicitly stated by the user**.
|
||||||
|
- If the source of a constraint is unclear, do not include it; add it to Open Questions if needed.
|
||||||
|
- Do not include constraints proposed or inferred by the assistant.
|
||||||
- Do NOT include assistant/system operational constraints (tool limits, execution prohibitions).
|
- Do NOT include assistant/system operational constraints (tool limits, execution prohibitions).
|
||||||
- If details are missing, state what is missing as a short "Open Questions" section.
|
- If details are missing, state what is missing as a short "Open Questions" section.
|
||||||
- Clearly specify the concrete work that the workflow will execute.
|
- Clearly specify the concrete work that the workflow will execute.
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
- 出力はタスク指示書のみ(前置き不要)
|
- 出力はタスク指示書のみ(前置き不要)
|
||||||
- スコープや対象(ファイル/モジュール)が出ている場合は明確に書く
|
- スコープや対象(ファイル/モジュール)が出ている場合は明確に書く
|
||||||
- ユーザー由来の制約や「やらないこと」は保持する
|
- ユーザー由来の制約や「やらないこと」は保持する
|
||||||
|
- 制約の出所が不明な場合は保持せず、必要なら Open Questions に回す
|
||||||
|
- アシスタントが提案・推測した制約は指示書に含めない
|
||||||
- アシスタントの運用上の制約(実行禁止/ツール制限など)は指示に含めない
|
- アシスタントの運用上の制約(実行禁止/ツール制限など)は指示に含めない
|
||||||
- 情報不足があれば「Open Questions」セクションを短く付ける
|
- 情報不足があれば「Open Questions」セクションを短く付ける
|
||||||
- ワークフローが実行する具体的な作業内容を明記する
|
- ワークフローが実行する具体的な作業内容を明記する
|
||||||
|
|||||||
@ -149,6 +149,128 @@ describe('WorkflowEngine Integration: Happy Path', () => {
|
|||||||
// plan, implement, ai_review, reviewers(1st), fix, reviewers(2nd), supervise = 7
|
// plan, implement, ai_review, reviewers(1st), fix, reviewers(2nd), supervise = 7
|
||||||
expect(state.iteration).toBe(7);
|
expect(state.iteration).toBe(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should inject latest reviewers output as Previous Response for repeated fix steps', async () => {
|
||||||
|
const config = buildDefaultWorkflowConfig();
|
||||||
|
const engine = new WorkflowEngine(config, tmpDir, 'test task', { projectCwd: tmpDir });
|
||||||
|
|
||||||
|
mockRunAgentSequence([
|
||||||
|
makeResponse({ agent: 'plan', content: 'Plan done' }),
|
||||||
|
makeResponse({ agent: 'implement', content: 'Impl done' }),
|
||||||
|
makeResponse({ agent: 'ai_review', content: 'No issues' }),
|
||||||
|
// Round 1 reviewers
|
||||||
|
makeResponse({ agent: 'arch-review', content: 'Arch R1 OK' }),
|
||||||
|
makeResponse({ agent: 'security-review', content: 'Sec R1 needs fix' }),
|
||||||
|
// fix round 1
|
||||||
|
makeResponse({ agent: 'fix', content: 'Fix R1' }),
|
||||||
|
// Round 2 reviewers
|
||||||
|
makeResponse({ agent: 'arch-review', content: 'Arch R2 OK' }),
|
||||||
|
makeResponse({ agent: 'security-review', content: 'Sec R2 still failing' }),
|
||||||
|
// fix round 2
|
||||||
|
makeResponse({ agent: 'fix', content: 'Fix R2' }),
|
||||||
|
// Round 3 reviewers (approved)
|
||||||
|
makeResponse({ agent: 'arch-review', content: 'Arch R3 OK' }),
|
||||||
|
makeResponse({ agent: 'security-review', content: 'Sec R3 OK' }),
|
||||||
|
// supervise
|
||||||
|
makeResponse({ agent: 'supervise', content: 'All passed' }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
mockDetectMatchedRuleSequence([
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // plan → implement
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // implement → ai_review
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // ai_review → reviewers
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // arch-review → approved
|
||||||
|
{ index: 1, method: 'phase1_tag' }, // security-review → needs_fix
|
||||||
|
{ index: 1, method: 'aggregate' }, // reviewers: any(needs_fix) → fix
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // fix → reviewers
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // arch-review → approved
|
||||||
|
{ index: 1, method: 'phase1_tag' }, // security-review → needs_fix
|
||||||
|
{ index: 1, method: 'aggregate' }, // reviewers: any(needs_fix) → fix
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // fix → reviewers
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // arch-review → approved
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // security-review → approved
|
||||||
|
{ index: 0, method: 'aggregate' }, // reviewers: all(approved) → supervise
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // supervise → COMPLETE
|
||||||
|
]);
|
||||||
|
|
||||||
|
const fixInstructions: string[] = [];
|
||||||
|
engine.on('movement:start', (step, _iteration, instruction) => {
|
||||||
|
if (step.name === 'fix') {
|
||||||
|
fixInstructions.push(instruction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await engine.run();
|
||||||
|
|
||||||
|
expect(fixInstructions).toHaveLength(2);
|
||||||
|
|
||||||
|
const fix1 = fixInstructions[0]!;
|
||||||
|
expect(fix1).toContain('## Previous Response');
|
||||||
|
expect(fix1).toContain('Arch R1 OK');
|
||||||
|
expect(fix1).toContain('Sec R1 needs fix');
|
||||||
|
expect(fix1).not.toContain('Arch R2 OK');
|
||||||
|
expect(fix1).not.toContain('Sec R2 still failing');
|
||||||
|
|
||||||
|
const fix2 = fixInstructions[1]!;
|
||||||
|
expect(fix2).toContain('## Previous Response');
|
||||||
|
expect(fix2).toContain('Arch R2 OK');
|
||||||
|
expect(fix2).toContain('Sec R2 still failing');
|
||||||
|
expect(fix2).not.toContain('Arch R1 OK');
|
||||||
|
expect(fix2).not.toContain('Sec R1 needs fix');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use the latest movement output across different steps for Previous Response', async () => {
|
||||||
|
const config = buildDefaultWorkflowConfig();
|
||||||
|
const engine = new WorkflowEngine(config, tmpDir, 'test task', { projectCwd: tmpDir });
|
||||||
|
|
||||||
|
mockRunAgentSequence([
|
||||||
|
makeResponse({ agent: 'plan', content: 'Plan done' }),
|
||||||
|
makeResponse({ agent: 'implement', content: 'Impl done' }),
|
||||||
|
makeResponse({ agent: 'ai_review', content: 'AI issues found' }),
|
||||||
|
// ai_fix (should see ai_review output)
|
||||||
|
makeResponse({ agent: 'ai_fix', content: 'AI issues fixed' }),
|
||||||
|
// reviewers (approved)
|
||||||
|
makeResponse({ agent: 'arch-review', content: 'Arch OK' }),
|
||||||
|
makeResponse({ agent: 'security-review', content: 'Sec OK' }),
|
||||||
|
// supervise (should see reviewers aggregate output)
|
||||||
|
makeResponse({ agent: 'supervise', content: 'All passed' }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
mockDetectMatchedRuleSequence([
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // plan → implement
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // implement → ai_review
|
||||||
|
{ index: 1, method: 'phase1_tag' }, // ai_review → ai_fix
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // ai_fix → reviewers
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // arch-review → approved
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // security-review → approved
|
||||||
|
{ index: 0, method: 'aggregate' }, // reviewers → supervise
|
||||||
|
{ index: 0, method: 'phase1_tag' }, // supervise → COMPLETE
|
||||||
|
]);
|
||||||
|
|
||||||
|
const aiFixInstructions: string[] = [];
|
||||||
|
const superviseInstructions: string[] = [];
|
||||||
|
engine.on('movement:start', (step, _iteration, instruction) => {
|
||||||
|
if (step.name === 'ai_fix') {
|
||||||
|
aiFixInstructions.push(instruction);
|
||||||
|
} else if (step.name === 'supervise') {
|
||||||
|
superviseInstructions.push(instruction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await engine.run();
|
||||||
|
|
||||||
|
expect(aiFixInstructions).toHaveLength(1);
|
||||||
|
const aiFix = aiFixInstructions[0]!;
|
||||||
|
expect(aiFix).toContain('## Previous Response');
|
||||||
|
expect(aiFix).toContain('AI issues found');
|
||||||
|
expect(aiFix).not.toContain('AI issues fixed');
|
||||||
|
|
||||||
|
expect(superviseInstructions).toHaveLength(1);
|
||||||
|
const supervise = superviseInstructions[0]!;
|
||||||
|
expect(supervise).toContain('## Previous Response');
|
||||||
|
expect(supervise).toContain('Arch OK');
|
||||||
|
expect(supervise).toContain('Sec OK');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// =====================================================
|
// =====================================================
|
||||||
|
|||||||
@ -66,6 +66,7 @@ function makeState(movementOutputs?: Map<string, AgentResponse>): WorkflowState
|
|||||||
iteration: 1,
|
iteration: 1,
|
||||||
status: 'running',
|
status: 'running',
|
||||||
movementOutputs: movementOutputs ?? new Map(),
|
movementOutputs: movementOutputs ?? new Map(),
|
||||||
|
lastOutput: undefined,
|
||||||
movementIterations: new Map(),
|
movementIterations: new Map(),
|
||||||
agentSessions: new Map(),
|
agentSessions: new Map(),
|
||||||
userInputs: [],
|
userInputs: [],
|
||||||
|
|||||||
@ -114,6 +114,8 @@ export interface WorkflowState {
|
|||||||
currentMovement: string;
|
currentMovement: string;
|
||||||
iteration: number;
|
iteration: number;
|
||||||
movementOutputs: Map<string, AgentResponse>;
|
movementOutputs: Map<string, AgentResponse>;
|
||||||
|
/** Most recent movement output (used for Previous Response injection) */
|
||||||
|
lastOutput?: AgentResponse;
|
||||||
userInputs: string[];
|
userInputs: string[];
|
||||||
agentSessions: Map<string, string>;
|
agentSessions: Map<string, string>;
|
||||||
/** Per-movement iteration counters (how many times each movement has been executed) */
|
/** Per-movement iteration counters (how many times each movement has been executed) */
|
||||||
|
|||||||
@ -134,6 +134,7 @@ export class MovementExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.movementOutputs.set(step.name, response);
|
state.movementOutputs.set(step.name, response);
|
||||||
|
state.lastOutput = response;
|
||||||
this.emitMovementReports(step);
|
this.emitMovementReports(step);
|
||||||
return { response, instruction };
|
return { response, instruction };
|
||||||
}
|
}
|
||||||
@ -174,4 +175,3 @@ export class MovementExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -160,6 +160,7 @@ export class ParallelRunner {
|
|||||||
};
|
};
|
||||||
|
|
||||||
state.movementOutputs.set(step.name, aggregatedResponse);
|
state.movementOutputs.set(step.name, aggregatedResponse);
|
||||||
|
state.lastOutput = aggregatedResponse;
|
||||||
this.deps.movementExecutor.emitMovementReports(step);
|
this.deps.movementExecutor.emitMovementReports(step);
|
||||||
return { response: aggregatedResponse, instruction: aggregatedInstruction };
|
return { response: aggregatedResponse, instruction: aggregatedInstruction };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export class StateManager {
|
|||||||
currentMovement: config.initialMovement,
|
currentMovement: config.initialMovement,
|
||||||
iteration: 0,
|
iteration: 0,
|
||||||
movementOutputs: new Map(),
|
movementOutputs: new Map(),
|
||||||
|
lastOutput: undefined,
|
||||||
userInputs,
|
userInputs,
|
||||||
agentSessions,
|
agentSessions,
|
||||||
movementIterations: new Map(),
|
movementIterations: new Map(),
|
||||||
@ -111,6 +112,7 @@ export function addUserInput(state: WorkflowState, input: string): void {
|
|||||||
* Get the most recent movement output.
|
* Get the most recent movement output.
|
||||||
*/
|
*/
|
||||||
export function getPreviousOutput(state: WorkflowState): AgentResponse | undefined {
|
export function getPreviousOutput(state: WorkflowState): AgentResponse | undefined {
|
||||||
|
if (state.lastOutput) return state.lastOutput;
|
||||||
const outputs = Array.from(state.movementOutputs.values());
|
const outputs = Array.from(state.movementOutputs.values());
|
||||||
return outputs[outputs.length - 1];
|
return outputs[outputs.length - 1];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -241,7 +241,7 @@ export async function executeWorkflow(
|
|||||||
phase,
|
phase,
|
||||||
phaseName,
|
phaseName,
|
||||||
status: phaseStatus,
|
status: phaseStatus,
|
||||||
...(content ? { content } : {}),
|
content: content ?? '',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
...(phaseError ? { error: phaseError } : {}),
|
...(phaseError ? { error: phaseError } : {}),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,7 +9,9 @@ You are a task summarizer. Convert the conversation into a concrete task instruc
|
|||||||
Requirements:
|
Requirements:
|
||||||
- Output only the final task instruction (no preamble).
|
- Output only the final task instruction (no preamble).
|
||||||
- Be specific about scope and targets (files/modules) if mentioned.
|
- Be specific about scope and targets (files/modules) if mentioned.
|
||||||
- Preserve constraints and "do not" instructions.
|
- Preserve constraints and "do not" instructions **only if they were explicitly stated by the user**.
|
||||||
|
- If the source of a constraint is unclear, do not include it; add it to Open Questions if needed.
|
||||||
|
- Do not include constraints proposed or inferred by the assistant.
|
||||||
- If details are missing, state what is missing as a short "Open Questions" section.
|
- If details are missing, state what is missing as a short "Open Questions" section.
|
||||||
{{#if workflowInfo}}
|
{{#if workflowInfo}}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,9 @@
|
|||||||
- 対象ファイル/モジュールごとに作業内容を明記する
|
- 対象ファイル/モジュールごとに作業内容を明記する
|
||||||
- 優先度(高/中/低)を付けて整理する
|
- 優先度(高/中/低)を付けて整理する
|
||||||
- 再現手順や確認方法があれば含める
|
- 再現手順や確認方法があれば含める
|
||||||
- 制約や「やらないこと」を保持する
|
- 制約や「やらないこと」は**ユーザーが明示したもののみ**保持する
|
||||||
|
- 制約の出所が不明な場合は保持せず、必要なら Open Questions に回す
|
||||||
|
- アシスタントが提案・推測した制約は指示書に含めない
|
||||||
- 情報不足があれば「Open Questions」セクションを短く付ける
|
- 情報不足があれば「Open Questions」セクションを短く付ける
|
||||||
{{#if workflowInfo}}
|
{{#if workflowInfo}}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user