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
|
||||
- Output only the final task instruction (no preamble).
|
||||
- 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).
|
||||
- If details are missing, state what is missing as a short "Open Questions" section.
|
||||
- Clearly specify the concrete work that the workflow will execute.
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
- 出力はタスク指示書のみ(前置き不要)
|
||||
- スコープや対象(ファイル/モジュール)が出ている場合は明確に書く
|
||||
- ユーザー由来の制約や「やらないこと」は保持する
|
||||
- 制約の出所が不明な場合は保持せず、必要なら Open Questions に回す
|
||||
- アシスタントが提案・推測した制約は指示書に含めない
|
||||
- アシスタントの運用上の制約(実行禁止/ツール制限など)は指示に含めない
|
||||
- 情報不足があれば「Open Questions」セクションを短く付ける
|
||||
- ワークフローが実行する具体的な作業内容を明記する
|
||||
|
||||
@ -149,6 +149,128 @@ describe('WorkflowEngine Integration: Happy Path', () => {
|
||||
// plan, implement, ai_review, reviewers(1st), fix, reviewers(2nd), supervise = 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,
|
||||
status: 'running',
|
||||
movementOutputs: movementOutputs ?? new Map(),
|
||||
lastOutput: undefined,
|
||||
movementIterations: new Map(),
|
||||
agentSessions: new Map(),
|
||||
userInputs: [],
|
||||
|
||||
@ -114,6 +114,8 @@ export interface WorkflowState {
|
||||
currentMovement: string;
|
||||
iteration: number;
|
||||
movementOutputs: Map<string, AgentResponse>;
|
||||
/** Most recent movement output (used for Previous Response injection) */
|
||||
lastOutput?: AgentResponse;
|
||||
userInputs: string[];
|
||||
agentSessions: Map<string, string>;
|
||||
/** 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.lastOutput = response;
|
||||
this.emitMovementReports(step);
|
||||
return { response, instruction };
|
||||
}
|
||||
@ -174,4 +175,3 @@ export class MovementExecutor {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -160,6 +160,7 @@ export class ParallelRunner {
|
||||
};
|
||||
|
||||
state.movementOutputs.set(step.name, aggregatedResponse);
|
||||
state.lastOutput = aggregatedResponse;
|
||||
this.deps.movementExecutor.emitMovementReports(step);
|
||||
return { response: aggregatedResponse, instruction: aggregatedInstruction };
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@ export class StateManager {
|
||||
currentMovement: config.initialMovement,
|
||||
iteration: 0,
|
||||
movementOutputs: new Map(),
|
||||
lastOutput: undefined,
|
||||
userInputs,
|
||||
agentSessions,
|
||||
movementIterations: new Map(),
|
||||
@ -111,6 +112,7 @@ export function addUserInput(state: WorkflowState, input: string): void {
|
||||
* Get the most recent movement output.
|
||||
*/
|
||||
export function getPreviousOutput(state: WorkflowState): AgentResponse | undefined {
|
||||
if (state.lastOutput) return state.lastOutput;
|
||||
const outputs = Array.from(state.movementOutputs.values());
|
||||
return outputs[outputs.length - 1];
|
||||
}
|
||||
|
||||
@ -241,7 +241,7 @@ export async function executeWorkflow(
|
||||
phase,
|
||||
phaseName,
|
||||
status: phaseStatus,
|
||||
...(content ? { content } : {}),
|
||||
content: content ?? '',
|
||||
timestamp: new Date().toISOString(),
|
||||
...(phaseError ? { error: phaseError } : {}),
|
||||
};
|
||||
|
||||
@ -9,7 +9,9 @@ You are a task summarizer. Convert the conversation into a concrete task instruc
|
||||
Requirements:
|
||||
- Output only the final task instruction (no preamble).
|
||||
- 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 workflowInfo}}
|
||||
|
||||
|
||||
@ -16,7 +16,9 @@
|
||||
- 対象ファイル/モジュールごとに作業内容を明記する
|
||||
- 優先度(高/中/低)を付けて整理する
|
||||
- 再現手順や確認方法があれば含める
|
||||
- 制約や「やらないこと」を保持する
|
||||
- 制約や「やらないこと」は**ユーザーが明示したもののみ**保持する
|
||||
- 制約の出所が不明な場合は保持せず、必要なら Open Questions に回す
|
||||
- アシスタントが提案・推測した制約は指示書に含めない
|
||||
- 情報不足があれば「Open Questions」セクションを短く付ける
|
||||
{{#if workflowInfo}}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user