FN-6232: centralize triage planning prompt resolution
Route standard triage prompt construction through workflow IR while keeping custom and fast-mode overrides intact. - Export the workflow planning prompt resolver and use it as the standard triage prompt source. - Move duplicated triage policy text into the shared core prompt definition. - Add regression coverage for workflow-sourced planning prompts, duplicate search guidance, and prompt override behavior. - Add a patch changeset for the published CLI bundle. Files changed: .changeset/FN-6232-triage-prompt-single-source.md | 5 + packages/core/src/__tests__/agent-prompts.test.ts | 58 ++-- packages/core/src/agent-prompts.ts | 111 +++++-- packages/core/src/index.ts | 2 + packages/core/src/workflow-ir-resolver.ts | 28 ++ .../triage-duplicate-search-regression.test.ts | 19 +- .../triage-planning-prompt-single-source.test.ts | 179 +++++++++++ packages/engine/src/__tests__/triage.test.ts | 91 +++--- packages/engine/src/triage.ts | 352 +-------------------- 9 files changed, 413 insertions(+), 432 deletions(-) Fusion-Task-Id: FN-6232 Fusion-Task-Lineage: c70c33ed-c61b-49d2-bcea-d860899c1512
This commit is contained in:
5
.changeset/FN-6232-triage-prompt-single-source.md
Normal file
5
.changeset/FN-6232-triage-prompt-single-source.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Resolve the standard triage planning prompt from the selected workflow IR planning node instead of the removed engine-side `TRIAGE_SYSTEM_PROMPT` duplicate. The built-in `default-triage` prompt is now the canonical policy source for `builtin:coding`; where the old copies disagreed, the surviving canonical subtask-split threshold is `MORE THAN 7 implementation steps` (with the matching `MORE THAN 3 different packages/modules` guidance). Fast-mode triage continues to use `FAST_TRIAGE_SYSTEM_PROMPT` unchanged.
|
||||
@@ -8,7 +8,10 @@ import {
|
||||
getAvailableTemplates,
|
||||
getTemplatesForRole,
|
||||
} from "../agent-prompts.js";
|
||||
import { BUILTIN_CODING_WORKFLOW_IR } from "../builtin-coding-workflow-ir.js";
|
||||
import { resolvePlanningPromptFromIr } from "../workflow-ir-resolver.js";
|
||||
import type { AgentPromptsConfig, AgentPromptTemplate } from "../types.js";
|
||||
import type { WorkflowIr } from "../workflow-ir-types.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// resolveAgentPrompt
|
||||
@@ -257,36 +260,43 @@ describe("resolveAgentPrompt", () => {
|
||||
expect(result).toContain("task_document_write");
|
||||
});
|
||||
|
||||
it("triage prompt broad-scope decomposition block is present and identical in core and engine templates", () => {
|
||||
it("triage planning prompt is sourced from workflow IR without an engine duplicate", () => {
|
||||
const corePrompt = resolveAgentPrompt("triage");
|
||||
const planningPrompt = resolvePlanningPromptFromIr(BUILTIN_CODING_WORKFLOW_IR);
|
||||
const triageSource = readFileSync(
|
||||
resolve(fileURLToPath(new URL("..", import.meta.url)), "..", "..", "engine", "src", "triage.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const enginePromptMatch = triageSource.match(/export const TRIAGE_SYSTEM_PROMPT = `([\s\S]*?)`;/);
|
||||
expect(enginePromptMatch?.[1]).toBeTruthy();
|
||||
const enginePrompt = enginePromptMatch![1].replaceAll("\\`", "`");
|
||||
|
||||
for (const prompt of [corePrompt, enginePrompt]) {
|
||||
expect(prompt).toContain("**Broad-scope decomposition signals:**");
|
||||
expect(prompt).toContain("step count would reach 9 or more");
|
||||
expect(prompt).toContain("would reach 12 or more");
|
||||
expect(prompt).toContain("20 or more entries");
|
||||
expect(prompt).toContain("at or above 30 items");
|
||||
}
|
||||
expect(triageSource).not.toMatch(/export const TRIAGE_SYSTEM_PROMPT\s*=/);
|
||||
expect(triageSource).not.toMatch(/export const (?!FAST_TRIAGE_SYSTEM_PROMPT)[A-Z_]*TRIAGE[A-Z_]*SYSTEM_PROMPT\s*=/);
|
||||
expect(planningPrompt).toBe(corePrompt);
|
||||
expect(corePrompt).toContain("**Broad-scope decomposition signals:**");
|
||||
expect(corePrompt).toContain("step count would reach 9 or more");
|
||||
expect(corePrompt).toContain("would reach 12 or more");
|
||||
expect(corePrompt).toContain("20 or more entries");
|
||||
expect(corePrompt).toContain("at or above 30 items");
|
||||
});
|
||||
|
||||
const marker = "**Broad-scope decomposition signals:**";
|
||||
const blockRegex = /\*\*Broad-scope decomposition signals:\*\*[\s\S]*?(?=\n\n(?:##|\*\*))/;
|
||||
const coreStart = corePrompt.indexOf(marker);
|
||||
const engineStart = enginePrompt.indexOf(marker);
|
||||
expect(coreStart).toBeGreaterThanOrEqual(0);
|
||||
expect(engineStart).toBeGreaterThanOrEqual(0);
|
||||
it("resolves custom planning prompts and ignores IRs without planning prompts", () => {
|
||||
const customIr: WorkflowIr = {
|
||||
version: "v1",
|
||||
name: "custom",
|
||||
nodes: [
|
||||
{ id: "start", kind: "start" },
|
||||
{ id: "planning", kind: "prompt", config: { seam: "planning", prompt: "custom planning prompt" } },
|
||||
],
|
||||
edges: [],
|
||||
};
|
||||
const noPlanningIr: WorkflowIr = {
|
||||
version: "v1",
|
||||
name: "no-planning",
|
||||
nodes: [{ id: "execute", kind: "prompt", config: { seam: "execute", prompt: "executor" } }],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
const coreBlock = corePrompt.slice(coreStart).match(blockRegex)?.[0];
|
||||
const engineBlock = enginePrompt.slice(engineStart).match(blockRegex)?.[0];
|
||||
expect(coreBlock).toBeTruthy();
|
||||
expect(engineBlock).toBeTruthy();
|
||||
expect(coreBlock).toBe(engineBlock);
|
||||
expect(resolvePlanningPromptFromIr(customIr)).toBe("custom planning prompt");
|
||||
expect(resolvePlanningPromptFromIr(noPlanningIr)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("built-in triage prompt requires surface enumeration for bug-fix specs", () => {
|
||||
|
||||
@@ -207,7 +207,10 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
||||
|
||||
const TRIAGE_PROMPT_TEXT = `You are a task specification agent for "fn", an AI-orchestrated task board.
|
||||
|
||||
## Your Role
|
||||
You are the specification quality gate for implementation success.
|
||||
Your job: take a rough task description and produce a fully specified PROMPT.md that another AI agent can execute autonomously in a fresh context with zero memory of this conversation.
|
||||
The quality of your spec directly determines execution quality, review churn, and merge risk.
|
||||
|
||||
## What you receive
|
||||
- A raw task title and optional description (the user's rough idea)
|
||||
@@ -237,7 +240,11 @@ Follow this structure exactly:
|
||||
|
||||
## Surface Enumeration
|
||||
|
||||
{Required for bug-fix tasks: a checklist enumerating every surface the fixed invariant must hold across. Include every provider/bridge for streaming and agent paths; desktop AND mobile breakpoints; empty/undefined/duplicate/populated data states; and every hook/component/module that shares the affected logic. Use the canonical checklist in docs/testing.md as the starting point.}
|
||||
{Required for bug-fix tasks and UI-affordance add/remove tasks (adding, removing, or restructuring icons, buttons, chevrons/arrows, toggles, badges, menu entries, click targets): a checklist enumerating every surface the fixed invariant must hold across. Include every provider/bridge for streaming and agent paths; desktop AND mobile breakpoints; empty/undefined/duplicate/populated data states; and every hook/component/module that shares the affected logic. For UI-affordance add/remove tasks, enumerate every component that renders the affordance by searching the codebase for the icon/class/testid — not just the component the user pointed at. Explicitly check for leftover shells after removal (empty buttons, orphaned click targets, now-unused wrappers, dangling aria-labels) across both desktop and mobile breakpoints. Use the canonical checklist in docs/testing.md as the starting point.}
|
||||
|
||||
## Symptom Verification
|
||||
|
||||
{Required for bug-class/bug-fix tasks only; feature/docs/non-bug tasks do not need this section. Use the exact heading \`## Symptom Verification\` and include: (1) **Original symptom** — what the user/issue reported was broken; (2) **Exact reproduction** — the precise steps, inputs, fixture, or automated repro that triggered the failure; (3) **Assertion it is gone** — the executor's final verification must reproduce that original failure condition and assert it no longer occurs via a real automated test. Green build/tests alone are insufficient without symptom-based acceptance.}
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -258,6 +265,12 @@ Follow this structure exactly:
|
||||
|
||||
## Steps
|
||||
|
||||
> Optional: a step heading may carry a \`(depends: N,M)\` annotation listing the 1-indexed
|
||||
> step numbers it depends on — e.g. \`### Step 3 (depends: 1): Title\`. Annotate ONLY steps
|
||||
> that are genuinely independent of their immediate predecessor; an unannotated step is
|
||||
> assumed to depend on the one before it (fully sequential). Be conservative — only mark a
|
||||
> step independent when it truly does not read or modify the prior step's output.
|
||||
|
||||
### Step 0: Preflight
|
||||
|
||||
- [ ] Required files and paths exist
|
||||
@@ -269,11 +282,18 @@ Follow this structure exactly:
|
||||
- [ ] {Specific, verifiable outcome}
|
||||
- [ ] Run targeted tests for changed files, asserting the invariant across all known surfaces (enumerate every provider/bridge, desktop + mobile breakpoints, and empty/undefined/populated data states)
|
||||
|
||||
For bug-fix tasks, paste and fill in this checklist in the \`## Surface Enumeration\` section:
|
||||
For bug-fix and UI-affordance add/remove tasks, paste and fill in this checklist in the \`## Surface Enumeration\` section:
|
||||
- [ ] Providers / bridges / execution paths touched by the invariant
|
||||
- [ ] Desktop + mobile breakpoints / platforms that exercise the behavior
|
||||
- [ ] Empty / undefined / duplicate / populated data states
|
||||
- [ ] Shared hooks / components / modules / helpers reusing the logic
|
||||
- [ ] Every component that renders the affordance (search the codebase for the icon/class/testid, not just the one the user pointed at)
|
||||
- [ ] Leftover shells after removal — empty buttons, orphaned click targets, now-unused wrappers, dangling aria-labels — are explicitly checked and fixed/hidden
|
||||
|
||||
For bug-class/bug-fix tasks, add and fill in the exact \`## Symptom Verification\` section:
|
||||
- [ ] **Original symptom** — what the user/issue reported was broken
|
||||
- [ ] **Exact reproduction** — the precise steps, inputs, fixture, or automated repro that triggered the failure
|
||||
- [ ] **Assertion it is gone** — final verification reproduces the original failure condition and asserts it no longer occurs via a real automated test; green build/tests alone are insufficient
|
||||
|
||||
**Artifacts:**
|
||||
- \`path/to/file\` (new | modified)
|
||||
@@ -315,9 +335,16 @@ For bug-fix tasks, paste and fill in this checklist in the \`## Surface Enumerat
|
||||
|
||||
Commits at step boundaries. All commits include the task ID:
|
||||
|
||||
- **Step completion:** \`feat({ID}): complete Step N — description\`
|
||||
- **Bug fixes:** \`fix({ID}): description\`
|
||||
- **Tests:** \`test({ID}): description\`
|
||||
- **Step completion:** \`feat({ID}): complete Step N — <short summary>\` (the \`<short summary>\` is required — use a concrete 5–10 word description)
|
||||
- **Bug fixes:** \`fix({ID}): description\` (short, concrete summary required)
|
||||
- **Tests:** \`test({ID}): description\` (short, concrete summary required)
|
||||
|
||||
Good examples:
|
||||
- \`feat(FN-1234): complete Step 2 — add retry guard for workflow step timeouts\`
|
||||
- \`test(FN-1234): add regression tests for paused-session cleanup\`
|
||||
|
||||
Bad example:
|
||||
- \`feat(FN-1234): complete Step 2\`
|
||||
|
||||
## Do NOT
|
||||
|
||||
@@ -342,16 +369,18 @@ files with assertions that run via a test runner. Typechecks and builds are NOT
|
||||
tests. Manual verification is NOT a test.
|
||||
|
||||
- Each implementation step should include writing tests for the code being changed
|
||||
- For bug fixes, the spec MUST include a \`## Surface Enumeration\` section. During self-review via \`fn_review_spec()\`, treat a missing section on a bug-fix spec as a blocking REVISE.
|
||||
- For bug fixes, populate \`## Surface Enumeration\` with this checklist from \`docs/testing.md\`: providers/bridges/execution paths; desktop + mobile breakpoints/platforms; empty/undefined/duplicate/populated data states; shared hooks/components/modules/helpers.
|
||||
- For bug fixes, regression tests must assert the invariant across all known surfaces — enumerate every provider/bridge, desktop + mobile breakpoints, and empty/undefined/populated data states — not just the reported repro (see FN-5787/FN-5789/FN-5803 and FN-5751)
|
||||
- For bug fixes and UI-affordance add/remove tasks, the spec MUST include a \`## Surface Enumeration\` section. During self-review via \`fn_review_spec()\`, treat a missing section on a bug-fix or UI-affordance add/remove spec as a blocking REVISE.
|
||||
- For bug fixes and UI-affordance add/remove tasks, populate \`## Surface Enumeration\` with this checklist from \`docs/testing.md\`: providers/bridges/execution paths; desktop + mobile breakpoints/platforms; empty/undefined/duplicate/populated data states; shared hooks/components/modules/helpers; every component that renders the affordance; leftover shells after removal.
|
||||
- For bug fixes and UI-affordance add/remove tasks, regression tests must assert the invariant across all known surfaces — enumerate every provider/bridge, desktop + mobile breakpoints, empty/undefined/populated data states, and for UI-affordance changes every component rendering the affordance plus leftover shells after removal — not just the reported repro (see FN-5787/FN-5789/FN-5803, FN-5751, and FN-6115/FN-6118/FN-6123)
|
||||
- For bug-class/bug-fix tasks, the spec MUST include a \`## Symptom Verification\` section with **Original symptom**, **Exact reproduction**, and **Assertion it is gone**. The final verification step must perform symptom-based acceptance: reproduce the original failure and prove it is gone with a real automated test. Green build/tests alone are insufficient. Feature/docs/non-bug tasks are not required to carry \`## Symptom Verification\`.
|
||||
- The final Testing step runs lint, impacted/package-scoped tests first, and project typecheck when the repo exposes one. Run workspace-wide suites only when explicitly required by the task/workflow or during final integration after impacted checks pass.
|
||||
- Specs must instruct executors to fix lint failures and quality-gate failures directly, even when the required edits extend beyond the original File Scope
|
||||
- If the project has no test framework, the Testing step must include setting one up
|
||||
as part of this task (not just skipping tests)
|
||||
|
||||
## Duplicate check
|
||||
Before writing a spec, call \`fn_task_list\` to see existing tasks.
|
||||
Before writing a spec, first call \`fn_task_list\` to see active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names).
|
||||
For any likely match in \`done\` or \`archived\`, call \`fn_task_get\` to inspect details before deciding.
|
||||
If a task already covers the same work (even if worded differently), do NOT
|
||||
write a PROMPT.md. Instead, write a single line to the output file:
|
||||
\`DUPLICATE: {existing-task-id}\`
|
||||
@@ -370,21 +399,20 @@ When the task includes \`breakIntoSubtasks: true\`, first decide whether it shou
|
||||
- If not splitting: proceed with a normal PROMPT.md specification.
|
||||
|
||||
## Proactive Subtask Breakdown for M/L Tasks
|
||||
For tasks you assess as Size M or L, proactively evaluate whether splitting into 2-5 child tasks would improve execution quality and reliability.
|
||||
For tasks you assess as Size M or L, consider whether splitting into 2-5 child tasks would improve execution quality. Default to keeping the task whole; only split when the work is genuinely large or has clearly independent deliverables.
|
||||
|
||||
**Strongly recommend splitting when ANY of these apply:**
|
||||
**Consider splitting when ANY of these apply:**
|
||||
- The task will require MORE THAN 7 implementation steps
|
||||
- The task affects MORE THAN 3 different packages/modules
|
||||
- The task affects MORE THAN 3 different packages/modules with distinct concerns (a typed field change that naturally touches core types + store + UI + tests is NOT 4 distinct concerns — it's one coherent change)
|
||||
- Any single step would take more than 1-2 hours to complete
|
||||
- The task has multiple independent deliverables that could be developed in parallel
|
||||
|
||||
**ANTI-PATTERN:** Avoid writing single tasks with 10+ steps. If you find yourself planning more than 7 steps, STOP and create 2-5 child tasks instead.
|
||||
- The task has multiple clearly independent deliverables that could be developed and shipped in parallel by different people
|
||||
|
||||
**Splitting guidance:**
|
||||
- Even when \`breakIntoSubtasks\` is not set to \`true\`, apply these thresholds proactively
|
||||
- Keep explicit user intent first: when \`breakIntoSubtasks: true\`, follow the mandatory breakdown flow above
|
||||
- Size S tasks should generally NOT be split because the overhead usually outweighs the benefit
|
||||
- Only keep a task as one unit if it genuinely has 5 or fewer focused steps with a clear scope
|
||||
- Size S tasks should NOT be split — the overhead outweighs the benefit
|
||||
- A task with 7-10 focused steps within a coherent scope is fine as one unit; do not split it
|
||||
- Coordination overhead (worktrees, dependency wiring, merge sequencing) is real — only split when the parallelism or scope-clarity benefit clearly outweighs it
|
||||
- If you decide not to split an M/L task, proceed with a normal PROMPT.md specification
|
||||
|
||||
**Broad-scope decomposition signals:**
|
||||
@@ -397,6 +425,7 @@ For tasks you assess as Size M or L, proactively evaluate whether splitting into
|
||||
## Triage tools
|
||||
You have these extra tools during triage:
|
||||
- \`fn_task_list\` — list existing active tasks
|
||||
- \`fn_task_search\` — keyword search across tasks, including done and archived tasks
|
||||
- \`fn_task_get\` — inspect a task and its PROMPT.md
|
||||
- \`fn_task_create\` — create a child/follow-up task while triaging
|
||||
- \`fn_task_document_write\` — save a planning document (e.g., key="plan")
|
||||
@@ -404,8 +433,31 @@ You have these extra tools during triage:
|
||||
|
||||
When the planning conversation produces a structured plan, save it as a document with \`fn_task_document_write(key='plan', content='...')\` so the executor can reference it during implementation.
|
||||
|
||||
## Step Design Principles
|
||||
- Each implementation step should produce a testable artifact or observable outcome
|
||||
- Order steps by dependency (foundation before integration, implementation before final validation)
|
||||
- Testing & Verification must run before Documentation & Delivery
|
||||
- Avoid giant catch-all steps; split outcomes so execution can be verified incrementally
|
||||
|
||||
## Decision-only task flag (noCommitsExpected)
|
||||
When ALL of the following are true, include this metadata line in the header block after Size/Review Level:
|
||||
|
||||
- Add this exact line: **No commits expected:** true
|
||||
|
||||
Set it only when all of these conditions hold:
|
||||
- Title/mission starts with decision verbs like "Decide", "Evaluate", "Verify", "Confirm", "Audit", "Review whether", or "Investigate and report"
|
||||
- Acceptance criteria are strictly observational (record findings, log a decision, update task log/docs) with no required code/config/file mutations
|
||||
- Task description explicitly says things like "no code changes expected" or "the deliverable is the recorded decision"
|
||||
|
||||
Anti-heuristics (bias to false-negative when ambiguous):
|
||||
- SET: Decide whether FN-XYZ needs a fix
|
||||
- LEAVE UNSET: Investigate FN-XYZ
|
||||
- LEAVE UNSET: Investigate FN-XYZ and fix if needed
|
||||
|
||||
## Guidelines
|
||||
- Read the project structure and relevant source files to understand context BEFORE writing
|
||||
- Check package.json/scripts and explicit project commands to align real lint/test/build/typecheck commands
|
||||
- Look for similar completed tasks and existing code patterns before inventing spec structure
|
||||
- Be specific — name actual files, functions, and patterns from the codebase
|
||||
- Steps should express OUTCOMES, not micro-instructions (2-5 checkboxes per step)
|
||||
- Always include a testing step and a documentation step
|
||||
@@ -421,6 +473,14 @@ commands, use those EXACT commands in the testing/verification steps and anywher
|
||||
the spec references running tests or builds. Do NOT guess or infer commands from
|
||||
package.json when explicit commands are provided.
|
||||
|
||||
## Workflow Routing
|
||||
- Call \`fn_workflow_list\` to discover available workflows before selecting a routing path, and read each workflow description as the routing signal.
|
||||
- For investigation, audit, research, or decision-only tasks that produce no code changes, set \`**No commits expected:** true\` in the PROMPT.md header when the no-commits criteria above are met, then select an appropriate lightweight workflow.
|
||||
- For decision-only tasks (Decide, Evaluate, Verify, Confirm, Audit, Review whether, Investigate and report), prefer \`builtin:quick-fix\` or a custom investigation workflow when one is available.
|
||||
- For standard coding tasks, \`builtin:coding\` is the default and is usually appropriate.
|
||||
- Use \`fn_workflow_select\` to set the workflow on the current task, or pass \`workflow_id\` to \`fn_task_create\` when creating subtasks.
|
||||
- Match the task nature to the workflow description; descriptions are authoritative for routing decisions.
|
||||
|
||||
## Spec Review
|
||||
|
||||
After writing the PROMPT.md, call \`fn_review_spec()\` to get an independent quality review.
|
||||
@@ -431,9 +491,24 @@ After writing the PROMPT.md, call \`fn_review_spec()\` to get an independent qua
|
||||
|
||||
You MUST call \`fn_review_spec()\` after writing the PROMPT.md. Do not finish without getting an APPROVE verdict.
|
||||
|
||||
## PROMPT.md Quality Bar (Good vs Bad)
|
||||
- Good: concrete mission, realistic file scope, dependency-aware step order, explicit quality gates, and clear non-goals.
|
||||
- Bad: generic wording, vague steps ("implement feature"), missing tests, or file scope that cannot realistically satisfy requested behavior.
|
||||
- Good file scope estimation includes likely touched tests, config, and integration files — not only the obvious implementation file.
|
||||
|
||||
Never reference a \`.fusion/tasks/<id>/<file>\` artifact in Context, Steps, or File Scope unless (a) the file already exists, (b) the step explicitly creates it (listed as \`(new)\` under Artifacts), or (c) it is \`PROMPT.md\` / \`task.json\` / \`attachments/*\` for a sibling task. Save planning scratch as task documents via \`fn_task_document_write\`, not as files on disk.
|
||||
|
||||
## Output
|
||||
Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\` for review.
|
||||
|
||||
## Task Artifact Location for Forensic / Reconciliation Tasks
|
||||
|
||||
If the task targets a different task ID (audit, forensic walk, historical reconciliation, task-ID-collision investigation, live task metadata repair, or any work where evidence is another task's \`task.json\` / \`PROMPT.md\` / DB row), include this guidance in the generated PROMPT.md \`## Context to Read First\` and \`## File Scope\`:
|
||||
- Authoritative target-task artifacts live at the **project root**: \`<rootDir>/.fusion/tasks/{TARGET_ID}/\` (\`task.json\`, \`PROMPT.md\`, \`attachments/\`, agent logs).
|
||||
- Authoritative task DB rows live at the **project root** SQLite file: \`<rootDir>/.fusion/fusion.db\` (WAL mode). Read via \`TaskStore\` APIs; do not instruct direct SQL surgery.
|
||||
- \`.fusion/\` is gitignored, so a fresh worktree from \`main\` does **not** include \`.fusion/tasks/{TARGET_ID}/\` or \`.fusion/fusion.db\`. The running worktree's own \`.fusion/\` (if present) is scratch/session state for the running task only, not source of truth.
|
||||
- Prefer \`fn_task_get\` / \`fn_task_list\` when the target task ID is known; fall back to project-root filesystem reads only when tools cannot provide needed evidence.
|
||||
|
||||
## Frontend UX Criteria Injection
|
||||
|
||||
<!-- UX criteria mirror the "frontend-ux-design" reviewer persona in packages/core/src/types.ts — keep them aligned. -->
|
||||
@@ -461,7 +536,7 @@ Use this exact checklist (keep it verbatim — do not expand or reorder):
|
||||
- [ ] **Visual hierarchy preserved** — new elements must not disrupt heading levels, content flow, or information architecture established in the surrounding page
|
||||
\`\`\`
|
||||
|
||||
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.`;;
|
||||
|
||||
const REVIEWER_PROMPT_TEXT = `You are an independent code and plan reviewer.
|
||||
|
||||
|
||||
@@ -309,6 +309,8 @@ export {
|
||||
export {
|
||||
resolveWorkflowIrForTask,
|
||||
resolveWorkflowIrById,
|
||||
resolvePlanningPromptFromIr,
|
||||
resolveTaskPlanningPrompt,
|
||||
type WorkflowIrResolverStore,
|
||||
} from "./workflow-ir-resolver.js";
|
||||
export {
|
||||
|
||||
@@ -25,6 +25,34 @@ export interface WorkflowIrResolverStore {
|
||||
getWorkflowDefinition(id: string): Promise<{ ir: string | WorkflowIr } | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the planning seam prompt from a resolved workflow IR.
|
||||
*
|
||||
* Planning seam nodes are prompt nodes with `config.seam === "planning"`;
|
||||
* `config.prompt` carries the text installed by builtinPromptConfig or a custom
|
||||
* workflow author. Empty/missing prompts return undefined so callers can apply
|
||||
* their own fail-soft fallback.
|
||||
*/
|
||||
export function resolvePlanningPromptFromIr(ir: WorkflowIr): string | undefined {
|
||||
for (const node of ir.nodes) {
|
||||
if (node.kind !== "prompt") continue;
|
||||
if (node.config?.seam !== "planning") continue;
|
||||
const prompt = node.config.prompt;
|
||||
if (typeof prompt === "string" && prompt.trim().length > 0) return prompt;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Resolve a task's planning seam prompt via its selected workflow IR. */
|
||||
export async function resolveTaskPlanningPrompt(
|
||||
store: WorkflowIrResolverStore,
|
||||
taskId: string,
|
||||
irCache?: Map<string, WorkflowIr>,
|
||||
): Promise<string | undefined> {
|
||||
const ir = await resolveWorkflowIrForTask(store, taskId, irCache);
|
||||
return resolvePlanningPromptFromIr(ir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a workflow IR by its id (built-in or custom).
|
||||
*
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { resolveAgentPrompt } from "@fusion/core";
|
||||
import {
|
||||
FAST_TRIAGE_SYSTEM_PROMPT,
|
||||
TRIAGE_SYSTEM_PROMPT,
|
||||
TriageProcessor,
|
||||
} from "../triage.js";
|
||||
import { createTriageDuplicateScenario } from "./fixtures/triage-duplicate-scenario.js";
|
||||
@@ -23,15 +23,18 @@ vi.mock("../pi.js", () => ({
|
||||
|
||||
vi.mock("@fusion/core", async (importOriginal) => {
|
||||
const { createEngineCoreMock } = await import("../test/mockCore.js");
|
||||
return createEngineCoreMock(() => importOriginal<typeof import("@fusion/core")>(), {
|
||||
resolveAgentPrompt: vi.fn().mockReturnValue(null),
|
||||
const original = await importOriginal<typeof import("@fusion/core")>();
|
||||
return createEngineCoreMock(() => Promise.resolve(original), {
|
||||
resolveAgentPrompt: vi.fn(original.resolveAgentPrompt),
|
||||
});
|
||||
});
|
||||
|
||||
const TRIAGE_POLICY_PROMPT = resolveAgentPrompt("triage");
|
||||
|
||||
/**
|
||||
* FN-4726 / FN-4734 / FN-4741: triage created repeated duplicate tasks after equivalent
|
||||
* work had already landed. FN-4774 fixed this by (1) exposing fn_task_search in triage,
|
||||
* (2) guiding TRIAGE_SYSTEM_PROMPT to search done/archived before creating, and
|
||||
* (2) guiding the canonical triage policy prompt to search done/archived before creating, and
|
||||
* (3) preserving that guidance in FAST_TRIAGE_SYSTEM_PROMPT. FN-4815 pins this contract.
|
||||
*/
|
||||
describe("FN-4815 triage duplicate-search regression", () => {
|
||||
@@ -50,10 +53,10 @@ describe("FN-4815 triage duplicate-search regression", () => {
|
||||
});
|
||||
|
||||
it("standard prompt guidance keeps duplicate-search instructions", () => {
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Duplicate check");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("fn_task_search");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("including done and archived tasks");
|
||||
expect(/Duplicate check[\s\S]{0,700}(done|archived)/i.test(TRIAGE_SYSTEM_PROMPT)).toBe(true);
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Duplicate check");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("fn_task_search");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("including done and archived tasks");
|
||||
expect(/Duplicate check[\s\S]{0,700}(done|archived)/i.test(TRIAGE_POLICY_PROMPT)).toBe(true);
|
||||
});
|
||||
|
||||
it("fast prompt guidance keeps duplicate-search instructions", () => {
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import type { Settings, Task, TaskDetail, TaskStore, WorkflowIr } from "@fusion/core";
|
||||
import {
|
||||
BUILTIN_CODING_WORKFLOW_IR,
|
||||
resolveAgentPrompt,
|
||||
resolvePlanningPromptFromIr,
|
||||
} from "@fusion/core";
|
||||
import { FAST_TRIAGE_SYSTEM_PROMPT, TriageProcessor } from "../triage.js";
|
||||
|
||||
const { mockReviewStep, mockCreateFnAgent } = vi.hoisted(() => ({
|
||||
mockReviewStep: vi.fn(),
|
||||
mockCreateFnAgent: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../reviewer.js", () => ({
|
||||
reviewStep: mockReviewStep,
|
||||
}));
|
||||
|
||||
vi.mock("../pi.js", () => ({
|
||||
createFnAgent: mockCreateFnAgent,
|
||||
describeModel: vi.fn().mockReturnValue("mock-model"),
|
||||
promptWithFallback: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock("@fusion/core", async (importOriginal) => {
|
||||
const { createEngineCoreMock } = await import("../test/mockCore.js");
|
||||
const original = await importOriginal<typeof import("@fusion/core")>();
|
||||
return createEngineCoreMock(() => Promise.resolve(original), {
|
||||
resolveAgentPrompt: vi.fn(original.resolveAgentPrompt),
|
||||
});
|
||||
});
|
||||
|
||||
function createTask(overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
id: "FN-6232-T",
|
||||
description: "Triage planning prompt test",
|
||||
column: "triage",
|
||||
dependencies: [],
|
||||
steps: [],
|
||||
currentStep: 0,
|
||||
log: [],
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function createDetail(task: Task): TaskDetail {
|
||||
return {
|
||||
...task,
|
||||
prompt: "",
|
||||
attachments: [],
|
||||
comments: [],
|
||||
} as TaskDetail;
|
||||
}
|
||||
|
||||
function createStore(task: Task, overrides: Partial<TaskStore> = {}, settings: Partial<Settings> = {}): TaskStore {
|
||||
return {
|
||||
getTask: vi.fn().mockResolvedValue(createDetail(task)),
|
||||
listTasks: vi.fn().mockResolvedValue([]),
|
||||
createTask: vi.fn(),
|
||||
moveTask: vi.fn(),
|
||||
updateTask: vi.fn().mockResolvedValue(undefined),
|
||||
deleteTask: vi.fn(),
|
||||
mergeTask: vi.fn(),
|
||||
getSettings: vi.fn().mockResolvedValue({
|
||||
maxConcurrent: 2,
|
||||
maxWorktrees: 4,
|
||||
pollIntervalMs: 10000,
|
||||
groupOverlappingFiles: false,
|
||||
autoMerge: true,
|
||||
...settings,
|
||||
} as Settings),
|
||||
updateSettings: vi.fn(),
|
||||
logEntry: vi.fn().mockResolvedValue(undefined),
|
||||
appendAgentLog: vi.fn().mockResolvedValue(undefined),
|
||||
getAgentLogs: vi.fn().mockResolvedValue([]),
|
||||
addSteeringComment: vi.fn(),
|
||||
parseDependenciesFromPrompt: vi.fn().mockResolvedValue([]),
|
||||
parseStepsFromPrompt: vi.fn().mockResolvedValue([]),
|
||||
parseFileScopeFromPrompt: vi.fn().mockResolvedValue([]),
|
||||
getTaskWorkflowSelection: vi.fn().mockReturnValue(undefined),
|
||||
getWorkflowDefinition: vi.fn().mockResolvedValue(undefined),
|
||||
on: vi.fn(),
|
||||
emit: vi.fn(),
|
||||
...overrides,
|
||||
} as unknown as TaskStore;
|
||||
}
|
||||
|
||||
async function captureBasePrompt(task: Task, store: TaskStore): Promise<string> {
|
||||
let captured = "";
|
||||
mockCreateFnAgent.mockImplementationOnce(async (opts: any) => {
|
||||
captured = opts.systemPromptLayers?.stable ?? opts.systemPrompt;
|
||||
return {
|
||||
session: {
|
||||
state: {},
|
||||
sessionManager: { getLeafId: vi.fn().mockReturnValue(null) },
|
||||
prompt: vi.fn().mockResolvedValue(undefined),
|
||||
dispose: vi.fn(),
|
||||
navigateTree: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
await new TriageProcessor(store, "/tmp/root").specifyTask(task);
|
||||
return captured;
|
||||
}
|
||||
|
||||
const canonicalPlanningPrompt = resolvePlanningPromptFromIr(BUILTIN_CODING_WORKFLOW_IR)!;
|
||||
|
||||
describe("triage planning prompt single source", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("uses the built-in workflow IR planning prompt in standard mode", async () => {
|
||||
const task = createTask({ id: "FN-6232-BUILTIN", executionMode: "standard" });
|
||||
const store = createStore(task, {
|
||||
getTaskWorkflowSelection: vi.fn().mockReturnValue({ workflowId: "builtin:coding", stepIds: [] }),
|
||||
});
|
||||
|
||||
await expect(captureBasePrompt(task, store)).resolves.toBe(canonicalPlanningPrompt);
|
||||
});
|
||||
|
||||
it("uses the built-in workflow IR planning prompt when no workflow is selected", async () => {
|
||||
const task = createTask({ id: "FN-6232-NO-SELECTION", executionMode: "standard" });
|
||||
const store = createStore(task);
|
||||
|
||||
await expect(captureBasePrompt(task, store)).resolves.toBe(canonicalPlanningPrompt);
|
||||
});
|
||||
|
||||
it("preserves user triage prompt override precedence", async () => {
|
||||
const task = createTask({ id: "FN-6232-OVERRIDE", executionMode: "standard" });
|
||||
const overridePrompt = "custom triage override prompt";
|
||||
const store = createStore(task, {}, {
|
||||
agentPrompts: {
|
||||
templates: [{ id: "custom-triage", name: "Custom", role: "triage", prompt: overridePrompt }],
|
||||
roleAssignments: { triage: "custom-triage" },
|
||||
},
|
||||
} as Partial<Settings>);
|
||||
|
||||
await expect(captureBasePrompt(task, store)).resolves.toBe(overridePrompt);
|
||||
});
|
||||
|
||||
it("keeps fast mode on FAST_TRIAGE_SYSTEM_PROMPT", async () => {
|
||||
const task = createTask({ id: "FN-6232-FAST", executionMode: "fast" });
|
||||
const store = createStore(task);
|
||||
|
||||
await expect(captureBasePrompt(task, store)).resolves.toBe(FAST_TRIAGE_SYSTEM_PROMPT);
|
||||
});
|
||||
|
||||
it("uses a selected custom workflow planning prompt", async () => {
|
||||
const task = createTask({ id: "FN-6232-CUSTOM", executionMode: "standard" });
|
||||
const customPrompt = "custom workflow planning prompt";
|
||||
const customIr: WorkflowIr = {
|
||||
version: "v1",
|
||||
name: "custom-workflow",
|
||||
nodes: [{ id: "planning", kind: "prompt", config: { seam: "planning", prompt: customPrompt } }],
|
||||
edges: [],
|
||||
};
|
||||
const store = createStore(task, {
|
||||
getTaskWorkflowSelection: vi.fn().mockReturnValue({ workflowId: "WF-custom", stepIds: [] }),
|
||||
getWorkflowDefinition: vi.fn().mockResolvedValue({ ir: customIr }),
|
||||
});
|
||||
|
||||
await expect(captureBasePrompt(task, store)).resolves.toBe(customPrompt);
|
||||
});
|
||||
|
||||
it("fails soft to the default triage prompt when workflow resolution cannot provide a planning prompt", async () => {
|
||||
const task = createTask({ id: "FN-6232-FAIL-SOFT", executionMode: "standard" });
|
||||
const store = createStore(task, {
|
||||
getTaskWorkflowSelection: vi.fn(() => {
|
||||
throw new Error("selection unavailable");
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(captureBasePrompt(task, store)).resolves.toBe(resolveAgentPrompt("triage"));
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import type { TaskStore, Task, TaskDetail, Settings } from "@fusion/core";
|
||||
import { resolveAgentPrompt } from "@fusion/core";
|
||||
import {
|
||||
TriageProcessor,
|
||||
TRIAGE_SYSTEM_PROMPT,
|
||||
FAST_TRIAGE_SYSTEM_PROMPT,
|
||||
buildSpecificationPrompt,
|
||||
readAttachmentContents,
|
||||
@@ -21,6 +21,8 @@ const { mockReviewStep, mockCreateFnAgent } = vi.hoisted(() => ({
|
||||
mockCreateFnAgent: vi.fn(),
|
||||
}));
|
||||
|
||||
const TRIAGE_POLICY_PROMPT = resolveAgentPrompt("triage");
|
||||
|
||||
vi.mock("../reviewer.js", () => ({
|
||||
reviewStep: mockReviewStep,
|
||||
}));
|
||||
@@ -33,8 +35,9 @@ vi.mock("../pi.js", () => ({
|
||||
|
||||
vi.mock("@fusion/core", async (importOriginal) => {
|
||||
const { createEngineCoreMock } = await import("../test/mockCore.js");
|
||||
return createEngineCoreMock(() => importOriginal<typeof import("@fusion/core")>(), {
|
||||
resolveAgentPrompt: vi.fn().mockReturnValue(null),
|
||||
const original = await importOriginal<typeof import("@fusion/core")>();
|
||||
return createEngineCoreMock(() => Promise.resolve(original), {
|
||||
resolveAgentPrompt: vi.fn(original.resolveAgentPrompt),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -331,7 +334,7 @@ describe("buildSpecificationPrompt", () => {
|
||||
);
|
||||
|
||||
expect(prompt).toContain("## Subtask Consideration");
|
||||
expect(prompt).toContain("more than 10 implementation steps");
|
||||
expect(prompt).toContain("MORE THAN 7 implementation steps");
|
||||
expect(prompt).toContain("GOOD TO SPLIT");
|
||||
expect(prompt).not.toContain("## Subtask Breakdown Requested");
|
||||
});
|
||||
@@ -593,55 +596,55 @@ describe("buildSpecificationPrompt", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("TRIAGE_SYSTEM_PROMPT", () => {
|
||||
describe("canonical triage policy prompt", () => {
|
||||
it("does not include unconditional research guidance", () => {
|
||||
expect(TRIAGE_SYSTEM_PROMPT).not.toContain("fn_research_run");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).not.toContain("Keep research bounded");
|
||||
expect(TRIAGE_POLICY_PROMPT).not.toContain("fn_research_run");
|
||||
expect(TRIAGE_POLICY_PROMPT).not.toContain("Keep research bounded");
|
||||
});
|
||||
|
||||
it("requires specs to keep lint, tests, build, and typecheck green even outside initial file scope", () => {
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("If keeping lint/tests/build/typecheck green requires edits outside the initial File Scope");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Run lint check");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Run project typecheck if available");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Lint passing");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Typecheck passing (if available)");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Specs must instruct executors to fix lint failures and quality-gate failures directly");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Refuse necessary fixes just because they touch files outside the initial File Scope");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("If keeping lint/tests/build/typecheck green requires edits outside the initial File Scope");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Run lint check");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Run project typecheck if available");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Lint passing");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Typecheck passing (if available)");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Specs must instruct executors to fix lint failures and quality-gate failures directly");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Refuse necessary fixes just because they touch files outside the initial File Scope");
|
||||
});
|
||||
|
||||
it("includes task-artifact location guidance for forensic/reconciliation tasks", () => {
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Task Artifact Location");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("<rootDir>/.fusion/tasks/{TARGET_ID}/");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain(".fusion/fusion.db");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("project root");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("forensic");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Task Artifact Location");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("<rootDir>/.fusion/tasks/{TARGET_ID}/");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain(".fusion/fusion.db");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("project root");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("forensic");
|
||||
});
|
||||
});
|
||||
|
||||
describe("TRIAGE_SYSTEM_PROMPT", () => {
|
||||
describe("canonical triage policy prompt", () => {
|
||||
it("includes proactive M/L subtask breakdown guidance", () => {
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain(
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain(
|
||||
"## Proactive Subtask Breakdown for M/L Tasks",
|
||||
);
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain(
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain(
|
||||
"Even when `breakIntoSubtasks` is not set to `true`",
|
||||
);
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain(
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain(
|
||||
"Size S tasks should NOT be split",
|
||||
);
|
||||
});
|
||||
|
||||
it("includes explicit subtask breakdown thresholds", () => {
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("more than 10 implementation steps");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain(
|
||||
"more than 5 different packages/modules",
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("MORE THAN 7 implementation steps");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain(
|
||||
"MORE THAN 3 different packages/modules",
|
||||
);
|
||||
});
|
||||
|
||||
it("biases toward keeping tasks whole and acknowledges coordination overhead", () => {
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Default to keeping the task whole");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Coordination overhead");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain(
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Default to keeping the task whole");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Coordination overhead");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain(
|
||||
"7-10 focused steps within a coherent scope is fine as one unit",
|
||||
);
|
||||
});
|
||||
@@ -655,7 +658,7 @@ describe("FN-5893 invariant regression wording", () => {
|
||||
|
||||
it("requires invariant-level regression coverage in standard, fast, and core triage prompts", () => {
|
||||
for (const prompt of [
|
||||
TRIAGE_SYSTEM_PROMPT,
|
||||
TRIAGE_POLICY_PROMPT,
|
||||
FAST_TRIAGE_SYSTEM_PROMPT,
|
||||
corePromptSource,
|
||||
]) {
|
||||
@@ -672,7 +675,7 @@ describe("FN-5893 invariant regression wording", () => {
|
||||
const missingSectionRevisePattern =
|
||||
/For bug fixes and UI-affordance add\/remove tasks, the spec MUST include a `## Surface Enumeration` section\. During self-review via `fn_review_spec\(\)`, treat a missing section on a bug-fix or UI-affordance add\/remove spec as a blocking REVISE\./;
|
||||
|
||||
for (const prompt of [TRIAGE_SYSTEM_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
||||
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
||||
expect(prompt).toContain("## Surface Enumeration");
|
||||
expect(prompt).toMatch(missingSectionRevisePattern);
|
||||
expect(prompt).toContain("docs/testing.md");
|
||||
@@ -694,7 +697,7 @@ describe("FN-5893 invariant regression wording", () => {
|
||||
});
|
||||
|
||||
it("requires implementation-step testing guidance to enumerate invariant surfaces in standard and fast prompts", () => {
|
||||
for (const prompt of [TRIAGE_SYSTEM_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
||||
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
||||
expect(prompt).toContain(
|
||||
"Run targeted tests for changed files, asserting the invariant across all known surfaces",
|
||||
);
|
||||
@@ -705,7 +708,7 @@ describe("FN-5893 invariant regression wording", () => {
|
||||
});
|
||||
|
||||
it("defines the FN-6229 Symptom Verification contract in standard and fast prompts", () => {
|
||||
for (const prompt of [TRIAGE_SYSTEM_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
||||
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
||||
expect(prompt).toContain("## Symptom Verification");
|
||||
expect(prompt).toContain("Use the exact heading `## Symptom Verification`");
|
||||
expect(prompt).toContain("**Original symptom** — what the user/issue reported was broken");
|
||||
@@ -720,7 +723,7 @@ describe("FN-5893 invariant regression wording", () => {
|
||||
});
|
||||
|
||||
it("requires Surface Enumeration for UI-affordance add/remove tasks regardless of review-level analysis", () => {
|
||||
for (const prompt of [TRIAGE_SYSTEM_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
||||
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
||||
expect(prompt).toContain("bug-fix tasks and UI-affordance add/remove tasks");
|
||||
expect(prompt).toContain("every component that renders the affordance");
|
||||
expect(prompt).toContain("searching the codebase for the icon/class/testid");
|
||||
@@ -755,7 +758,7 @@ describe("fast-mode triage", () => {
|
||||
});
|
||||
|
||||
it("documents workflow routing in standard and fast prompts", () => {
|
||||
for (const prompt of [TRIAGE_SYSTEM_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
||||
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
||||
expect(prompt).toContain("## Workflow Routing");
|
||||
expect(prompt).toContain("fn_workflow_list");
|
||||
expect(prompt).toContain("fn_workflow_select");
|
||||
@@ -1781,9 +1784,9 @@ describe("approved triage recovery", () => {
|
||||
});
|
||||
|
||||
it("includes decision-only noCommitsExpected heuristic instructions in system prompts", () => {
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("**No commits expected:** true");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Decide whether FN-XYZ needs a fix");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("Investigate FN-XYZ and fix if needed");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("**No commits expected:** true");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Decide whether FN-XYZ needs a fix");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("Investigate FN-XYZ and fix if needed");
|
||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("**No commits expected:** true");
|
||||
});
|
||||
|
||||
@@ -4411,18 +4414,18 @@ describe("FN-4774 regression: triage duplicate detection over done/archived task
|
||||
});
|
||||
|
||||
// Regression: FN-4774 (FN-4827 recovery; supersedes FN-4815) — see docs/triage-duplicate-detection-postmortem.md
|
||||
it("TRIAGE_SYSTEM_PROMPT guides agents to search done/archived before creating", () => {
|
||||
it("canonical triage policy prompt guides agents to search done/archived before creating", () => {
|
||||
// Standard prompt mentions fn_task_search in duplicate-check guidance
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("fn_task_search");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("fn_task_search");
|
||||
// The tool bullet list explicitly states it covers done and archived
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("including done and archived tasks");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("including done and archived tasks");
|
||||
// Duplicate-check section co-locates fn_task_search with done/archived references
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("done");
|
||||
expect(TRIAGE_SYSTEM_PROMPT).toContain("archived");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("done");
|
||||
expect(TRIAGE_POLICY_PROMPT).toContain("archived");
|
||||
// Defensive regex: duplicate-check guidance must cross-reference fn_task_search with done/archived
|
||||
expect(
|
||||
/Duplicate check[\s\S]{0,600}fn_task_search[\s\S]{0,400}(done|archived)/i.test(
|
||||
TRIAGE_SYSTEM_PROMPT,
|
||||
TRIAGE_POLICY_PROMPT,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
getTaskDuplicateLineage,
|
||||
parseExplicitDuplicateMarker,
|
||||
resolveAgentPrompt,
|
||||
resolveTaskPlanningPrompt,
|
||||
resolvePersistAgentThinkingLog,
|
||||
compareTaskPriority,
|
||||
sortTasksByPriorityThenAgeAndId,
|
||||
@@ -87,339 +88,6 @@ import { archiveAsGhostBug } from "./self-healing.js";
|
||||
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
|
||||
import { resolveAndEmitGoalContext } from "./goal-injection-diagnostics.js";
|
||||
|
||||
export const TRIAGE_SYSTEM_PROMPT = `You are a task specification agent for "fn", an AI-orchestrated task board.
|
||||
|
||||
## Your Role
|
||||
You are the specification quality gate for implementation success.
|
||||
Your job: take a rough task description and produce a fully specified PROMPT.md that another AI agent can execute autonomously in a fresh context with zero memory of this conversation.
|
||||
The quality of your spec directly determines execution quality, review churn, and merge risk.
|
||||
|
||||
## What you receive
|
||||
- A raw task title and optional description (the user's rough idea)
|
||||
- Access to the project's files so you can understand context
|
||||
|
||||
## What you produce
|
||||
Write a complete PROMPT.md specification to the given path using the write tool.
|
||||
|
||||
## PROMPT.md Format
|
||||
|
||||
Follow this structure exactly:
|
||||
|
||||
\`\`\`markdown
|
||||
# Task: {ID} - {Name}
|
||||
|
||||
**Created:** {YYYY-MM-DD}
|
||||
**Size:** {S | M | L}
|
||||
|
||||
## Review Level: {0-3} ({None | Plan Only | Plan and Code | Full})
|
||||
|
||||
**Assessment:** {1-2 sentences explaining the score}
|
||||
**Score:** {N}/8 — Blast radius: {N}, Pattern novelty: {N}, Security: {N}, Reversibility: {N}
|
||||
|
||||
## Mission
|
||||
|
||||
{One paragraph: what you're building and why it matters}
|
||||
|
||||
## Surface Enumeration
|
||||
|
||||
{Required for bug-fix tasks and UI-affordance add/remove tasks (adding, removing, or restructuring icons, buttons, chevrons/arrows, toggles, badges, menu entries, click targets): a checklist enumerating every surface the fixed invariant must hold across. Include every provider/bridge for streaming and agent paths; desktop AND mobile breakpoints; empty/undefined/duplicate/populated data states; and every hook/component/module that shares the affected logic. For UI-affordance add/remove tasks, enumerate every component that renders the affordance by searching the codebase for the icon/class/testid — not just the component the user pointed at. Explicitly check for leftover shells after removal (empty buttons, orphaned click targets, now-unused wrappers, dangling aria-labels) across both desktop and mobile breakpoints. Use the canonical checklist in docs/testing.md as the starting point.}
|
||||
|
||||
## Symptom Verification
|
||||
|
||||
{Required for bug-class/bug-fix tasks only; feature/docs/non-bug tasks do not need this section. Use the exact heading \`## Symptom Verification\` and include: (1) **Original symptom** — what the user/issue reported was broken; (2) **Exact reproduction** — the precise steps, inputs, fixture, or automated repro that triggered the failure; (3) **Assertion it is gone** — the executor's final verification must reproduce that original failure condition and assert it no longer occurs via a real automated test. Green build/tests alone are insufficient without symptom-based acceptance.}
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **None**
|
||||
{OR}
|
||||
- **Task:** {ID} ({what must be complete})
|
||||
|
||||
## Context to Read First
|
||||
|
||||
{List specific files the worker should read before starting — only what's needed}
|
||||
|
||||
## File Scope
|
||||
|
||||
{List files/directories the task will create or modify — be specific}
|
||||
|
||||
- \`path/to/file.ext\`
|
||||
- \`path/to/directory/*\`
|
||||
|
||||
## Steps
|
||||
|
||||
> Optional: a step heading may carry a \`(depends: N,M)\` annotation listing the 1-indexed
|
||||
> step numbers it depends on — e.g. \`### Step 3 (depends: 1): Title\`. Annotate ONLY steps
|
||||
> that are genuinely independent of their immediate predecessor; an unannotated step is
|
||||
> assumed to depend on the one before it (fully sequential). Be conservative — only mark a
|
||||
> step independent when it truly does not read or modify the prior step's output.
|
||||
|
||||
### Step 0: Preflight
|
||||
|
||||
- [ ] Required files and paths exist
|
||||
- [ ] Dependencies satisfied
|
||||
|
||||
### Step 1: {Name}
|
||||
|
||||
- [ ] {Specific, verifiable outcome}
|
||||
- [ ] {Specific, verifiable outcome}
|
||||
- [ ] Run targeted tests for changed files, asserting the invariant across all known surfaces (enumerate every provider/bridge, desktop + mobile breakpoints, and empty/undefined/populated data states)
|
||||
|
||||
For bug-fix and UI-affordance add/remove tasks, paste and fill in this checklist in the \`## Surface Enumeration\` section:
|
||||
- [ ] Providers / bridges / execution paths touched by the invariant
|
||||
- [ ] Desktop + mobile breakpoints / platforms that exercise the behavior
|
||||
- [ ] Empty / undefined / duplicate / populated data states
|
||||
- [ ] Shared hooks / components / modules / helpers reusing the logic
|
||||
- [ ] Every component that renders the affordance (search the codebase for the icon/class/testid, not just the one the user pointed at)
|
||||
- [ ] Leftover shells after removal — empty buttons, orphaned click targets, now-unused wrappers, dangling aria-labels — are explicitly checked and fixed/hidden
|
||||
|
||||
For bug-class/bug-fix tasks, add and fill in the exact \`## Symptom Verification\` section:
|
||||
- [ ] **Original symptom** — what the user/issue reported was broken
|
||||
- [ ] **Exact reproduction** — the precise steps, inputs, fixture, or automated repro that triggered the failure
|
||||
- [ ] **Assertion it is gone** — final verification reproduces the original failure condition and asserts it no longer occurs via a real automated test; green build/tests alone are insufficient
|
||||
|
||||
**Artifacts:**
|
||||
- \`path/to/file\` (new | modified)
|
||||
|
||||
### Step {N-1}: Testing & Verification
|
||||
|
||||
> ZERO failures allowed for checks required by this task's quality gates. Run impacted/package-scoped verification first; run workspace-wide suites only when the task or workflow explicitly requires them, or during final integration after impacted checks pass.
|
||||
> If keeping lint/tests/build/typecheck green requires edits outside the initial File Scope, make those fixes as part of this task.
|
||||
|
||||
- [ ] Run lint check (\`pnpm lint\`)
|
||||
- [ ] Run impacted tests
|
||||
- [ ] Run project typecheck if available
|
||||
- [ ] Fix all failures
|
||||
- [ ] Build passes
|
||||
|
||||
### Step {N}: Documentation & Delivery
|
||||
|
||||
- [ ] Update relevant documentation
|
||||
- [ ] Save documentation deliverables as task documents via \`fn_task_document_write\` (key="docs", content=...)
|
||||
- [ ] Out-of-scope findings created as new tasks via \`fn_task_create\` tool
|
||||
|
||||
## Documentation Requirements
|
||||
|
||||
**Must Update:**
|
||||
- \`path/to/doc.md\` — {what to add/change}
|
||||
|
||||
**Check If Affected:**
|
||||
- \`path/to/doc.md\` — {update if relevant}
|
||||
|
||||
## Completion Criteria
|
||||
|
||||
- [ ] All steps complete
|
||||
- [ ] Lint passing
|
||||
- [ ] All tests passing
|
||||
- [ ] Typecheck passing (if available)
|
||||
- [ ] Documentation updated
|
||||
|
||||
## Git Commit Convention
|
||||
|
||||
Commits at step boundaries. All commits include the task ID:
|
||||
|
||||
- **Step completion:** \`feat({ID}): complete Step N — <short summary>\` (the \`<short summary>\` is required — use a concrete 5–10 word description)
|
||||
- **Bug fixes:** \`fix({ID}): description\` (short, concrete summary required)
|
||||
- **Tests:** \`test({ID}): description\` (short, concrete summary required)
|
||||
|
||||
Good examples:
|
||||
- \`feat(FN-1234): complete Step 2 — add retry guard for workflow step timeouts\`
|
||||
- \`test(FN-1234): add regression tests for paused-session cleanup\`
|
||||
|
||||
Bad example:
|
||||
- \`feat(FN-1234): complete Step 2\`
|
||||
|
||||
## Do NOT
|
||||
|
||||
- Expand task scope
|
||||
- Skip tests
|
||||
- Refuse necessary fixes just because they touch files outside the initial File Scope
|
||||
- Commit without the task ID prefix
|
||||
- Remove, delete, or gut modules, settings, interfaces, exports, or test files outside the File Scope
|
||||
- Remove features as "cleanup" — if something seems unused, create a task via \`fn_task_create\`
|
||||
|
||||
## Changeset Requirements
|
||||
|
||||
If this task REMOVES existing functionality (deleting modules, settings, API endpoints, or exports), a changeset file is REQUIRED:
|
||||
- Create \`.changeset/{task-id}-removal.md\` explaining what was removed and why
|
||||
- This is mandatory for any net-negative change (more deletions than additions to existing files)
|
||||
\`\`\`
|
||||
|
||||
## Testing requirements
|
||||
|
||||
The Testing & Verification step MUST require REAL automated tests — actual test
|
||||
files with assertions that run via a test runner. Typechecks and builds are NOT
|
||||
tests. Manual verification is NOT a test.
|
||||
|
||||
- Each implementation step should include writing tests for the code being changed
|
||||
- For bug fixes and UI-affordance add/remove tasks, the spec MUST include a \`## Surface Enumeration\` section. During self-review via \`fn_review_spec()\`, treat a missing section on a bug-fix or UI-affordance add/remove spec as a blocking REVISE.
|
||||
- For bug fixes and UI-affordance add/remove tasks, populate \`## Surface Enumeration\` with this checklist from \`docs/testing.md\`: providers/bridges/execution paths; desktop + mobile breakpoints/platforms; empty/undefined/duplicate/populated data states; shared hooks/components/modules/helpers; every component that renders the affordance; leftover shells after removal.
|
||||
- For bug fixes and UI-affordance add/remove tasks, regression tests must assert the invariant across all known surfaces — enumerate every provider/bridge, desktop + mobile breakpoints, empty/undefined/populated data states, and for UI-affordance changes every component rendering the affordance plus leftover shells after removal — not just the reported repro (see FN-5787/FN-5789/FN-5803, FN-5751, and FN-6115/FN-6118/FN-6123)
|
||||
- For bug-class/bug-fix tasks, the spec MUST include a \`## Symptom Verification\` section with **Original symptom**, **Exact reproduction**, and **Assertion it is gone**. The final verification step must perform symptom-based acceptance: reproduce the original failure and prove it is gone with a real automated test. Green build/tests alone are insufficient. Feature/docs/non-bug tasks are not required to carry \`## Symptom Verification\`.
|
||||
- The final Testing step runs lint, impacted/package-scoped tests first, and project typecheck when the repo exposes one. Run workspace-wide suites only when explicitly required by the task/workflow or during final integration after impacted checks pass.
|
||||
- Specs must instruct executors to fix lint failures and quality-gate failures directly, even when the required edits extend beyond the original File Scope
|
||||
- If the project has no test framework, the Testing step must include setting one up
|
||||
as part of this task (not just skipping tests)
|
||||
|
||||
## Duplicate check
|
||||
Before writing a spec, first call \`fn_task_list\` to see active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names).
|
||||
For any likely match in \`done\` or \`archived\`, call \`fn_task_get\` to inspect details before deciding.
|
||||
If a task already covers the same work (even if worded differently), do NOT
|
||||
write a PROMPT.md. Instead, write a single line to the output file:
|
||||
\`DUPLICATE: {existing-task-id}\`
|
||||
|
||||
## Dependency awareness
|
||||
When you plan to list a task in the \`## Dependencies\` section, first call \`fn_task_get\` on that task ID to read its PROMPT.md.
|
||||
Use what you learn — file scope, APIs, patterns, completion criteria — to make the new spec accurate: reference the right paths, avoid conflicting assumptions, and describe what the dependency must deliver before this task starts.
|
||||
If the dependency task has no PROMPT.md yet (not yet specified), note that in the Dependencies section.
|
||||
|
||||
## Triage subtask breakdown
|
||||
When the task includes \`breakIntoSubtasks: true\`, first decide whether it should be split.
|
||||
|
||||
- Split only when the work is meaningfully decomposable into 2-5 independently executable child tasks.
|
||||
- If splitting: use the \`fn_task_create\` tool to create child tasks in triage, include clear descriptions and dependencies between them, then stop. Do NOT write a PROMPT.md for the parent task.
|
||||
- **CRITICAL — subtask dependencies:** the parent task is deleted once all subtasks are created. \`dependencies\` on a new subtask may ONLY reference sibling subtasks you have created earlier in this same split (or unrelated existing tasks). **Never depend on the parent task's id.** If a child conceptually "waits for the parent's remaining work", create a sibling subtask that does that work and depend on the sibling instead. The \`fn_task_create\` tool will reject parent-id dependencies with an error.
|
||||
- If not splitting: proceed with a normal PROMPT.md specification.
|
||||
|
||||
## Proactive Subtask Breakdown for M/L Tasks
|
||||
For tasks you assess as Size M or L, consider whether splitting into 2-5 child tasks would improve execution quality. Default to keeping the task whole; only split when the work is genuinely large or has clearly independent deliverables.
|
||||
|
||||
**Consider splitting when ANY of these apply:**
|
||||
- The task will require more than 10 implementation steps
|
||||
- The task affects more than 5 different packages/modules with distinct concerns (a typed field change that naturally touches core types + store + UI + tests is NOT 4 distinct concerns — it's one coherent change)
|
||||
- Any single step would take more than 3-4 hours to complete
|
||||
- The task has multiple clearly independent deliverables that could be developed and shipped in parallel by different people
|
||||
|
||||
**Splitting guidance:**
|
||||
- Even when \`breakIntoSubtasks\` is not set to \`true\`, apply these thresholds proactively
|
||||
- Keep explicit user intent first: when \`breakIntoSubtasks: true\`, follow the mandatory breakdown flow above
|
||||
- Size S tasks should NOT be split — the overhead outweighs the benefit
|
||||
- A task with 7-10 focused steps within a coherent scope is fine as one unit; do not split it
|
||||
- Coordination overhead (worktrees, dependency wiring, merge sequencing) is real — only split when the parallelism or scope-clarity benefit clearly outweighs it
|
||||
- If you decide not to split an M/L task, proceed with a normal PROMPT.md specification
|
||||
|
||||
**Broad-scope decomposition signals:**
|
||||
- Size L tasks, especially when the planned step count would reach 9 or more.
|
||||
- Plans whose implementation-step count would reach 12 or more (additive signal — counts even when the surrounding "more than 7/10 steps" threshold above has not yet fired).
|
||||
- Tasks whose declared \`## File Scope\` would list 20 or more entries.
|
||||
- Descriptions that quantify large remediation batches (for example "47 failing tests", "30+ broken files") at or above 30 items — treat as a strong signal that the work should be partitioned by subsystem or file group before specifying.
|
||||
- When two or more of the signals above fire together, default to splitting via \`fn_task_create\`. If you still choose to keep the task as a single unit, justify the decision explicitly in the PROMPT.md \`## Mission\` paragraph.
|
||||
|
||||
## Triage tools
|
||||
You have these extra tools during triage:
|
||||
- \`fn_task_list\` — list existing active tasks
|
||||
- \`fn_task_search\` — keyword search across tasks, including done and archived tasks
|
||||
- \`fn_task_get\` — inspect a task and its PROMPT.md
|
||||
- \`fn_task_create\` — create a child/follow-up task while triaging
|
||||
- \`fn_task_document_write\` — save a planning document (e.g., key="plan")
|
||||
- \`fn_task_document_read\` — read back a previously saved document
|
||||
|
||||
When the planning conversation produces a structured plan, save it as a document with \`fn_task_document_write(key='plan', content='...')\` so the executor can reference it during implementation.
|
||||
|
||||
## Step Design Principles
|
||||
- Each implementation step should produce a testable artifact or observable outcome
|
||||
- Order steps by dependency (foundation before integration, implementation before final validation)
|
||||
- Testing & Verification must run before Documentation & Delivery
|
||||
- Avoid giant catch-all steps; split outcomes so execution can be verified incrementally
|
||||
|
||||
## Decision-only task flag (noCommitsExpected)
|
||||
When ALL of the following are true, include this metadata line in the header block after Size/Review Level:
|
||||
|
||||
- Add this exact line: **No commits expected:** true
|
||||
|
||||
Set it only when all of these conditions hold:
|
||||
- Title/mission starts with decision verbs like "Decide", "Evaluate", "Verify", "Confirm", "Audit", "Review whether", or "Investigate and report"
|
||||
- Acceptance criteria are strictly observational (record findings, log a decision, update task log/docs) with no required code/config/file mutations
|
||||
- Task description explicitly says things like "no code changes expected" or "the deliverable is the recorded decision"
|
||||
|
||||
Anti-heuristics (bias to false-negative when ambiguous):
|
||||
- SET: Decide whether FN-XYZ needs a fix
|
||||
- LEAVE UNSET: Investigate FN-XYZ
|
||||
- LEAVE UNSET: Investigate FN-XYZ and fix if needed
|
||||
|
||||
## Guidelines
|
||||
- Read the project structure and relevant source files to understand context BEFORE writing
|
||||
- Check package.json/scripts and explicit project commands to align real lint/test/build/typecheck commands
|
||||
- Look for similar completed tasks and existing code patterns before inventing spec structure
|
||||
- Be specific — name actual files, functions, and patterns from the codebase
|
||||
- Steps should express OUTCOMES, not micro-instructions (2-5 checkboxes per step)
|
||||
- Always include a testing step and a documentation step
|
||||
- For tasks whose primary deliverable is documentation (updating docs, writing README, API references), include an explicit step or checkbox instructing the executor to save the final documentation content via \`fn_task_document_write\`
|
||||
- Include a "Do NOT" section with project-appropriate guardrails
|
||||
- Size assessment: S (<2h), M (2-4h), L (4-8h). Split if XL (8h+)
|
||||
- Review level scoring: Blast radius (0-2), Pattern novelty (0-2), Security (0-2), Reversibility (0-2)
|
||||
- 0-1 → Level 0, 2-3 → Level 1, 4-5 → Level 2, 6-8 → Level 3
|
||||
|
||||
## Project commands
|
||||
When the user prompt includes a "Project Commands" section with test and/or build
|
||||
commands, use those EXACT commands in the testing/verification steps and anywhere
|
||||
the spec references running tests or builds. Do NOT guess or infer commands from
|
||||
package.json when explicit commands are provided.
|
||||
|
||||
## Workflow Routing
|
||||
- Call \`fn_workflow_list\` to discover available workflows before selecting a routing path, and read each workflow description as the routing signal.
|
||||
- For investigation, audit, research, or decision-only tasks that produce no code changes, set \`**No commits expected:** true\` in the PROMPT.md header when the no-commits criteria above are met, then select an appropriate lightweight workflow.
|
||||
- For decision-only tasks (Decide, Evaluate, Verify, Confirm, Audit, Review whether, Investigate and report), prefer \`builtin:quick-fix\` or a custom investigation workflow when one is available.
|
||||
- For standard coding tasks, \`builtin:coding\` is the default and is usually appropriate.
|
||||
- Use \`fn_workflow_select\` to set the workflow on the current task, or pass \`workflow_id\` to \`fn_task_create\` when creating subtasks.
|
||||
- Match the task nature to the workflow description; descriptions are authoritative for routing decisions.
|
||||
|
||||
## Spec Review
|
||||
|
||||
After writing the PROMPT.md, call \`fn_review_spec()\` to get an independent quality review.
|
||||
|
||||
- **APPROVE** → your spec is accepted, you're done
|
||||
- **REVISE** → fix the issues described in the review feedback, rewrite the PROMPT.md, and call \`fn_review_spec()\` again. Repeat until approved.
|
||||
- **RETHINK** → your approach was fundamentally rejected. The conversation will rewind. Read the feedback carefully and take a completely different approach. Do NOT repeat the rejected strategy.
|
||||
|
||||
You MUST call \`fn_review_spec()\` after writing the PROMPT.md. Do not finish without getting an APPROVE verdict.
|
||||
|
||||
## PROMPT.md Quality Bar (Good vs Bad)
|
||||
- Good: concrete mission, realistic file scope, dependency-aware step order, explicit quality gates, and clear non-goals.
|
||||
- Bad: generic wording, vague steps ("implement feature"), missing tests, or file scope that cannot realistically satisfy requested behavior.
|
||||
- Good file scope estimation includes likely touched tests, config, and integration files — not only the obvious implementation file.
|
||||
|
||||
Never reference a \`.fusion/tasks/<id>/<file>\` artifact in Context, Steps, or File Scope unless (a) the file already exists, (b) the step explicitly creates it (listed as \`(new)\` under Artifacts), or (c) it is \`PROMPT.md\` / \`task.json\` / \`attachments/*\` for a sibling task. Save planning scratch as task documents via \`fn_task_document_write\`, not as files on disk.
|
||||
|
||||
## Output
|
||||
Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\` for review.
|
||||
|
||||
## Task Artifact Location for Forensic / Reconciliation Tasks
|
||||
|
||||
If the task targets a different task ID (audit, forensic walk, historical reconciliation, task-ID-collision investigation, live task metadata repair, or any work where evidence is another task's \`task.json\` / \`PROMPT.md\` / DB row), include this guidance in the generated PROMPT.md \`## Context to Read First\` and \`## File Scope\`:
|
||||
- Authoritative target-task artifacts live at the **project root**: \`<rootDir>/.fusion/tasks/{TARGET_ID}/\` (\`task.json\`, \`PROMPT.md\`, \`attachments/\`, agent logs).
|
||||
- Authoritative task DB rows live at the **project root** SQLite file: \`<rootDir>/.fusion/fusion.db\` (WAL mode). Read via \`TaskStore\` APIs; do not instruct direct SQL surgery.
|
||||
- \`.fusion/\` is gitignored, so a fresh worktree from \`main\` does **not** include \`.fusion/tasks/{TARGET_ID}/\` or \`.fusion/fusion.db\`. The running worktree's own \`.fusion/\` (if present) is scratch/session state for the running task only, not source of truth.
|
||||
- Prefer \`fn_task_get\` / \`fn_task_list\` when the target task ID is known; fall back to project-root filesystem reads only when tools cannot provide needed evidence.
|
||||
|
||||
## Frontend UX Criteria Injection
|
||||
|
||||
<!-- UX criteria mirror the "frontend-ux-design" reviewer persona in packages/core/src/types.ts — keep them aligned. -->
|
||||
|
||||
If the derived **File Scope** touches any of the following paths:
|
||||
- \`packages/dashboard/**\`
|
||||
- \`packages/*/app/components/**\`
|
||||
- \`packages/*/app/hooks/**\`
|
||||
- Any \`*.css\` or \`*.tsx\` file inside a dashboard-like package
|
||||
|
||||
…then **PREPEND** a \`## Frontend UX Criteria\` section to the generated PROMPT.md, placed immediately after the \`## Mission\` section.
|
||||
|
||||
Use this exact checklist (keep it verbatim — do not expand or reorder):
|
||||
|
||||
\`\`\`markdown
|
||||
## Frontend UX Criteria
|
||||
|
||||
- [ ] **Design tokens only** — no hardcoded \`px\` values except \`0\`, no hardcoded hex/rgb colors; use CSS custom properties (\`--color-*\`, \`--spacing-*\`, etc.)
|
||||
- [ ] **Icon sizing** — match the surrounding component's icon size convention (default lucide size unless the local pattern already uses an explicit \`size={N}\`)
|
||||
- [ ] **Semantic color tokens for status** — use \`--color-error\` for stderr/error states, \`--color-warning\` for starting/pending states; never hardcode status colors
|
||||
- [ ] **Component reuse** — reach for existing classes (\`.btn\`, \`.btn-icon\`, \`.card\`, \`.input\`) before writing one-off styles
|
||||
- [ ] **Responsive scaffolding** — add \`@media (max-width: 768px)\` overrides for any new layout; verify mobile usability
|
||||
- [ ] **Single canonical nav destination** — each route must appear in exactly one of: Header primary nav, Header overflow menu, or MobileNavBar More; no duplicates across all three
|
||||
- [ ] **Status-indicator dot convention** — use the existing \`.status-dot\` pattern (size, border, animation) rather than custom dot styling
|
||||
- [ ] **Visual hierarchy preserved** — new elements must not disrupt heading levels, content flow, or information architecture established in the surrounding page
|
||||
\`\`\`
|
||||
|
||||
Only inject this section when the task genuinely touches frontend UI. Omit it for backend-only, config-only, or documentation-only tasks.`;
|
||||
|
||||
export const FAST_TRIAGE_SYSTEM_PROMPT = `You are a task specification agent for "fn", an AI-orchestrated task board. This task is running in **fast mode** — produce a lean, executable PROMPT.md without heavyweight review scoring or subtask analysis.
|
||||
|
||||
## Your Role
|
||||
@@ -1309,9 +977,17 @@ export class TriageProcessor {
|
||||
runContext: triageRunContext,
|
||||
});
|
||||
|
||||
const workflowPlanningPrompt = isFast
|
||||
? undefined
|
||||
: await resolveTaskPlanningPrompt(this.store, task.id).catch(() => undefined);
|
||||
// FN-6232: standard-mode built-in triage policy is sourced from the workflow IR planning node; the former engine duplicate was removed.
|
||||
const userTriagePrompt = settings.agentPrompts?.roleAssignments?.triage
|
||||
? resolveAgentPrompt("triage", settings.agentPrompts)
|
||||
: "";
|
||||
const defaultTriagePrompt = resolveAgentPrompt("triage");
|
||||
const triageLayers = buildPromptLayers({
|
||||
basePrompt: resolveAgentPrompt("triage", settings.agentPrompts)
|
||||
|| (isFast ? FAST_TRIAGE_SYSTEM_PROMPT : TRIAGE_SYSTEM_PROMPT),
|
||||
basePrompt: userTriagePrompt
|
||||
|| (isFast ? FAST_TRIAGE_SYSTEM_PROMPT : (workflowPlanningPrompt || defaultTriagePrompt)),
|
||||
goalContext: triageGoalResolution.goalContext,
|
||||
agentInstructions: [
|
||||
triageIdentitySection,
|
||||
@@ -3094,9 +2770,9 @@ The user has requested that this task be broken into smaller subtasks if it is c
|
||||
The user did not explicitly request subtask breakdown. Default to keeping the task whole; only split when the work is genuinely large or has clearly independent deliverables.
|
||||
|
||||
**Split into 2-5 child tasks when ANY of these apply:**
|
||||
- The task will require more than 10 implementation steps
|
||||
- The task affects more than 5 different packages/modules with distinct concerns (touching multiple packages as a coherent vertical change does NOT count — e.g. types + store + UI + tests for one feature is one task)
|
||||
- Any single step would take more than 3-4 hours to complete
|
||||
- The task will require MORE THAN 7 implementation steps
|
||||
- The task affects MORE THAN 3 different packages/modules with distinct concerns (touching multiple packages as a coherent vertical change does NOT count — e.g. types + store + UI + tests for one feature is one task)
|
||||
- Any single step would take more than 1-2 hours to complete
|
||||
- The task has multiple clearly independent deliverables that could be developed and shipped in parallel by different people
|
||||
|
||||
**GOOD TO SPLIT:**
|
||||
|
||||
Reference in New Issue
Block a user