FN-6235: source reviewer prompts from workflow IR
Deduplicate reviewer policy text by making workflow IR review seams the engine's built-in prompt source. - Move the canonical built-in reviewer prompt into core agent prompts and export seam prompt resolution helpers. - Resolve reviewer prompts from explicit role overrides first, then workflow IR review seams, with the built-in prompt as a fallback. - Update prompt cache and reviewer tests to cover single-source reviewer prompt behavior. - Add a patch changeset for the published Fusion package. Files changed: .../FN-6235-reviewer-prompt-single-source.md | 5 + packages/core/src/__tests__/agent-prompts.test.ts | 8 +- packages/core/src/agent-prompts.ts | 109 +++++++++-- packages/core/src/index.ts | 2 + packages/core/src/workflow-ir-resolver.ts | 31 ++- .../src/__tests__/prompt-cache-integration.test.ts | 10 +- .../reviewer-prompt-single-source.test.ts | 160 +++++++++++++++ packages/engine/src/__tests__/reviewer.test.ts | 97 ++++----- packages/engine/src/prompt-layers.ts | 2 +- packages/engine/src/reviewer.ts | 217 ++------------------- 10 files changed, 370 insertions(+), 271 deletions(-) Fusion-Task-Id: FN-6235 Fusion-Task-Lineage: 725932d1-2469-4507-a0f9-0946c83a1572
This commit is contained in:
5
.changeset/FN-6235-reviewer-prompt-single-source.md
Normal file
5
.changeset/FN-6235-reviewer-prompt-single-source.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Resolve the built-in reviewer base prompt from the workflow IR `review` node instead of an engine-local `REVIEWER_SYSTEM_PROMPT` duplicate. The canonical reviewer policy now lives in the `default-reviewer` agent prompt / built-in workflow seam, with reconciled superset content that preserves the FN-5928/FN-6229 surface-enumeration and symptom-verification gates, undersplit-task guidance, test-quality rules, worktree-boundary review, and the embedded port-4040 safety rule.
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from "../agent-prompts.js";
|
} from "../agent-prompts.js";
|
||||||
import { BUILTIN_CODING_WORKFLOW_IR } from "../builtin-coding-workflow-ir.js";
|
import { BUILTIN_CODING_WORKFLOW_IR } from "../builtin-coding-workflow-ir.js";
|
||||||
import { renderTriagePolicyPlaceholders } from "../builtin-workflow-settings.js";
|
import { renderTriagePolicyPlaceholders } from "../builtin-workflow-settings.js";
|
||||||
import { resolvePlanningPromptFromIr } from "../workflow-ir-resolver.js";
|
import { resolvePlanningPromptFromIr, resolveSeamPromptFromIr } from "../workflow-ir-resolver.js";
|
||||||
import type { AgentPromptsConfig, AgentPromptTemplate } from "../types.js";
|
import type { AgentPromptsConfig, AgentPromptTemplate } from "../types.js";
|
||||||
import type { WorkflowIr } from "../workflow-ir-types.js";
|
import type { WorkflowIr } from "../workflow-ir-types.js";
|
||||||
|
|
||||||
@@ -286,13 +286,14 @@ describe("resolveAgentPrompt", () => {
|
|||||||
expect(renderedPrompt).not.toContain("{{");
|
expect(renderedPrompt).not.toContain("{{");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolves custom planning prompts and ignores IRs without planning prompts", () => {
|
it("resolves custom seam prompts and ignores IRs without matching prompts", () => {
|
||||||
const customIr: WorkflowIr = {
|
const customIr: WorkflowIr = {
|
||||||
version: "v1",
|
version: "v1",
|
||||||
name: "custom",
|
name: "custom",
|
||||||
nodes: [
|
nodes: [
|
||||||
{ id: "start", kind: "start" },
|
{ id: "start", kind: "start" },
|
||||||
{ id: "planning", kind: "prompt", config: { seam: "planning", prompt: "custom planning prompt" } },
|
{ id: "planning", kind: "prompt", config: { seam: "planning", prompt: "custom planning prompt" } },
|
||||||
|
{ id: "review", kind: "prompt", config: { seam: "review", prompt: "custom review prompt" } },
|
||||||
],
|
],
|
||||||
edges: [],
|
edges: [],
|
||||||
};
|
};
|
||||||
@@ -304,7 +305,10 @@ describe("resolveAgentPrompt", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
expect(resolvePlanningPromptFromIr(customIr)).toBe("custom planning prompt");
|
expect(resolvePlanningPromptFromIr(customIr)).toBe("custom planning prompt");
|
||||||
|
expect(resolveSeamPromptFromIr(customIr, "review")).toBe("custom review prompt");
|
||||||
|
expect(resolveSeamPromptFromIr(BUILTIN_CODING_WORKFLOW_IR, "review")).toBe(resolveAgentPrompt("reviewer"));
|
||||||
expect(resolvePlanningPromptFromIr(noPlanningIr)).toBeUndefined();
|
expect(resolvePlanningPromptFromIr(noPlanningIr)).toBeUndefined();
|
||||||
|
expect(resolveSeamPromptFromIr(noPlanningIr, "review")).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("built-in triage prompt requires surface enumeration for bug-fix specs", () => {
|
it("built-in triage prompt requires surface enumeration for bug-fix specs", () => {
|
||||||
|
|||||||
@@ -7,11 +7,10 @@
|
|||||||
* - Additional role variants (senior-engineer, strict-reviewer, concise-triage)
|
* - Additional role variants (senior-engineer, strict-reviewer, concise-triage)
|
||||||
* - A resolver function that merges custom templates from project settings with built-ins
|
* - A resolver function that merges custom templates from project settings with built-ins
|
||||||
*
|
*
|
||||||
* NOTE: The built-in prompt texts are derived from the engine's hardcoded prompts
|
* NOTE: Built-in prompt texts that feed workflow seams live here as the canonical
|
||||||
* (EXECUTOR_SYSTEM_PROMPT, TRIAGE_SYSTEM_PROMPT, REVIEWER_SYSTEM_PROMPT, and the
|
* source for @fusion/core and @fusion/engine. Engine code should resolve triage
|
||||||
* merger prompt). They should be kept in sync when the engine prompts change.
|
* and reviewer built-ins through workflow IR seam prompts instead of carrying
|
||||||
* Since @fusion/core cannot import @fusion/engine (circular dependency), these
|
* duplicate policy constants.
|
||||||
* are maintained as inline strings.
|
|
||||||
*
|
*
|
||||||
* @module agent-prompts
|
* @module agent-prompts
|
||||||
*/
|
*/
|
||||||
@@ -19,7 +18,7 @@
|
|||||||
import type { AgentCapability, AgentPromptTemplate, AgentPromptsConfig } from "./types.js";
|
import type { AgentCapability, AgentPromptTemplate, AgentPromptsConfig } from "./types.js";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Built-in prompt text (derived from engine constants — keep in sync)
|
// Built-in prompt text (canonical source for workflow seam prompts)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const EXECUTOR_PROMPT_TEXT = `You are a task execution agent for "fn", an AI-orchestrated task board.
|
const EXECUTOR_PROMPT_TEXT = `You are a task execution agent for "fn", an AI-orchestrated task board.
|
||||||
@@ -538,11 +537,26 @@ Use this exact checklist (keep it verbatim — do not expand or reorder):
|
|||||||
|
|
||||||
Only inject this section when the task genuinely touches frontend UI. Omit it for backend-only, config-only, or documentation-only tasks.`;;
|
Only inject this section when the task genuinely touches frontend UI. Omit it for backend-only, config-only, or documentation-only tasks.`;;
|
||||||
|
|
||||||
|
// FN-6235: single source for the built-in reviewer policy; the engine REVIEWER_SYSTEM_PROMPT duplicate was removed.
|
||||||
const REVIEWER_PROMPT_TEXT = `You are an independent code and plan reviewer.
|
const REVIEWER_PROMPT_TEXT = `You are an independent code and plan reviewer.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
You are an objective quality gate for plans, code, and specs.
|
||||||
|
You are neither the implementor's advocate nor adversary: your job is evidence-based assessment that protects delivery quality.
|
||||||
|
|
||||||
You provide quality assessment for task implementations. You have full read
|
You provide quality assessment for task implementations. You have full read
|
||||||
access to the codebase and can run commands to inspect code.
|
access to the codebase and can run commands to inspect code.
|
||||||
|
|
||||||
|
## What to Look For
|
||||||
|
- Correctness against stated requirements
|
||||||
|
- Edge-case handling and failure-path behavior
|
||||||
|
- Test adequacy (behavior-focused coverage, meaningful assertions)
|
||||||
|
- Consistency with existing project patterns and conventions
|
||||||
|
- Security, data-safety, and permission boundary concerns
|
||||||
|
- Performance implications where changes affect hot paths or heavy operations
|
||||||
|
|
||||||
|
Review efficiently: prioritize high-impact correctness/risk issues first. Do not spend blocking attention on style nits when substantive defects exist.
|
||||||
|
|
||||||
## Verdict Criteria
|
## Verdict Criteria
|
||||||
|
|
||||||
- **APPROVE** — Step will achieve its stated outcomes. Minor suggestions go in
|
- **APPROVE** — Step will achieve its stated outcomes. Minor suggestions go in
|
||||||
@@ -556,6 +570,11 @@ access to the codebase and can run commands to inspect code.
|
|||||||
|
|
||||||
### APPROVE vs REVISE
|
### APPROVE vs REVISE
|
||||||
|
|
||||||
|
Concrete examples:
|
||||||
|
- APPROVE: implementation satisfies outcomes; only optional cleanup or minor wording suggestions remain.
|
||||||
|
- REVISE: a required behavior is missing, tests are insufficient for changed behavior, or a likely regression exists.
|
||||||
|
- RETHINK: the approach conflicts with architecture/task goals such that incremental edits are unlikely to rescue it.
|
||||||
|
|
||||||
**APPROVE** when:
|
**APPROVE** when:
|
||||||
- The approach will work, but you see a cleaner alternative
|
- The approach will work, but you see a cleaner alternative
|
||||||
- Documentation style could improve
|
- Documentation style could improve
|
||||||
@@ -568,6 +587,7 @@ access to the codebase and can run commands to inspect code.
|
|||||||
- Backward compatibility is broken without migration
|
- Backward compatibility is broken without migration
|
||||||
- Code outside the task's File Scope is deleted, removed, or gutted (out-of-scope removal)
|
- Code outside the task's File Scope is deleted, removed, or gutted (out-of-scope removal)
|
||||||
- Existing functionality is removed without a corresponding changeset explaining the removal
|
- Existing functionality is removed without a corresponding changeset explaining the removal
|
||||||
|
- Code changes were made outside the assigned task worktree, unless the path is an expected exception such as project memory or task attachments
|
||||||
|
|
||||||
### Do NOT issue REVISE for
|
### Do NOT issue REVISE for
|
||||||
- STATUS/formatting preferences
|
- STATUS/formatting preferences
|
||||||
@@ -610,7 +630,7 @@ access to the codebase and can run commands to inspect code.
|
|||||||
|
|
||||||
### Test Gaps
|
### Test Gaps
|
||||||
- [Missing test scenarios]
|
- [Missing test scenarios]
|
||||||
- [For bug fixes, call out any repro-only regression test that does not assert the invariant across the enumerated surfaces. Issue REVISE when coverage stops at the single reported case instead of spanning the \`## Surface Enumeration\` checklist (FN-5893; see FN-5787/FN-5789/FN-5803, FN-5797/FN-5875/FN-5919, and FN-5751).]
|
- [For bug fixes and UI-affordance add/remove changes, call out any single-surface-only test that doesn't verify the invariant across the spec's enumerated surfaces. For UI-affordance removals, also flag tests that don't verify the removed affordance's container/wrapper is fully cleaned up on both desktop and mobile breakpoints. Issue REVISE when coverage stops at the single reported surface (FN-6134; see FN-6115→FN-6118→FN-6123 for the motivating multi-task incident). Keep enforcing FN-5893 for bug fixes; see FN-5787/FN-5789/FN-5803, FN-5797/FN-5875/FN-5919, and FN-5751.]
|
||||||
|
|
||||||
### Suggestions
|
### Suggestions
|
||||||
- [Optional improvements, not blocking]
|
- [Optional improvements, not blocking]
|
||||||
@@ -635,18 +655,85 @@ access to the codebase and can run commands to inspect code.
|
|||||||
- **File scope accuracy:** [All affected files listed? No extras?]
|
- **File scope accuracy:** [All affected files listed? No extras?]
|
||||||
- **Dependency correctness:** [Dependencies exist and are appropriate?]
|
- **Dependency correctness:** [Dependencies exist and are appropriate?]
|
||||||
- **Testing requirements:** [Real automated tests required, not just typechecks?]
|
- **Testing requirements:** [Real automated tests required, not just typechecks?]
|
||||||
- **Surface enumeration:** [For bug-fix specs, is \`## Surface Enumeration\` present and does it enumerate the relevant providers/bridges/execution paths, desktop + mobile breakpoints/platforms, empty/undefined/duplicate/populated states, and shared hooks/components/modules/helpers? Missing or incomplete coverage is a blocking REVISE.]
|
- **Surface enumeration:** [For bug-fix specs and UI-affordance add/remove specs, is \`## Surface Enumeration\` present and does it enumerate the relevant providers/bridges/execution paths, desktop + mobile breakpoints/platforms, empty/undefined/duplicate/populated states, and shared hooks/components/modules/helpers? For UI-affordance add/remove tasks, also verify: (a) the spec searches for ALL components rendering the affordance, not just the one the user pointed at; (b) the spec explicitly addresses leftover shells after removal across desktop and mobile breakpoints. Missing or incomplete coverage is a blocking REVISE.]
|
||||||
|
- **Symptom verification:** [For bug-class/bug-fix specs only, is \`## Symptom Verification\` present and complete with **Original symptom**, **Exact reproduction**, and **Assertion it is gone**? A bug-class spec whose final verification only checks green build/tests without reproducing the original failure and asserting it no longer occurs is a blocking REVISE under FN-5893. Missing, empty, or incomplete \`## Symptom Verification\` is a blocking REVISE for bug-class specs; feature/docs/non-bug specs are not required to carry it.]
|
||||||
- **Documentation completeness:** [Must Update / Check If Affected sections present?]
|
- **Documentation completeness:** [Must Update / Check If Affected sections present?]
|
||||||
|
- **Dangling task-document references:** [No \`.fusion/tasks/<id>/<file>\` path is cited in Context, Steps, or File Scope unless the file exists or is explicitly created as a \`(new)\` artifact in this spec. References to nonexistent task-local artifacts are a blocking REVISE.]
|
||||||
- **Sizing & review level:** [Size and review level appropriate for the work?]
|
- **Sizing & review level:** [Size and review level appropriate for the work?]
|
||||||
- **Subtask breakdown:** [Were complex tasks appropriately split into 2-5 child tasks? A task with 8+ implementation steps, affecting 3+ packages, should have been divided]
|
- **Subtask breakdown:** [Only flag genuinely oversized specs (12+ implementation steps, OR 5+ truly independent deliverables that could ship separately). Do NOT flag a coherent vertical change just because it touches multiple packages. When borderline, prefer leaving the task whole.]
|
||||||
- **User comment coverage:** [Were all user comments addressed? Every user comment must be reflected in the spec — missing coverage is a blocking REVISE]
|
- **User comment coverage:** [Were all user comments addressed? Every user comment must be reflected in the spec — missing coverage is a blocking REVISE]
|
||||||
|
|
||||||
### Suggestions
|
### Suggestions
|
||||||
- [Optional improvements, not blocking]
|
- [Optional improvements, not blocking]
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## Safety Rules
|
## Spec Review — Undersplit Task Detection
|
||||||
- **NEVER kill processes on port 4040.** Port 4040 is the production dashboard. If you need to test server endpoints, start a server on a different port (\`--port 0\` for random). If port 4040 is occupied, use a different port — do NOT kill the occupant. Issue REVISE if the executor kills or attempts to kill processes on port 4040.`;
|
|
||||||
|
When reviewing specs, assess whether the task should have been broken into subtasks. The bar for splitting is high — most tasks should remain whole. Coordination overhead (worktrees, dependency wiring, merge sequencing) is real, so splitting must clearly pay for itself.
|
||||||
|
|
||||||
|
**Default position:** do NOT flag undersplit. Reach for it only when the spec is genuinely oversized.
|
||||||
|
|
||||||
|
**Flag as REVISE only when ALL of the following are true:**
|
||||||
|
- The spec has 12+ implementation steps, OR contains 5+ clearly independent deliverables that could be shipped separately by different people
|
||||||
|
- The deliverables are NOT a coherent vertical change (a single feature touching core + dashboard + tests is coherent — do not split it)
|
||||||
|
- Splitting would produce children that each have ≥4 steps and a clearly distinct scope
|
||||||
|
|
||||||
|
If the spec is borderline (under those thresholds, or arguable), put your splitting suggestion in the **Suggestions** section instead of REVISE — the planner can take it or leave it.
|
||||||
|
|
||||||
|
**How to flag an undersplit task (only when the criteria above are met):**
|
||||||
|
Say explicitly: "This task should be broken into subtasks because [specific reason]."
|
||||||
|
Recommend the number of child tasks (2-5) and what each should cover.
|
||||||
|
Instruct the planner to:
|
||||||
|
1. Use the \`fn_task_create\` tool to create 2–5 child tasks from the oversized spec
|
||||||
|
2. Do NOT write a parent PROMPT.md — the parent will be closed automatically after children are created
|
||||||
|
(Not write a parent PROMPT.md is also unacceptable.)
|
||||||
|
3. Make each child cover one coherent deliverable with clear scope boundaries
|
||||||
|
|
||||||
|
Example REVISE feedback for a genuinely oversized task:
|
||||||
|
"This task has 14 steps and contains 4 independent deliverables (engine integration, dashboard UI, CLI command, migration tooling) that could ship separately. Use fn_task_create to split into: (1) engine logic, (2) dashboard UI, (3) CLI integration, (4) migration tooling. Do not write a parent PROMPT."
|
||||||
|
|
||||||
|
**Do NOT flag if ANY of these apply:**
|
||||||
|
- The spec has 11 or fewer implementation steps
|
||||||
|
- Steps are sequential and tightly coupled (e.g., a pipeline where each step depends on the previous)
|
||||||
|
- The task is a vertical change touching multiple packages for one coherent feature (typical in this monorepo)
|
||||||
|
- The task is a bug fix, regardless of how many files it touches
|
||||||
|
- Splitting would create coordination overhead that exceeds the benefit
|
||||||
|
|
||||||
|
## Plan Granularity
|
||||||
|
|
||||||
|
When reviewing plans, assess whether the approach achieves the step's OUTCOMES —
|
||||||
|
not whether every function and parameter is listed.
|
||||||
|
|
||||||
|
Good plan: identifies key behavioral changes, calls out risks, has a testing strategy.
|
||||||
|
Do NOT demand function-level implementation checklists.
|
||||||
|
|
||||||
|
## Test Quality Review
|
||||||
|
|
||||||
|
When reviewing tests, check that they verify observable behavior and regression risk (not only implementation trivia).
|
||||||
|
Flag REVISE when key edge cases or failure modes for changed behavior are untested.
|
||||||
|
For bug fixes, apply FN-5893 strictly: if the regression test only reproduces the reported case instead of asserting the invariant across the spec's \`## Surface Enumeration\` surfaces, issue REVISE. Treat that as a repro-only regression test; issue REVISE when coverage stops at the single reported case instead of spanning the \`## Surface Enumeration\` checklist. Use the motivating recurrences (FN-5787/FN-5789/FN-5803, FN-5797/FN-5875/FN-5919, and FN-5751) as concrete examples of why repro-only coverage is insufficient.
|
||||||
|
For bug-class/bug-fix specs, also enforce symptom-based acceptance: if the spec is missing \`## Symptom Verification\`, leaves it empty/incomplete, lacks **Original symptom**, **Exact reproduction**, or **Assertion it is gone**, or its final verification only checks green build/tests without reproducing the original failure condition and asserting it no longer occurs, issue REVISE. Do not require \`## Symptom Verification\` for feature/docs/non-bug specs.
|
||||||
|
For UI-affordance add/remove changes, apply the same surface-enumeration strictness: if the test only checks the single surface the user reported instead of all enumerated surfaces, issue REVISE. For UI-affordance removals, require coverage/evidence that empty button shells, orphaned click targets, now-unused wrappers, and dangling aria-labels are cleaned up across desktop and mobile breakpoints; FN-6115/FN-6118/FN-6123 is the motivating recurrence.
|
||||||
|
|
||||||
|
## Worktree Boundary Review
|
||||||
|
|
||||||
|
For code reviews, verify that implementation changes are in the assigned task
|
||||||
|
worktree. The review request includes the current worktree path. Inspect git
|
||||||
|
state and recent commits from that worktree, and treat changes outside it as a
|
||||||
|
blocking REVISE unless they are expected project-root state such as
|
||||||
|
\`.fusion/memory/\` files, task attachments, or other explicitly documented
|
||||||
|
Fusion metadata. If you see edits or commits in the primary project checkout
|
||||||
|
instead of the task worktree, call that out directly and ask the worker to move
|
||||||
|
the changes into the assigned worktree.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Be specific — reference actual files and line numbers
|
||||||
|
- Be constructive — suggest fixes, not just problems
|
||||||
|
- Be proportional — don't block on style nits
|
||||||
|
- Output your review as plain text (not to a file)
|
||||||
|
- **NEVER kill processes on port 4040.** Port 4040 is the production dashboard. If you need to test server endpoints, start a server on a different port (\`--port 0\` for random). If port 4040 is occupied, use a different port — do NOT kill the occupant. Issue REVISE if the executor kills or attempts to kill processes on port 4040.
|
||||||
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base merger prompt text (without commit format instructions, which are
|
* Base merger prompt text (without commit format instructions, which are
|
||||||
|
|||||||
@@ -314,7 +314,9 @@ export {
|
|||||||
export {
|
export {
|
||||||
resolveWorkflowIrForTask,
|
resolveWorkflowIrForTask,
|
||||||
resolveWorkflowIrById,
|
resolveWorkflowIrById,
|
||||||
|
resolveSeamPromptFromIr,
|
||||||
resolvePlanningPromptFromIr,
|
resolvePlanningPromptFromIr,
|
||||||
|
resolveTaskSeamPrompt,
|
||||||
resolveTaskPlanningPrompt,
|
resolveTaskPlanningPrompt,
|
||||||
type WorkflowIrResolverStore,
|
type WorkflowIrResolverStore,
|
||||||
} from "./workflow-ir-resolver.js";
|
} from "./workflow-ir-resolver.js";
|
||||||
|
|||||||
@@ -26,31 +26,50 @@ export interface WorkflowIrResolverStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the planning seam prompt from a resolved workflow IR.
|
* Extract a prompt seam's prompt text from a resolved workflow IR.
|
||||||
*
|
*
|
||||||
* Planning seam nodes are prompt nodes with `config.seam === "planning"`;
|
* Seam prompt nodes are prompt nodes with `config.seam === seam`;
|
||||||
* `config.prompt` carries the text installed by builtinPromptConfig or a custom
|
* `config.prompt` carries the text installed by builtinPromptConfig or a custom
|
||||||
* workflow author. Empty/missing prompts return undefined so callers can apply
|
* workflow author. Empty/missing prompts return undefined so callers can apply
|
||||||
* their own fail-soft fallback.
|
* their own fail-soft fallback.
|
||||||
*/
|
*/
|
||||||
export function resolvePlanningPromptFromIr(ir: WorkflowIr): string | undefined {
|
export function resolveSeamPromptFromIr(ir: WorkflowIr, seam: string): string | undefined {
|
||||||
for (const node of ir.nodes) {
|
for (const node of ir.nodes) {
|
||||||
if (node.kind !== "prompt") continue;
|
if (node.kind !== "prompt") continue;
|
||||||
if (node.config?.seam !== "planning") continue;
|
if (node.config?.seam !== seam) continue;
|
||||||
const prompt = node.config.prompt;
|
const prompt = node.config.prompt;
|
||||||
if (typeof prompt === "string" && prompt.trim().length > 0) return prompt;
|
if (typeof prompt === "string" && prompt.trim().length > 0) return prompt;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Extract the planning seam prompt from a resolved workflow IR. */
|
||||||
|
export function resolvePlanningPromptFromIr(ir: WorkflowIr): string | undefined {
|
||||||
|
return resolveSeamPromptFromIr(ir, "planning");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolve a task's seam prompt via its selected workflow IR. */
|
||||||
|
export async function resolveTaskSeamPrompt(
|
||||||
|
store: WorkflowIrResolverStore,
|
||||||
|
taskId: string,
|
||||||
|
seam: string,
|
||||||
|
irCache?: Map<string, WorkflowIr>,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const ir = await resolveWorkflowIrForTask(store, taskId, irCache);
|
||||||
|
return resolveSeamPromptFromIr(ir, seam);
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Resolve a task's planning seam prompt via its selected workflow IR. */
|
/** Resolve a task's planning seam prompt via its selected workflow IR. */
|
||||||
export async function resolveTaskPlanningPrompt(
|
export async function resolveTaskPlanningPrompt(
|
||||||
store: WorkflowIrResolverStore,
|
store: WorkflowIrResolverStore,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
irCache?: Map<string, WorkflowIr>,
|
irCache?: Map<string, WorkflowIr>,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
const ir = await resolveWorkflowIrForTask(store, taskId, irCache);
|
return resolveTaskSeamPrompt(store, taskId, "planning", irCache);
|
||||||
return resolvePlanningPromptFromIr(ir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { resolveAgentPrompt } from "@fusion/core";
|
||||||
import { buildPromptLayers, collapsePromptLayers, type SystemPromptLayers } from "../prompt-layers.js";
|
import { buildPromptLayers, collapsePromptLayers, type SystemPromptLayers } from "../prompt-layers.js";
|
||||||
import { REVIEWER_SYSTEM_PROMPT } from "../reviewer.js";
|
|
||||||
|
const DEFAULT_REVIEWER_PROMPT = resolveAgentPrompt("reviewer");
|
||||||
|
|
||||||
describe("cross-session prompt cache integration", () => {
|
describe("cross-session prompt cache integration", () => {
|
||||||
const MEMORY_INSTRUCTIONS = "\n## Memory\n\nUse fn_memory_search to look up relevant context.";
|
const MEMORY_INSTRUCTIONS = "\n## Memory\n\nUse fn_memory_search to look up relevant context.";
|
||||||
|
|
||||||
function simulateReviewerSession(sessionIndex: number): SystemPromptLayers {
|
function simulateReviewerSession(sessionIndex: number): SystemPromptLayers {
|
||||||
return buildPromptLayers({
|
return buildPromptLayers({
|
||||||
basePrompt: REVIEWER_SYSTEM_PROMPT,
|
basePrompt: DEFAULT_REVIEWER_PROMPT,
|
||||||
agentInstructions: `Session ${sessionIndex}: custom instructions that vary per agent.`,
|
agentInstructions: `Session ${sessionIndex}: custom instructions that vary per agent.`,
|
||||||
memorySection: MEMORY_INSTRUCTIONS,
|
memorySection: MEMORY_INSTRUCTIONS,
|
||||||
pluginContributions: sessionIndex % 2 === 0
|
pluginContributions: sessionIndex % 2 === 0
|
||||||
@@ -42,8 +44,8 @@ describe("cross-session prompt cache integration", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("stable prefix starts with REVIEWER_SYSTEM_PROMPT", () => {
|
it("stable prefix starts with the canonical default reviewer prompt", () => {
|
||||||
const layers = simulateReviewerSession(0);
|
const layers = simulateReviewerSession(0);
|
||||||
expect(layers.stable.startsWith(REVIEWER_SYSTEM_PROMPT)).toBe(true);
|
expect(layers.stable.startsWith(DEFAULT_REVIEWER_PROMPT)).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import {
|
||||||
|
BUILTIN_CODING_WORKFLOW_IR,
|
||||||
|
resolveAgentPrompt,
|
||||||
|
resolveSeamPromptFromIr,
|
||||||
|
type WorkflowIr,
|
||||||
|
} from "@fusion/core";
|
||||||
|
|
||||||
|
vi.mock("../pi.js", () => ({
|
||||||
|
createFnAgent: vi.fn(),
|
||||||
|
describeModel: vi.fn().mockReturnValue("mock-provider/mock-model"),
|
||||||
|
promptWithFallback: vi.fn(async (session, prompt, options) => {
|
||||||
|
if (options === undefined) {
|
||||||
|
await session.prompt(prompt);
|
||||||
|
} else {
|
||||||
|
await session.prompt(prompt, options);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { reviewStep } from "../reviewer.js";
|
||||||
|
import { createFnAgent } from "../pi.js";
|
||||||
|
|
||||||
|
const mockedCreateFnAgent = vi.mocked(createFnAgent);
|
||||||
|
|
||||||
|
function createMockSession(reviewText = "### Verdict: APPROVE\n### Summary\nLooks good.") {
|
||||||
|
return {
|
||||||
|
session: {
|
||||||
|
prompt: vi.fn().mockResolvedValue(undefined),
|
||||||
|
subscribe: vi.fn().mockImplementation((cb: any) => {
|
||||||
|
cb({
|
||||||
|
type: "message_update",
|
||||||
|
assistantMessageEvent: { type: "text_delta", delta: reviewText },
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
dispose: vi.fn(),
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStore(workflowId = "builtin:coding", customIr?: WorkflowIr) {
|
||||||
|
return {
|
||||||
|
getSettings: vi.fn().mockResolvedValue({}),
|
||||||
|
getTaskWorkflowSelection: vi.fn().mockReturnValue({ workflowId, stepIds: [] }),
|
||||||
|
getWorkflowDefinition: vi.fn().mockImplementation(async (id: string) => {
|
||||||
|
if (customIr && id === workflowId) return { ir: customIr };
|
||||||
|
return undefined;
|
||||||
|
}),
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function captureReviewerSystemPrompt(options: Parameters<typeof reviewStep>[7] = {}) {
|
||||||
|
mockedCreateFnAgent.mockResolvedValue(createMockSession());
|
||||||
|
await reviewStep(
|
||||||
|
"/tmp/worktree",
|
||||||
|
"FN-6235",
|
||||||
|
1,
|
||||||
|
"Review prompt source",
|
||||||
|
"plan",
|
||||||
|
"# Plan",
|
||||||
|
undefined,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
return mockedCreateFnAgent.mock.calls[0][0].systemPrompt as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("reviewer prompt single source", () => {
|
||||||
|
it("does not reintroduce an engine reviewer policy constant", () => {
|
||||||
|
const reviewerSource = readFileSync(
|
||||||
|
resolve(fileURLToPath(new URL("..", import.meta.url)), "reviewer.ts"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(reviewerSource).not.toMatch(/export const REVIEWER_SYSTEM_PROMPT\s*=/);
|
||||||
|
expect(reviewerSource).not.toMatch(/export const [A-Z_]*REVIEWER[A-Z_]*SYSTEM_PROMPT\s*=/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps builtin coding review seam byte-identical to the default reviewer prompt", () => {
|
||||||
|
expect(resolveSeamPromptFromIr(BUILTIN_CODING_WORKFLOW_IR, "review")).toBe(resolveAgentPrompt("reviewer"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the builtin coding IR review-node prompt when no user override is set", async () => {
|
||||||
|
const systemPrompt = await captureReviewerSystemPrompt({ store: createStore() });
|
||||||
|
|
||||||
|
expect(systemPrompt).toBe(resolveSeamPromptFromIr(BUILTIN_CODING_WORKFLOW_IR, "review"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses a selected custom workflow review-node prompt", async () => {
|
||||||
|
const customIr: WorkflowIr = {
|
||||||
|
version: "v1",
|
||||||
|
name: "custom-reviewer",
|
||||||
|
nodes: [
|
||||||
|
{ id: "start", kind: "start" },
|
||||||
|
{ id: "review", kind: "prompt", config: { seam: "review", prompt: "custom workflow reviewer prompt" } },
|
||||||
|
],
|
||||||
|
edges: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const systemPrompt = await captureReviewerSystemPrompt({ store: createStore("WF-review", customIr) });
|
||||||
|
|
||||||
|
expect(systemPrompt).toBe("custom workflow reviewer prompt");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves reviewer user-override precedence over workflow IR prompts", async () => {
|
||||||
|
const customIr: WorkflowIr = {
|
||||||
|
version: "v1",
|
||||||
|
name: "custom-reviewer",
|
||||||
|
nodes: [
|
||||||
|
{ id: "review", kind: "prompt", config: { seam: "review", prompt: "workflow prompt should not win" } },
|
||||||
|
],
|
||||||
|
edges: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const systemPrompt = await captureReviewerSystemPrompt({
|
||||||
|
store: createStore("WF-review", customIr),
|
||||||
|
agentPrompts: {
|
||||||
|
templates: [{
|
||||||
|
id: "custom-reviewer",
|
||||||
|
name: "Custom Reviewer",
|
||||||
|
description: "Project reviewer override",
|
||||||
|
role: "reviewer",
|
||||||
|
prompt: "user override reviewer prompt",
|
||||||
|
}],
|
||||||
|
roleAssignments: { reviewer: "custom-reviewer" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(systemPrompt).toBe("user override reviewer prompt");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to a non-empty default reviewer prompt when no store is provided", async () => {
|
||||||
|
const systemPrompt = await captureReviewerSystemPrompt();
|
||||||
|
|
||||||
|
expect(systemPrompt).toBe(resolveAgentPrompt("reviewer"));
|
||||||
|
expect(systemPrompt.trim().length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(["plan", "code", "spec"] as const)("uses the same resolved base prompt for %s reviews", async (reviewType) => {
|
||||||
|
mockedCreateFnAgent.mockResolvedValue(createMockSession());
|
||||||
|
await reviewStep(
|
||||||
|
"/tmp/worktree",
|
||||||
|
"FN-6235",
|
||||||
|
1,
|
||||||
|
"Review prompt source",
|
||||||
|
reviewType,
|
||||||
|
"# Prompt",
|
||||||
|
undefined,
|
||||||
|
{ store: createStore() },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockedCreateFnAgent.mock.calls[0][0].systemPrompt).toBe(resolveAgentPrompt("reviewer"));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,9 +12,12 @@ vi.mock("../pi.js", () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { reviewStep, REVIEWER_SYSTEM_PROMPT } from "../reviewer.js";
|
import { resolveAgentPrompt } from "@fusion/core";
|
||||||
|
import { reviewStep } from "../reviewer.js";
|
||||||
import { createFnAgent, promptWithFallback } from "../pi.js";
|
import { createFnAgent, promptWithFallback } from "../pi.js";
|
||||||
|
|
||||||
|
const DEFAULT_REVIEWER_PROMPT = resolveAgentPrompt("reviewer");
|
||||||
|
|
||||||
const mockedCreateFnAgent = vi.mocked(createFnAgent);
|
const mockedCreateFnAgent = vi.mocked(createFnAgent);
|
||||||
const mockedPromptWithFallback = vi.mocked(promptWithFallback);
|
const mockedPromptWithFallback = vi.mocked(promptWithFallback);
|
||||||
const CONTEXT_LIMIT_ERROR = "exceeded model token limit: 262144 (requested: 262879)";
|
const CONTEXT_LIMIT_ERROR = "exceeded model token limit: 262144 (requested: 262879)";
|
||||||
@@ -293,55 +296,55 @@ describe("reviewStep — spec review type", () => {
|
|||||||
|
|
||||||
describe("FN-5928 surface-enumeration review-gate wording", () => {
|
describe("FN-5928 surface-enumeration review-gate wording", () => {
|
||||||
it("requires spec reviews to block missing or incomplete surface enumeration for bug-fix specs", () => {
|
it("requires spec reviews to block missing or incomplete surface enumeration for bug-fix specs", () => {
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("**Surface enumeration:**");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("**Surface enumeration:**");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toMatch(
|
expect(DEFAULT_REVIEWER_PROMPT).toMatch(
|
||||||
/For bug-fix specs and UI-affordance add\/remove specs, is `## Surface Enumeration` present[\s\S]*Missing or incomplete coverage is a blocking REVISE\./,
|
/For bug-fix specs and UI-affordance add\/remove specs, is `## Surface Enumeration` present[\s\S]*Missing or incomplete coverage is a blocking REVISE\./,
|
||||||
);
|
);
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("desktop + mobile breakpoints/platforms");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("desktop + mobile breakpoints/platforms");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("shared hooks/components/modules/helpers");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("shared hooks/components/modules/helpers");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("bug-fix specs and UI-affordance add/remove specs");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("bug-fix specs and UI-affordance add/remove specs");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("requires code reviews to reject repro-only regression tests for bug fixes", () => {
|
it("requires code reviews to reject repro-only regression tests for bug fixes", () => {
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toMatch(
|
expect(DEFAULT_REVIEWER_PROMPT).toMatch(
|
||||||
/For bug fixes, apply FN-5893 strictly: if the regression test only reproduces the reported case instead of asserting the invariant across the spec's `## Surface Enumeration` surfaces, issue REVISE\./,
|
/For bug fixes, apply FN-5893 strictly: if the regression test only reproduces the reported case instead of asserting the invariant across the spec's `## Surface Enumeration` surfaces, issue REVISE\./,
|
||||||
);
|
);
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("single-surface-only test");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("single-surface-only test");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("doesn't verify the invariant across the spec's enumerated surfaces");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("doesn't verify the invariant across the spec's enumerated surfaces");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("Keep enforcing FN-5893 for bug fixes");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("Keep enforcing FN-5893 for bug fixes");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("FN-5787/FN-5789/FN-5803");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("FN-5787/FN-5789/FN-5803");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("FN-5797/FN-5875/FN-5919");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("FN-5797/FN-5875/FN-5919");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("FN-5751");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("FN-5751");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("requires spec reviews to block bug-class specs missing symptom verification", () => {
|
it("requires spec reviews to block bug-class specs missing symptom verification", () => {
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("**Symptom verification:**");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("**Symptom verification:**");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toMatch(
|
expect(DEFAULT_REVIEWER_PROMPT).toMatch(
|
||||||
/For bug-class\/bug-fix specs only, is `## Symptom Verification` present and complete with \*\*Original symptom\*\*, \*\*Exact reproduction\*\*, and \*\*Assertion it is gone\*\*\?/,
|
/For bug-class\/bug-fix specs only, is `## Symptom Verification` present and complete with \*\*Original symptom\*\*, \*\*Exact reproduction\*\*, and \*\*Assertion it is gone\*\*\?/,
|
||||||
);
|
);
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain(
|
expect(DEFAULT_REVIEWER_PROMPT).toContain(
|
||||||
"A bug-class spec whose final verification only checks green build/tests without reproducing the original failure and asserting it no longer occurs is a blocking REVISE under FN-5893",
|
"A bug-class spec whose final verification only checks green build/tests without reproducing the original failure and asserting it no longer occurs is a blocking REVISE under FN-5893",
|
||||||
);
|
);
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain(
|
expect(DEFAULT_REVIEWER_PROMPT).toContain(
|
||||||
"Missing, empty, or incomplete `## Symptom Verification` is a blocking REVISE for bug-class specs",
|
"Missing, empty, or incomplete `## Symptom Verification` is a blocking REVISE for bug-class specs",
|
||||||
);
|
);
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("feature/docs/non-bug specs are not required to carry it");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("feature/docs/non-bug specs are not required to carry it");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("requires code reviews to reject green-build-only symptom acceptance for bug fixes", () => {
|
it("requires code reviews to reject green-build-only symptom acceptance for bug fixes", () => {
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toMatch(
|
expect(DEFAULT_REVIEWER_PROMPT).toMatch(
|
||||||
/For bug-class\/bug-fix specs, also enforce symptom-based acceptance:[\s\S]*final verification only checks green build\/tests without reproducing the original failure condition and asserting it no longer occurs, issue REVISE\./,
|
/For bug-class\/bug-fix specs, also enforce symptom-based acceptance:[\s\S]*final verification only checks green build\/tests without reproducing the original failure condition and asserting it no longer occurs, issue REVISE\./,
|
||||||
);
|
);
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("lacks **Original symptom**, **Exact reproduction**, or **Assertion it is gone**");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("lacks **Original symptom**, **Exact reproduction**, or **Assertion it is gone**");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("Do not require `## Symptom Verification` for feature/docs/non-bug specs");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("Do not require `## Symptom Verification` for feature/docs/non-bug specs");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("requires spec/code reviews to enforce surface enumeration for UI-affordance add/remove tasks", () => {
|
it("requires spec/code reviews to enforce surface enumeration for UI-affordance add/remove tasks", () => {
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("leftover shells after removal");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("leftover shells after removal");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("For bug fixes and UI-affordance add/remove changes");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("For bug fixes and UI-affordance add/remove changes");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("UI-affordance removals");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("UI-affordance removals");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("For UI-affordance add/remove changes, apply the same surface-enumeration strictness");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("For UI-affordance add/remove changes, apply the same surface-enumeration strictness");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("FN-6115/FN-6118/FN-6123");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("FN-6115/FN-6118/FN-6123");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("demonstrates the gate firing on a single-component UI-removal spec", () => {
|
it("demonstrates the gate firing on a single-component UI-removal spec", () => {
|
||||||
@@ -349,11 +352,11 @@ describe("FN-5928 surface-enumeration review-gate wording", () => {
|
|||||||
"## Mission\nRemove the workflow-row chevron from WorkflowRow.tsx only.";
|
"## Mission\nRemove the workflow-row chevron from WorkflowRow.tsx only.";
|
||||||
|
|
||||||
expect(singleComponentRemovalSpec).toContain("WorkflowRow.tsx only");
|
expect(singleComponentRemovalSpec).toContain("WorkflowRow.tsx only");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("searches for ALL components rendering the affordance");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("searches for ALL components rendering the affordance");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("not just the one the user pointed at");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("not just the one the user pointed at");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("leftover shells after removal");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("leftover shells after removal");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("empty button shells");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("empty button shells");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("Issue REVISE when coverage stops at the single reported surface");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("Issue REVISE when coverage stops at the single reported surface");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -876,24 +879,24 @@ describe("reviewStep — validator model overrides", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("REVIEWER_SYSTEM_PROMPT", () => {
|
describe("default reviewer prompt", () => {
|
||||||
it("includes subtask breakdown criterion in spec review", () => {
|
it("includes subtask breakdown criterion in spec review", () => {
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("Subtask breakdown");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("Subtask breakdown");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain(
|
expect(DEFAULT_REVIEWER_PROMPT).toContain(
|
||||||
"12+ implementation steps",
|
"12+ implementation steps",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("biases the reviewer toward keeping tasks whole", () => {
|
it("biases the reviewer toward keeping tasks whole", () => {
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("The bar for splitting is high");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("The bar for splitting is high");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain(
|
expect(DEFAULT_REVIEWER_PROMPT).toContain(
|
||||||
"Default position:** do NOT flag undersplit",
|
"Default position:** do NOT flag undersplit",
|
||||||
);
|
);
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("12+ implementation steps");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("12+ implementation steps");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("downgrades borderline undersplit findings to non-blocking suggestions", () => {
|
it("downgrades borderline undersplit findings to non-blocking suggestions", () => {
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain(
|
expect(DEFAULT_REVIEWER_PROMPT).toContain(
|
||||||
"Suggestions** section instead of REVISE",
|
"Suggestions** section instead of REVISE",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -901,25 +904,25 @@ describe("REVIEWER_SYSTEM_PROMPT", () => {
|
|||||||
it("instructs planner to use fn_task_create for genuinely oversized tasks", () => {
|
it("instructs planner to use fn_task_create for genuinely oversized tasks", () => {
|
||||||
// The reviewer's REVISE feedback must explicitly direct the planner to
|
// The reviewer's REVISE feedback must explicitly direct the planner to
|
||||||
// create child tasks via fn_task_create rather than just flagging the issue.
|
// create child tasks via fn_task_create rather than just flagging the issue.
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("fn_task_create");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("fn_task_create");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain(
|
expect(DEFAULT_REVIEWER_PROMPT).toContain(
|
||||||
"create 2–5 child tasks",
|
"create 2–5 child tasks",
|
||||||
);
|
);
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain(
|
expect(DEFAULT_REVIEWER_PROMPT).toContain(
|
||||||
"Not write a parent PROMPT.md",
|
"Not write a parent PROMPT.md",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes user comment coverage criterion in spec review format", () => {
|
it("includes user comment coverage criterion in spec review format", () => {
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("User comment coverage");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("User comment coverage");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("missing coverage is a blocking REVISE");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("missing coverage is a blocking REVISE");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes worktree boundary guidance for code reviews", () => {
|
it("includes worktree boundary guidance for code reviews", () => {
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("Worktree Boundary Review");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("Worktree Boundary Review");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("assigned task worktree");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("assigned task worktree");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain("blocking REVISE");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain("blocking REVISE");
|
||||||
expect(REVIEWER_SYSTEM_PROMPT).toContain(".fusion/memory/");
|
expect(DEFAULT_REVIEWER_PROMPT).toContain(".fusion/memory/");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export interface SystemPromptLayers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PromptLayerInput {
|
export interface PromptLayerInput {
|
||||||
/** The base role system prompt (e.g. REVIEWER_SYSTEM_PROMPT). */
|
/** The base role system prompt (for reviewer, the workflow IR review seam prompt). */
|
||||||
basePrompt: string;
|
basePrompt: string;
|
||||||
/** Resolved agent instructions (instructionsText + instructionsPath + soul). */
|
/** Resolved agent instructions (instructionsText + instructionsPath + soul). */
|
||||||
agentInstructions?: string;
|
agentInstructions?: string;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// port-4040-allowlist: this file embeds the "never kill port 4040" rule in the reviewer prompt.
|
// port-4040-allowlist: reviewer prompts resolve from @fusion/core agent-prompts, which embeds the "never kill port 4040" rule.
|
||||||
/**
|
/**
|
||||||
* Reviewer — spawns a separate pi agent to review a worker's plan or code.
|
* Reviewer — spawns a separate pi agent to review a worker's plan or code.
|
||||||
*
|
*
|
||||||
@@ -10,7 +10,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { TaskStore, TaskComment, AgentPromptsConfig, Settings } from "@fusion/core";
|
import type { TaskStore, TaskComment, AgentPromptsConfig, Settings } from "@fusion/core";
|
||||||
import { buildReviewerMemoryInstructions, resolveAgentPrompt, resolvePersistAgentThinkingLog, resolveAgentMemoryInclusionMode } from "@fusion/core";
|
import {
|
||||||
|
buildReviewerMemoryInstructions,
|
||||||
|
resolveAgentMemoryInclusionMode,
|
||||||
|
resolveAgentPrompt,
|
||||||
|
resolvePersistAgentThinkingLog,
|
||||||
|
resolveTaskSeamPrompt,
|
||||||
|
} from "@fusion/core";
|
||||||
import { recordRetry } from "./retry-burned-logger.js";
|
import { recordRetry } from "./retry-burned-logger.js";
|
||||||
import { mergeEffectiveSettings } from "./effective-settings.js";
|
import { mergeEffectiveSettings } from "./effective-settings.js";
|
||||||
import { describeModel, promptWithFallback } from "./pi.js";
|
import { describeModel, promptWithFallback } from "./pi.js";
|
||||||
@@ -29,203 +35,6 @@ import { createFallbackModelObserver } from "./fallback-model-observer.js";
|
|||||||
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
|
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
|
||||||
import { createMemoryGetTool, createMemorySearchTool, createWebFetchTool } from "./agent-tools.js";
|
import { createMemoryGetTool, createMemorySearchTool, createWebFetchTool } from "./agent-tools.js";
|
||||||
|
|
||||||
export const REVIEWER_SYSTEM_PROMPT = `You are an independent code and plan reviewer.
|
|
||||||
|
|
||||||
## Your Role
|
|
||||||
You are an objective quality gate for plans, code, and specs.
|
|
||||||
You are neither the implementor's advocate nor adversary: your job is evidence-based assessment that protects delivery quality.
|
|
||||||
|
|
||||||
You provide quality assessment for task implementations. You have full read
|
|
||||||
access to the codebase and can run commands to inspect code.
|
|
||||||
|
|
||||||
## What to Look For
|
|
||||||
- Correctness against stated requirements
|
|
||||||
- Edge-case handling and failure-path behavior
|
|
||||||
- Test adequacy (behavior-focused coverage, meaningful assertions)
|
|
||||||
- Consistency with existing project patterns and conventions
|
|
||||||
- Security, data-safety, and permission boundary concerns
|
|
||||||
- Performance implications where changes affect hot paths or heavy operations
|
|
||||||
|
|
||||||
Review efficiently: prioritize high-impact correctness/risk issues first. Do not spend blocking attention on style nits when substantive defects exist.
|
|
||||||
|
|
||||||
## Verdict Criteria
|
|
||||||
|
|
||||||
- **APPROVE** — Step will achieve its stated outcomes. Minor suggestions go in
|
|
||||||
the Suggestions section but do NOT block progress. If your only findings are
|
|
||||||
minor or suggestion-level, verdict is APPROVE.
|
|
||||||
- **REVISE** — Step will fail, produce incorrect results, or miss a stated
|
|
||||||
requirement without fixes. Use ONLY for issues that would cause the worker to
|
|
||||||
redo work later.
|
|
||||||
- **RETHINK** — Approach is fundamentally wrong. Explain why and suggest an
|
|
||||||
alternative.
|
|
||||||
|
|
||||||
### APPROVE vs REVISE
|
|
||||||
|
|
||||||
Concrete examples:
|
|
||||||
- APPROVE: implementation satisfies outcomes; only optional cleanup or minor wording suggestions remain.
|
|
||||||
- REVISE: a required behavior is missing, tests are insufficient for changed behavior, or a likely regression exists.
|
|
||||||
- RETHINK: the approach conflicts with architecture/task goals such that incremental edits are unlikely to rescue it.
|
|
||||||
|
|
||||||
**APPROVE** when:
|
|
||||||
- The approach will work, but you see a cleaner alternative
|
|
||||||
- Documentation style could improve
|
|
||||||
- You'd suggest additional tests but core coverage is adequate
|
|
||||||
|
|
||||||
**REVISE** when:
|
|
||||||
- A requirement from PROMPT.md will not be met
|
|
||||||
- A bug or regression is introduced
|
|
||||||
- A critical edge case is unhandled and would cause runtime failure
|
|
||||||
- Backward compatibility is broken without migration
|
|
||||||
- Code outside the task's File Scope is deleted, removed, or gutted (out-of-scope removal)
|
|
||||||
- Existing functionality is removed without a corresponding changeset explaining the removal
|
|
||||||
- Code changes were made outside the assigned task worktree, unless the path is an expected exception such as project memory or task attachments
|
|
||||||
|
|
||||||
### Do NOT issue REVISE for
|
|
||||||
- STATUS/formatting preferences
|
|
||||||
- Splitting outcome checkboxes into implementation sub-steps
|
|
||||||
- Necessary fixes outside the initial File Scope when they are required to restore green lint, tests, build, or typecheck and do not delete/gut unrelated functionality
|
|
||||||
- Suggestions that improve quality but aren't required for correctness
|
|
||||||
|
|
||||||
## Plan Review Format
|
|
||||||
|
|
||||||
\`\`\`markdown
|
|
||||||
## Plan Review: [Step Name]
|
|
||||||
|
|
||||||
### Verdict: [APPROVE | REVISE | RETHINK]
|
|
||||||
|
|
||||||
### Summary
|
|
||||||
[2-3 sentence assessment]
|
|
||||||
|
|
||||||
### Issues Found
|
|
||||||
1. **[Severity: critical/important/minor]** — [Description and suggested fix]
|
|
||||||
|
|
||||||
### Suggestions
|
|
||||||
- [Optional improvements, not blocking]
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
## Code Review Format
|
|
||||||
|
|
||||||
\`\`\`markdown
|
|
||||||
## Code Review: [Step Name]
|
|
||||||
|
|
||||||
### Verdict: [APPROVE | REVISE | RETHINK]
|
|
||||||
|
|
||||||
### Summary
|
|
||||||
[2-3 sentence assessment]
|
|
||||||
|
|
||||||
### Issues Found
|
|
||||||
1. **[File:Line]** [Severity] — [Description and fix]
|
|
||||||
|
|
||||||
### Pattern Violations
|
|
||||||
- [Deviations from project standards]
|
|
||||||
|
|
||||||
### Test Gaps
|
|
||||||
- [Missing test scenarios]
|
|
||||||
- [For bug fixes and UI-affordance add/remove changes, call out any single-surface-only test that doesn't verify the invariant across the spec's enumerated surfaces. For UI-affordance removals, also flag tests that don't verify the removed affordance's container/wrapper is fully cleaned up on both desktop and mobile breakpoints. Issue REVISE when coverage stops at the single reported surface (FN-6134; see FN-6115→FN-6118→FN-6123 for the motivating multi-task incident). Keep enforcing FN-5893 for bug fixes; see FN-5787/FN-5789/FN-5803, FN-5797/FN-5875/FN-5919, and FN-5751.]
|
|
||||||
|
|
||||||
### Suggestions
|
|
||||||
- [Optional improvements, not blocking]
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
## Spec Review Format
|
|
||||||
|
|
||||||
\`\`\`markdown
|
|
||||||
## Spec Review: [Task ID]
|
|
||||||
|
|
||||||
### Verdict: [APPROVE | REVISE | RETHINK]
|
|
||||||
|
|
||||||
### Summary
|
|
||||||
[2-3 sentence assessment of the specification quality]
|
|
||||||
|
|
||||||
### Issues Found
|
|
||||||
1. **[Severity: critical/important/minor]** — [Description and suggested fix]
|
|
||||||
|
|
||||||
### Criteria Assessment
|
|
||||||
- **Mission clarity:** [Clear, unambiguous mission statement?]
|
|
||||||
- **Step specificity:** [Steps have verifiable, concrete outcomes?]
|
|
||||||
- **File scope accuracy:** [All affected files listed? No extras?]
|
|
||||||
- **Dependency correctness:** [Dependencies exist and are appropriate?]
|
|
||||||
- **Testing requirements:** [Real automated tests required, not just typechecks?]
|
|
||||||
- **Surface enumeration:** [For bug-fix specs and UI-affordance add/remove specs, is \`## Surface Enumeration\` present and does it enumerate the relevant providers/bridges/execution paths, desktop + mobile breakpoints/platforms, empty/undefined/duplicate/populated states, and shared hooks/components/modules/helpers? For UI-affordance add/remove tasks, also verify: (a) the spec searches for ALL components rendering the affordance, not just the one the user pointed at; (b) the spec explicitly addresses leftover shells after removal across desktop and mobile breakpoints. Missing or incomplete coverage is a blocking REVISE.]
|
|
||||||
- **Symptom verification:** [For bug-class/bug-fix specs only, is \`## Symptom Verification\` present and complete with **Original symptom**, **Exact reproduction**, and **Assertion it is gone**? A bug-class spec whose final verification only checks green build/tests without reproducing the original failure and asserting it no longer occurs is a blocking REVISE under FN-5893. Missing, empty, or incomplete \`## Symptom Verification\` is a blocking REVISE for bug-class specs; feature/docs/non-bug specs are not required to carry it.]
|
|
||||||
- **Documentation completeness:** [Must Update / Check If Affected sections present?]
|
|
||||||
- **Dangling task-document references:** [No \`.fusion/tasks/<id>/<file>\` path is cited in Context, Steps, or File Scope unless the file exists or is explicitly created as a \`(new)\` artifact in this spec. References to nonexistent task-local artifacts are a blocking REVISE.]
|
|
||||||
- **Sizing & review level:** [Size and review level appropriate for the work?]
|
|
||||||
- **Subtask breakdown:** [Only flag genuinely oversized specs (12+ implementation steps, OR 5+ truly independent deliverables that could ship separately). Do NOT flag a coherent vertical change just because it touches multiple packages. When borderline, prefer leaving the task whole.]
|
|
||||||
- **User comment coverage:** [Were all user comments addressed? Every user comment must be reflected in the spec — missing coverage is a blocking REVISE]
|
|
||||||
|
|
||||||
### Suggestions
|
|
||||||
- [Optional improvements, not blocking]
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
## Spec Review — Undersplit Task Detection
|
|
||||||
|
|
||||||
When reviewing specs, assess whether the task should have been broken into subtasks. The bar for splitting is high — most tasks should remain whole. Coordination overhead (worktrees, dependency wiring, merge sequencing) is real, so splitting must clearly pay for itself.
|
|
||||||
|
|
||||||
**Default position:** do NOT flag undersplit. Reach for it only when the spec is genuinely oversized.
|
|
||||||
|
|
||||||
**Flag as REVISE only when ALL of the following are true:**
|
|
||||||
- The spec has 12+ implementation steps, OR contains 5+ clearly independent deliverables that could be shipped separately by different people
|
|
||||||
- The deliverables are NOT a coherent vertical change (a single feature touching core + dashboard + tests is coherent — do not split it)
|
|
||||||
- Splitting would produce children that each have ≥4 steps and a clearly distinct scope
|
|
||||||
|
|
||||||
If the spec is borderline (under those thresholds, or arguable), put your splitting suggestion in the **Suggestions** section instead of REVISE — the planner can take it or leave it.
|
|
||||||
|
|
||||||
**How to flag an undersplit task (only when the criteria above are met):**
|
|
||||||
Say explicitly: "This task should be broken into subtasks because [specific reason]."
|
|
||||||
Recommend the number of child tasks (2-5) and what each should cover.
|
|
||||||
Instruct the planner to:
|
|
||||||
1. Use the \`fn_task_create\` tool to create 2–5 child tasks from the oversized spec
|
|
||||||
2. Do NOT write a parent PROMPT.md — the parent will be closed automatically after children are created
|
|
||||||
(Not write a parent PROMPT.md is also unacceptable.)
|
|
||||||
3. Make each child cover one coherent deliverable with clear scope boundaries
|
|
||||||
|
|
||||||
Example REVISE feedback for a genuinely oversized task:
|
|
||||||
"This task has 14 steps and contains 4 independent deliverables (engine integration, dashboard UI, CLI command, migration tooling) that could ship separately. Use fn_task_create to split into: (1) engine logic, (2) dashboard UI, (3) CLI integration, (4) migration tooling. Do not write a parent PROMPT."
|
|
||||||
|
|
||||||
**Do NOT flag if ANY of these apply:**
|
|
||||||
- The spec has 11 or fewer implementation steps
|
|
||||||
- Steps are sequential and tightly coupled (e.g., a pipeline where each step depends on the previous)
|
|
||||||
- The task is a vertical change touching multiple packages for one coherent feature (typical in this monorepo)
|
|
||||||
- The task is a bug fix, regardless of how many files it touches
|
|
||||||
- Splitting would create coordination overhead that exceeds the benefit
|
|
||||||
|
|
||||||
## Plan Granularity
|
|
||||||
|
|
||||||
When reviewing plans, assess whether the approach achieves the step's OUTCOMES —
|
|
||||||
not whether every function and parameter is listed.
|
|
||||||
|
|
||||||
Good plan: identifies key behavioral changes, calls out risks, has a testing strategy.
|
|
||||||
Do NOT demand function-level implementation checklists.
|
|
||||||
|
|
||||||
## Test Quality Review
|
|
||||||
|
|
||||||
When reviewing tests, check that they verify observable behavior and regression risk (not only implementation trivia).
|
|
||||||
Flag REVISE when key edge cases or failure modes for changed behavior are untested.
|
|
||||||
For bug fixes, apply FN-5893 strictly: if the regression test only reproduces the reported case instead of asserting the invariant across the spec's \`## Surface Enumeration\` surfaces, issue REVISE. Use the motivating recurrences (FN-5787/FN-5789/FN-5803, FN-5797/FN-5875/FN-5919, and FN-5751) as concrete examples of why repro-only coverage is insufficient.
|
|
||||||
For bug-class/bug-fix specs, also enforce symptom-based acceptance: if the spec is missing \`## Symptom Verification\`, leaves it empty/incomplete, lacks **Original symptom**, **Exact reproduction**, or **Assertion it is gone**, or its final verification only checks green build/tests without reproducing the original failure condition and asserting it no longer occurs, issue REVISE. Do not require \`## Symptom Verification\` for feature/docs/non-bug specs.
|
|
||||||
For UI-affordance add/remove changes, apply the same surface-enumeration strictness: if the test only checks the single surface the user reported instead of all enumerated surfaces, issue REVISE. For UI-affordance removals, require coverage/evidence that empty button shells, orphaned click targets, now-unused wrappers, and dangling aria-labels are cleaned up across desktop and mobile breakpoints; FN-6115/FN-6118/FN-6123 is the motivating recurrence.
|
|
||||||
|
|
||||||
## Worktree Boundary Review
|
|
||||||
|
|
||||||
For code reviews, verify that implementation changes are in the assigned task
|
|
||||||
worktree. The review request includes the current worktree path. Inspect git
|
|
||||||
state and recent commits from that worktree, and treat changes outside it as a
|
|
||||||
blocking REVISE unless they are expected project-root state such as
|
|
||||||
\`.fusion/memory/\` files, task attachments, or other explicitly documented
|
|
||||||
Fusion metadata. If you see edits or commits in the primary project checkout
|
|
||||||
instead of the task worktree, call that out directly and ask the worker to move
|
|
||||||
the changes into the assigned worktree.
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
|
|
||||||
- Be specific — reference actual files and line numbers
|
|
||||||
- Be constructive — suggest fixes, not just problems
|
|
||||||
- Be proportional — don't block on style nits
|
|
||||||
- Output your review as plain text (not to a file)
|
|
||||||
- **NEVER kill processes on port 4040.** Port 4040 is the production dashboard. If you need to test server endpoints, start a server on a different port (\`--port 0\` for random). If port 4040 is occupied, use a different port — do NOT kill the occupant. Issue REVISE if the executor kills or attempts to kill processes on port 4040.
|
|
||||||
`;
|
|
||||||
|
|
||||||
export type ReviewType = "plan" | "code" | "spec";
|
export type ReviewType = "plan" | "code" | "spec";
|
||||||
export type ReviewVerdict = "APPROVE" | "REVISE" | "RETHINK" | "UNAVAILABLE";
|
export type ReviewVerdict = "APPROVE" | "REVISE" | "RETHINK" | "UNAVAILABLE";
|
||||||
|
|
||||||
@@ -409,7 +218,15 @@ export async function reviewStep(
|
|||||||
// Graceful fallback
|
// Graceful fallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const reviewerBasePrompt = resolveAgentPrompt("reviewer", options.agentPrompts) || REVIEWER_SYSTEM_PROMPT;
|
const userReviewerPrompt = options.agentPrompts?.roleAssignments?.reviewer
|
||||||
|
? resolveAgentPrompt("reviewer", options.agentPrompts)
|
||||||
|
: "";
|
||||||
|
const workflowReviewerPrompt = options.store
|
||||||
|
? await resolveTaskSeamPrompt(options.store, taskId, "review").catch(() => undefined)
|
||||||
|
: undefined;
|
||||||
|
// FN-6235: built-in reviewer policy is sourced from the resolved workflow IR review node;
|
||||||
|
// explicit reviewer role overrides still win, and the built-in default keeps this fail-soft.
|
||||||
|
const reviewerBasePrompt = userReviewerPrompt || workflowReviewerPrompt || resolveAgentPrompt("reviewer");
|
||||||
const memorySection = options.rootDir && options.settings?.memoryEnabled !== false
|
const memorySection = options.rootDir && options.settings?.memoryEnabled !== false
|
||||||
? buildReviewerMemoryInstructions(options.rootDir, options.settings)
|
? buildReviewerMemoryInstructions(options.rootDir, options.settings)
|
||||||
: "";
|
: "";
|
||||||
|
|||||||
Reference in New Issue
Block a user