takt/src/core/piece/instruction/StatusJudgmentBuilder.ts
nrslib ed367f27df Phase 3判定ロジックをconductorエージェント+フォールバック戦略に分離
Phase 3でレビュアーエージェントが判定タグを出力せず新しい作業を開始する問題を解決。
判定専用のconductorエージェントと4段階フォールバック戦略(AutoSelect→ReportBased→ResponseBased→AgentConsult)を導入し、
ParallelRunnerのlastResponse未配線問題とJudgmentDetectorのアンダースコア対応も修正。
2026-02-05 11:34:23 +09:00

96 lines
2.8 KiB
TypeScript

/**
* Phase 3 instruction builder (status judgment)
*
* Builds instructions for the conductor agent to evaluate work results
* and output the appropriate status tag. Supports report-based and
* response-based input sources.
*
* Renders a single complete template combining the judgment header
* and status rules (criteria table + output format).
*/
import type { PieceMovement, Language } from '../../models/types.js';
import { generateStatusRulesComponents } from './status-rules.js';
import { loadTemplate } from '../../../shared/prompts/index.js';
/**
* Context for building status judgment instruction.
*/
export interface StatusJudgmentContext {
/** Language */
language?: Language;
/** Whether interactive-only rules are enabled */
interactive?: boolean;
/** Pre-read report content (from gatherInput) */
reportContent?: string;
/** Last response from Phase 1 (from gatherInput) */
lastResponse?: string;
/** Input source type for fallback strategies */
inputSource?: 'report' | 'response';
}
/**
* Builds Phase 3 (status judgment) instructions.
*
* Renders a single complete template with all variables.
*/
export class StatusJudgmentBuilder {
constructor(
private readonly step: PieceMovement,
private readonly context: StatusJudgmentContext,
) {}
build(): string {
if (!this.step.rules || this.step.rules.length === 0) {
throw new Error(`StatusJudgmentBuilder called for movement "${this.step.name}" which has no rules`);
}
const language = this.context.language ?? 'en';
const components = generateStatusRulesComponents(
this.step.name,
this.step.rules,
language,
{ interactive: this.context.interactive },
);
// 情報源に応じた内容を構築
const inputSource = this.context.inputSource || 'report';
let contentToJudge = '';
if (inputSource === 'report') {
contentToJudge = this.buildFromReport();
} else if (inputSource === 'response') {
contentToJudge = this.buildFromResponse();
}
return loadTemplate('perform_phase3_message', language, {
reportContent: contentToJudge,
criteriaTable: components.criteriaTable,
outputList: components.outputList,
hasAppendix: components.hasAppendix,
appendixContent: components.appendixContent,
});
}
/**
* Build judgment content from pre-read report content.
*/
private buildFromReport(): string {
if (!this.context.reportContent) {
throw new Error('reportContent is required for report-based judgment');
}
return this.context.reportContent;
}
/**
* Build judgment content from last response.
*/
private buildFromResponse(): string {
if (!this.context.lastResponse) {
throw new Error('lastResponse is required for response-based judgment');
}
return `\n## Agent Response\n\n${this.context.lastResponse}`;
}
}