FN-6236: move fast triage into workflow settings
Move fast triage prompting into the workflow/settings prompt model. - Add a core-owned fast triage prompt and expose the workflow prompt mode type. - Add a fast-mode builtin workflow variant and route triage prompt selection through workflow settings. - Remove the engine-local fast triage prompt constant and update triage tests for workflow-driven fast mode. - Document the fast triage prompt mode setting and add a patch changeset. Files changed: .changeset/FN-6236-fast-mode-workflow-variant.md | 7 + docs/settings-reference.md | 2 + packages/core/src/__tests__/agent-prompts.test.ts | 16 +- .../builtin-workflow-settings-triage.test.ts | 5 + packages/core/src/agent-prompts.ts | 221 +++++++++++++++++++ packages/core/src/builtin-workflow-prompts.ts | 12 +- packages/core/src/builtin-workflow-settings.ts | 18 +- packages/core/src/index.ts | 5 + packages/core/src/types.ts | 4 + .../triage-duplicate-search-regression.test.ts | 18 +- .../triage-fast-mode-workflow-variant.test.ts | 219 +++++++++++++++++++ .../triage-planning-prompt-single-source.test.ts | 8 +- packages/engine/src/__tests__/triage.test.ts | 64 +++--- packages/engine/src/triage.ts | 235 ++------------------- 14 files changed, 565 insertions(+), 269 deletions(-) Fusion-Task-Id: FN-6236 Fusion-Task-Lineage: 369954d8-41e7-4d88-aa6d-e6bbca077d50
This commit is contained in:
7
.changeset/FN-6236-fast-mode-workflow-variant.md
Normal file
7
.changeset/FN-6236-fast-mode-workflow-variant.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Fast-mode triage is now expressed as workflow-declared policy: the lean prompt lives in the built-in `default-triage-fast` agent prompt and `planning-fast` seam, while `leanPlanning` and `autoApproveSpec` are workflow-native settings for prompt selection and spec-review auto-approval.
|
||||||
|
|
||||||
|
The internal `FAST_TRIAGE_SYSTEM_PROMPT` engine constant was removed. Existing `executionMode: "fast"` tasks remain byte-equivalent through a single legacy execution-mode-to-resolved-policy bridge.
|
||||||
@@ -254,6 +254,8 @@ The built-in workflows also declare triage/spec policy settings that were **not*
|
|||||||
| `triageNoCommitsDecisionVerbs` | all seven built-ins | Decision-only verbs: Decide, Evaluate, Verify, Confirm, Audit, Review whether, Investigate and report. |
|
| `triageNoCommitsDecisionVerbs` | all seven built-ins | Decision-only verbs: Decide, Evaluate, Verify, Confirm, Audit, Review whether, Investigate and report. |
|
||||||
| `triageDecisionOnlyWorkflowId` | `builtin:quick-fix` | Preferred workflow for decision-only/no-commit tasks. |
|
| `triageDecisionOnlyWorkflowId` | `builtin:quick-fix` | Preferred workflow for decision-only/no-commit tasks. |
|
||||||
| `triageDefaultWorkflowId` | `builtin:coding` | Default workflow for standard coding tasks. |
|
| `triageDefaultWorkflowId` | `builtin:coding` | Default workflow for standard coding tasks. |
|
||||||
|
| `leanPlanning` | `false` | Workflow-native fast-mode policy: select the lean `planning-fast` prompt variant instead of the full triage spec prompt. |
|
||||||
|
| `autoApproveSpec` | `false` | Workflow-native fast-mode policy: auto-approve generated specs and skip the independent spec reviewer. |
|
||||||
|
|
||||||
In the dashboard Settings modal, Project Models now exposes Plan/Triage, Executor,
|
In the dashboard Settings modal, Project Models now exposes Plan/Triage, Executor,
|
||||||
and Reviewer dropdown controls for the default workflow. The modal's primary
|
and Reviewer dropdown controls for the default workflow. The modal's primary
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
getTemplatesForRole,
|
getTemplatesForRole,
|
||||||
} 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 { BUILTIN_SEAM_PROMPTS, builtinSeamPrompt } from "../builtin-workflow-prompts.js";
|
||||||
import { renderTriagePolicyPlaceholders } from "../builtin-workflow-settings.js";
|
import { renderTriagePolicyPlaceholders } from "../builtin-workflow-settings.js";
|
||||||
import { resolvePlanningPromptFromIr, resolveSeamPromptFromIr } 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";
|
||||||
@@ -261,6 +262,17 @@ describe("resolveAgentPrompt", () => {
|
|||||||
expect(result).toContain("task_document_write");
|
expect(result).toContain("task_document_write");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("fast triage prompt is sourced from built-in workflow seam data", () => {
|
||||||
|
const fastTemplate = BUILTIN_AGENT_PROMPTS.find((prompt) => prompt.id === "default-triage-fast");
|
||||||
|
|
||||||
|
expect(fastTemplate).toBeDefined();
|
||||||
|
expect(fastTemplate?.role).toBe("triage");
|
||||||
|
expect(BUILTIN_SEAM_PROMPTS["planning-fast"]).toBe(fastTemplate?.prompt);
|
||||||
|
expect(builtinSeamPrompt("planning-fast")).toBe(fastTemplate?.prompt);
|
||||||
|
expect(builtinSeamPrompt("planning-fast")).toContain("This task is running in **fast mode**");
|
||||||
|
expect(builtinSeamPrompt("planning-fast")).not.toContain("## Review Level");
|
||||||
|
});
|
||||||
|
|
||||||
it("triage planning prompt is sourced from workflow IR without an engine duplicate", () => {
|
it("triage planning prompt is sourced from workflow IR without an engine duplicate", () => {
|
||||||
const corePrompt = resolveAgentPrompt("triage");
|
const corePrompt = resolveAgentPrompt("triage");
|
||||||
const planningPrompt = resolvePlanningPromptFromIr(BUILTIN_CODING_WORKFLOW_IR);
|
const planningPrompt = resolvePlanningPromptFromIr(BUILTIN_CODING_WORKFLOW_IR);
|
||||||
@@ -269,8 +281,8 @@ describe("resolveAgentPrompt", () => {
|
|||||||
"utf8",
|
"utf8",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(triageSource).not.toMatch(/export const TRIAGE_SYSTEM_PROMPT\s*=/);
|
expect(triageSource).not.toContain(["FAST", "TRIAGE", "SYSTEM", "PROMPT"].join("_"));
|
||||||
expect(triageSource).not.toMatch(/export const (?!FAST_TRIAGE_SYSTEM_PROMPT)[A-Z_]*TRIAGE[A-Z_]*SYSTEM_PROMPT\s*=/);
|
expect(triageSource).not.toMatch(/export const [A-Z_]*TRIAGE[A-Z_]*SYSTEM_PROMPT\s*=/);
|
||||||
expect(planningPrompt).toBe(corePrompt);
|
expect(planningPrompt).toBe(corePrompt);
|
||||||
expect(corePrompt).toContain("**Broad-scope decomposition signals:**");
|
expect(corePrompt).toContain("**Broad-scope decomposition signals:**");
|
||||||
expect(corePrompt).toContain("step count would reach {{triageSubtaskLargeStepSignal}} or more");
|
expect(corePrompt).toContain("step count would reach {{triageSubtaskLargeStepSignal}} or more");
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
BUILTIN_WORKFLOW_SETTINGS,
|
BUILTIN_WORKFLOW_SETTINGS,
|
||||||
renderTriagePolicyPlaceholders,
|
renderTriagePolicyPlaceholders,
|
||||||
} from "../builtin-workflow-settings.js";
|
} from "../builtin-workflow-settings.js";
|
||||||
|
import { MOVED_SETTINGS_KEYS } from "../moved-settings.js";
|
||||||
|
|
||||||
const expectedDefaults: Record<string, { type: string; default: unknown }> = {
|
const expectedDefaults: Record<string, { type: string; default: unknown }> = {
|
||||||
triageSizeSmallMaxHours: { type: "number", default: 2 },
|
triageSizeSmallMaxHours: { type: "number", default: 2 },
|
||||||
@@ -22,6 +23,8 @@ const expectedDefaults: Record<string, { type: string; default: unknown }> = {
|
|||||||
},
|
},
|
||||||
triageDecisionOnlyWorkflowId: { type: "enum", default: "builtin:quick-fix" },
|
triageDecisionOnlyWorkflowId: { type: "enum", default: "builtin:quick-fix" },
|
||||||
triageDefaultWorkflowId: { type: "enum", default: "builtin:coding" },
|
triageDefaultWorkflowId: { type: "enum", default: "builtin:coding" },
|
||||||
|
leanPlanning: { type: "boolean", default: false },
|
||||||
|
autoApproveSpec: { type: "boolean", default: false },
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("workflow-native triage policy settings", () => {
|
describe("workflow-native triage policy settings", () => {
|
||||||
@@ -29,6 +32,7 @@ describe("workflow-native triage policy settings", () => {
|
|||||||
const triageById = new Map(BUILTIN_TRIAGE_POLICY_SETTINGS.map((setting) => [setting.id, setting]));
|
const triageById = new Map(BUILTIN_TRIAGE_POLICY_SETTINGS.map((setting) => [setting.id, setting]));
|
||||||
const fullIds = new Set(BUILTIN_WORKFLOW_SETTINGS.map((setting) => setting.id));
|
const fullIds = new Set(BUILTIN_WORKFLOW_SETTINGS.map((setting) => setting.id));
|
||||||
const movedIds = new Set(BUILTIN_MOVED_WORKFLOW_SETTINGS.map((setting) => setting.id));
|
const movedIds = new Set(BUILTIN_MOVED_WORKFLOW_SETTINGS.map((setting) => setting.id));
|
||||||
|
const movedKeyIds = new Set(MOVED_SETTINGS_KEYS);
|
||||||
|
|
||||||
expect(BUILTIN_TRIAGE_POLICY_SETTINGS).toHaveLength(Object.keys(expectedDefaults).length);
|
expect(BUILTIN_TRIAGE_POLICY_SETTINGS).toHaveLength(Object.keys(expectedDefaults).length);
|
||||||
for (const [id, expected] of Object.entries(expectedDefaults)) {
|
for (const [id, expected] of Object.entries(expectedDefaults)) {
|
||||||
@@ -38,6 +42,7 @@ describe("workflow-native triage policy settings", () => {
|
|||||||
expect(setting?.default).toStrictEqual(expected.default);
|
expect(setting?.default).toStrictEqual(expected.default);
|
||||||
expect(fullIds.has(id), `${id} should be in the full built-in catalog`).toBe(true);
|
expect(fullIds.has(id), `${id} should be in the full built-in catalog`).toBe(true);
|
||||||
expect(movedIds.has(id), `${id} should not be in the moved-key catalog`).toBe(false);
|
expect(movedIds.has(id), `${id} should not be in the moved-key catalog`).toBe(false);
|
||||||
|
expect(movedKeyIds.has(id), `${id} should not be in MOVED_SETTINGS_KEYS`).toBe(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -204,6 +204,219 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|||||||
- If you need to run \`pnpm install\` (e.g. you added a new package), use \`fn_run_verification\` with \`scope: "workspace"\` and \`timeoutSec: 600\`.
|
- If you need to run \`pnpm install\` (e.g. you added a new package), use \`fn_run_verification\` with \`scope: "workspace"\` and \`timeoutSec: 600\`.
|
||||||
- If a verification command times out, do NOT blindly retry — investigate. Check for hung subprocesses, infinite test loops, or tests waiting on missing dependencies. Use \`node_modules/.modules.yaml\` presence to confirm bootstrap.`;
|
- If a verification command times out, do NOT blindly retry — investigate. Check for hung subprocesses, infinite test loops, or tests waiting on missing dependencies. Use \`node_modules/.modules.yaml\` presence to confirm bootstrap.`;
|
||||||
|
|
||||||
|
const FAST_TRIAGE_PROMPT_TEXT = `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
|
||||||
|
You are a fast-path spec writer. Keep output lean but executable, with enough precision that an executor can run immediately.
|
||||||
|
|
||||||
|
Your job: turn a rough task description into a focused PROMPT.md another agent can execute autonomously.
|
||||||
|
|
||||||
|
## 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}
|
||||||
|
|
||||||
|
## Mission
|
||||||
|
|
||||||
|
{One paragraph: what to build 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 first})
|
||||||
|
|
||||||
|
## Context to Read First
|
||||||
|
|
||||||
|
{List the minimal, specific files needed for implementation}
|
||||||
|
|
||||||
|
## File Scope
|
||||||
|
|
||||||
|
{List exact files/directories expected to change}
|
||||||
|
|
||||||
|
- \`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: {Implementation step 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
|
||||||
|
- [ ] Build passes
|
||||||
|
|
||||||
|
### Step {N}: Documentation & Delivery
|
||||||
|
|
||||||
|
- [ ] Update relevant documentation
|
||||||
|
- [ ] Save documentation deliverables as task documents via \`fn_task_document_write\` (key="docs", content=...)
|
||||||
|
- [ ] Create out-of-scope follow-up tasks via \`fn_task_create\` when needed
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- Require real automated tests with assertions that run in the project's test runner
|
||||||
|
- Typecheck/build/manual checks are not tests and cannot replace tests
|
||||||
|
- 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\`.
|
||||||
|
- Include targeted tests in implementation steps and full quality-gate runs in final verification
|
||||||
|
|
||||||
|
## Duplicate check
|
||||||
|
Before writing a spec, call \`fn_task_list\` to find existing 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 an existing task already covers the same work, do NOT write a PROMPT.md. Instead write exactly:
|
||||||
|
\`DUPLICATE: {existing-task-id}\`
|
||||||
|
|
||||||
|
## Dependency awareness
|
||||||
|
When adding a dependency in \`## Dependencies\`, first call \`fn_task_get\` for that task and read its PROMPT.md.
|
||||||
|
Use that context to align file paths, APIs, assumptions, and completion expectations. If the dependency has no PROMPT.md yet, note that explicitly.
|
||||||
|
|
||||||
|
## Decision-only task flag (noCommitsExpected)
|
||||||
|
When ALL of the following are true, include this metadata line in the header block after Size:
|
||||||
|
|
||||||
|
- 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 relevant source files before writing the spec
|
||||||
|
- Be specific: reference concrete files, modules, and commands from this repo
|
||||||
|
- Keep steps outcome-focused with 2–4 checkboxes per step
|
||||||
|
- Keep file scope realistic: include tests and integration touchpoints likely required for green quality gates
|
||||||
|
- Always include Testing & Verification and Documentation & Delivery steps
|
||||||
|
- Keep fast-mode scope lean and executable; do not add heavyweight review scoring or subtask-analysis sections
|
||||||
|
|
||||||
|
## Project commands
|
||||||
|
When the user prompt includes explicit test/build commands, use those exact commands in the generated spec.
|
||||||
|
|
||||||
|
## Workflow Routing
|
||||||
|
Call \`fn_workflow_list\` and use workflow descriptions as the routing signal. For investigation/audit/research or decision-only tasks that meet the no-commits criteria above, include \`**No commits expected:** true\` in the PROMPT.md header and prefer \`builtin:quick-fix\` or a custom investigation workflow; standard coding tasks can stay on the default \`builtin:coding\`. Use \`fn_workflow_select\` for the current task or pass \`workflow_id\` to \`fn_task_create\` for subtasks.
|
||||||
|
|
||||||
|
## Task Artifact Location for Forensic / Reconciliation Tasks
|
||||||
|
|
||||||
|
For audit/forensic/historical reconciliation tasks that target a different task ID, explicitly state in generated PROMPT.md context/scope that authoritative artifacts and DB state are at project root, not the worktree.
|
||||||
|
- Target-task files live at \`<rootDir>/.fusion/tasks/{TARGET_ID}/\` (\`task.json\`, \`PROMPT.md\`, \`attachments/\`, logs).
|
||||||
|
- Task DB truth lives at \`<rootDir>/.fusion/fusion.db\` (SQLite/WAL) and should be accessed via \`TaskStore\`/task tools, not direct SQL edits.
|
||||||
|
- \`.fusion/\` is gitignored: fresh worktrees from \`main\` do not contain other tasks' \`.fusion/tasks/{TARGET_ID}/\` or \`.fusion/fusion.db\`; worktree-local \`.fusion/\` is running-task scratch/session state only.
|
||||||
|
|
||||||
|
## Spec Review
|
||||||
|
|
||||||
|
After writing the PROMPT.md, call \`fn_review_spec()\` to confirm the spec.
|
||||||
|
|
||||||
|
Fast-mode specs are auto-approved — the review tool will return APPROVE immediately without spawning an independent reviewer. You do NOT need to wait for or iterate on review feedback.
|
||||||
|
|
||||||
|
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()\` to confirm.`;
|
||||||
|
|
||||||
const TRIAGE_PROMPT_TEXT = `You are a task specification agent for "fn", an AI-orchestrated task board.
|
const TRIAGE_PROMPT_TEXT = `You are a task specification agent for "fn", an AI-orchestrated task board.
|
||||||
|
|
||||||
## Your Role
|
## Your Role
|
||||||
@@ -1125,6 +1338,14 @@ export const BUILTIN_AGENT_PROMPTS: readonly AgentPromptTemplate[] = [
|
|||||||
prompt: `${TRIAGE_PROMPT_TEXT}\n\n${TRIAGE_HEARTBEAT_GUIDANCE}`,
|
prompt: `${TRIAGE_PROMPT_TEXT}\n\n${TRIAGE_HEARTBEAT_GUIDANCE}`,
|
||||||
builtIn: true,
|
builtIn: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "default-triage-fast",
|
||||||
|
name: "Default Triage (Fast)",
|
||||||
|
description: "Lean fast-path task specification agent producing executable PROMPT.md files without heavyweight review scoring.",
|
||||||
|
role: "triage",
|
||||||
|
prompt: FAST_TRIAGE_PROMPT_TEXT,
|
||||||
|
builtIn: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "default-reviewer",
|
id: "default-reviewer",
|
||||||
name: "Default Reviewer",
|
name: "Default Reviewer",
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
import { BUILTIN_AGENT_PROMPTS } from "./agent-prompts.js";
|
import { BUILTIN_AGENT_PROMPTS } from "./agent-prompts.js";
|
||||||
|
|
||||||
const DEFAULT_EXECUTOR_PROMPT = BUILTIN_AGENT_PROMPTS.find((prompt) => prompt.role === "executor")?.prompt ?? "";
|
const DEFAULT_EXECUTOR_PROMPT = BUILTIN_AGENT_PROMPTS.find((prompt) => prompt.role === "executor")?.prompt ?? "";
|
||||||
const DEFAULT_TRIAGE_PROMPT = BUILTIN_AGENT_PROMPTS.find((prompt) => prompt.role === "triage")?.prompt ?? "";
|
const DEFAULT_TRIAGE_PROMPT = BUILTIN_AGENT_PROMPTS.find((prompt) => prompt.id === "default-triage")?.prompt ?? "";
|
||||||
|
const DEFAULT_TRIAGE_FAST_PROMPT = BUILTIN_AGENT_PROMPTS.find((prompt) => prompt.id === "default-triage-fast")?.prompt ?? "";
|
||||||
const DEFAULT_REVIEWER_PROMPT = BUILTIN_AGENT_PROMPTS.find((prompt) => prompt.role === "reviewer")?.prompt ?? "";
|
const DEFAULT_REVIEWER_PROMPT = BUILTIN_AGENT_PROMPTS.find((prompt) => prompt.role === "reviewer")?.prompt ?? "";
|
||||||
const DEFAULT_MERGER_PROMPT = BUILTIN_AGENT_PROMPTS.find((prompt) => prompt.role === "merger")?.prompt ?? "";
|
const DEFAULT_MERGER_PROMPT = BUILTIN_AGENT_PROMPTS.find((prompt) => prompt.role === "merger")?.prompt ?? "";
|
||||||
|
|
||||||
const BUILTIN_SEAM_PROMPTS: Record<string, string> = {
|
export const BUILTIN_SEAM_PROMPTS: Record<string, string> = {
|
||||||
execute: DEFAULT_EXECUTOR_PROMPT,
|
execute: DEFAULT_EXECUTOR_PROMPT,
|
||||||
planning: DEFAULT_TRIAGE_PROMPT,
|
planning: DEFAULT_TRIAGE_PROMPT,
|
||||||
|
"planning-fast": DEFAULT_TRIAGE_FAST_PROMPT,
|
||||||
"step-execute": DEFAULT_EXECUTOR_PROMPT,
|
"step-execute": DEFAULT_EXECUTOR_PROMPT,
|
||||||
"workflow-step": DEFAULT_REVIEWER_PROMPT,
|
"workflow-step": DEFAULT_REVIEWER_PROMPT,
|
||||||
review: DEFAULT_REVIEWER_PROMPT,
|
review: DEFAULT_REVIEWER_PROMPT,
|
||||||
merge: DEFAULT_MERGER_PROMPT,
|
merge: DEFAULT_MERGER_PROMPT,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function builtinPromptConfig(seam: string, name: string): Record<string, unknown> {
|
export function builtinSeamPrompt(seam: string): string {
|
||||||
return { seam, name, prompt: BUILTIN_SEAM_PROMPTS[seam] ?? "" };
|
return BUILTIN_SEAM_PROMPTS[seam] ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function builtinPromptConfig(seam: string, name: string): Record<string, unknown> {
|
||||||
|
return { seam, name, prompt: builtinSeamPrompt(seam) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import type { WorkflowSettingDefinition } from "./workflow-ir-types.js";
|
|||||||
* hard-move migration, must never be added to `MOVED_SETTINGS_KEYS`, and must
|
* hard-move migration, must never be added to `MOVED_SETTINGS_KEYS`, and must
|
||||||
* not appear in project/global settings schemas. Canonical values are inherited
|
* not appear in project/global settings schemas. Canonical values are inherited
|
||||||
* from the post-FN-6232 planning prompt: subtask step threshold `7` (not the
|
* from the post-FN-6232 planning prompt: subtask step threshold `7` (not the
|
||||||
* older engine copy) and packages/modules threshold `3`.
|
* older engine copy) and packages/modules threshold `3`. Fast-mode policy is
|
||||||
|
* workflow-native here too: `leanPlanning` selects the lean planning variant,
|
||||||
|
* and `autoApproveSpec` skips the independent spec reviewer.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -349,6 +351,20 @@ export const BUILTIN_TRIAGE_POLICY_SETTINGS: WorkflowSettingDefinition[] = [
|
|||||||
],
|
],
|
||||||
description: "Default workflow id for standard coding tasks.",
|
description: "Default workflow id for standard coding tasks.",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "leanPlanning",
|
||||||
|
name: "Lean planning",
|
||||||
|
type: "boolean",
|
||||||
|
default: false,
|
||||||
|
description: "Use the lean fast-path planning prompt variant instead of the full triage spec prompt.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "autoApproveSpec",
|
||||||
|
name: "Auto-approve spec",
|
||||||
|
type: "boolean",
|
||||||
|
default: false,
|
||||||
|
description: "Auto-approve the generated PROMPT.md and skip the independent spec reviewer.",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const BUILTIN_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [
|
export const BUILTIN_WORKFLOW_SETTINGS: WorkflowSettingDefinition[] = [
|
||||||
|
|||||||
@@ -112,6 +112,11 @@ export {
|
|||||||
BUILTIN_TRIAGE_POLICY_SETTINGS,
|
BUILTIN_TRIAGE_POLICY_SETTINGS,
|
||||||
renderTriagePolicyPlaceholders,
|
renderTriagePolicyPlaceholders,
|
||||||
} from "./builtin-workflow-settings.js";
|
} from "./builtin-workflow-settings.js";
|
||||||
|
export {
|
||||||
|
BUILTIN_SEAM_PROMPTS,
|
||||||
|
builtinPromptConfig,
|
||||||
|
builtinSeamPrompt,
|
||||||
|
} from "./builtin-workflow-prompts.js";
|
||||||
export {
|
export {
|
||||||
MOVED_SETTINGS_KEYS,
|
MOVED_SETTINGS_KEYS,
|
||||||
SETTINGS_MIGRATION_VERSION,
|
SETTINGS_MIGRATION_VERSION,
|
||||||
|
|||||||
@@ -4151,6 +4151,10 @@ export interface Settings extends GlobalSettings, ProjectSettings {
|
|||||||
/** Whether PR authentication is currently available (read-only, set by server).
|
/** Whether PR authentication is currently available (read-only, set by server).
|
||||||
* True when authenticated gh CLI access is available or token fallback exists. */
|
* True when authenticated gh CLI access is available or token fallback exists. */
|
||||||
prAuthAvailable?: boolean;
|
prAuthAvailable?: boolean;
|
||||||
|
/** Use the lean fast-path planning prompt variant instead of the full triage spec prompt. */
|
||||||
|
leanPlanning?: boolean;
|
||||||
|
/** Auto-approve generated specs and skip the independent spec reviewer. */
|
||||||
|
autoApproveSpec?: boolean;
|
||||||
/** Index signature for dynamic settings access */
|
/** Index signature for dynamic settings access */
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { describe, it, expect, vi } from "vitest";
|
import { describe, it, expect, vi } from "vitest";
|
||||||
import { resolveAgentPrompt } from "@fusion/core";
|
import { builtinSeamPrompt, resolveAgentPrompt } from "@fusion/core";
|
||||||
import {
|
import { TriageProcessor } from "../triage.js";
|
||||||
FAST_TRIAGE_SYSTEM_PROMPT,
|
|
||||||
TriageProcessor,
|
|
||||||
} from "../triage.js";
|
|
||||||
import { createTriageDuplicateScenario } from "./fixtures/triage-duplicate-scenario.js";
|
import { createTriageDuplicateScenario } from "./fixtures/triage-duplicate-scenario.js";
|
||||||
|
|
||||||
const { mockReviewStep, mockCreateFnAgent } = vi.hoisted(() => ({
|
const { mockReviewStep, mockCreateFnAgent } = vi.hoisted(() => ({
|
||||||
@@ -30,12 +27,13 @@ vi.mock("@fusion/core", async (importOriginal) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const TRIAGE_POLICY_PROMPT = resolveAgentPrompt("triage");
|
const TRIAGE_POLICY_PROMPT = resolveAgentPrompt("triage");
|
||||||
|
const FAST_PLANNING_PROMPT = builtinSeamPrompt("planning-fast");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FN-4726 / FN-4734 / FN-4741: triage created repeated duplicate tasks after equivalent
|
* 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,
|
* work had already landed. FN-4774 fixed this by (1) exposing fn_task_search in triage,
|
||||||
* (2) guiding the canonical triage policy 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.
|
* (3) preserving that guidance in FAST_PLANNING_PROMPT. FN-4815 pins this contract.
|
||||||
*/
|
*/
|
||||||
describe("FN-4815 triage duplicate-search regression", () => {
|
describe("FN-4815 triage duplicate-search regression", () => {
|
||||||
it("toolset contract: createTriageTools includes fn_task_search", () => {
|
it("toolset contract: createTriageTools includes fn_task_search", () => {
|
||||||
@@ -60,10 +58,10 @@ describe("FN-4815 triage duplicate-search regression", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("fast prompt guidance keeps duplicate-search instructions", () => {
|
it("fast prompt guidance keeps duplicate-search instructions", () => {
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("Duplicate check");
|
expect(FAST_PLANNING_PROMPT).toContain("Duplicate check");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("fn_task_search");
|
expect(FAST_PLANNING_PROMPT).toContain("fn_task_search");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("For any likely match in `done` or `archived`");
|
expect(FAST_PLANNING_PROMPT).toContain("For any likely match in `done` or `archived`");
|
||||||
expect(/Duplicate check[\s\S]{0,700}(done|archived)/i.test(FAST_TRIAGE_SYSTEM_PROMPT)).toBe(true);
|
expect(/Duplicate check[\s\S]{0,700}(done|archived)/i.test(FAST_PLANNING_PROMPT)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("end-to-end duplicate discovery via fixture shows done match before create", async () => {
|
it("end-to-end duplicate discovery via fixture shows done match before create", async () => {
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import type { Settings, Task, TaskDetail, TaskStore } from "@fusion/core";
|
||||||
|
import {
|
||||||
|
BUILTIN_CODING_WORKFLOW_IR,
|
||||||
|
builtinSeamPrompt,
|
||||||
|
renderTriagePolicyPlaceholders,
|
||||||
|
resolvePlanningPromptFromIr,
|
||||||
|
} from "@fusion/core";
|
||||||
|
import { TriageProcessor } from "../triage.js";
|
||||||
|
|
||||||
|
const { mockReviewStep, mockCreateFnAgent, mockPromptWithFallback } = vi.hoisted(() => ({
|
||||||
|
mockReviewStep: vi.fn(),
|
||||||
|
mockCreateFnAgent: vi.fn(),
|
||||||
|
mockPromptWithFallback: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../reviewer.js", () => ({
|
||||||
|
reviewStep: mockReviewStep,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../pi.js", () => ({
|
||||||
|
createFnAgent: mockCreateFnAgent,
|
||||||
|
describeModel: vi.fn().mockReturnValue("mock-model"),
|
||||||
|
promptWithFallback: mockPromptWithFallback,
|
||||||
|
}));
|
||||||
|
|
||||||
|
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-6236-T",
|
||||||
|
description: "Fast workflow variant regression",
|
||||||
|
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, settings: Partial<Settings> = {}, overrides: Partial<TaskStore> = {}): 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({ workflowId: "builtin:coding", stepIds: [] }),
|
||||||
|
getWorkflowDefinition: vi.fn().mockResolvedValue(undefined),
|
||||||
|
getWorkflowSettingValues: vi.fn().mockResolvedValue({}),
|
||||||
|
getWorkflowSettingsProjectId: vi.fn().mockReturnValue("default"),
|
||||||
|
on: vi.fn(),
|
||||||
|
emit: vi.fn(),
|
||||||
|
...overrides,
|
||||||
|
} as unknown as TaskStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockSession(capture: { basePrompt?: string; customTools?: any[] } = {}) {
|
||||||
|
mockCreateFnAgent.mockImplementationOnce(async (opts: any) => {
|
||||||
|
capture.basePrompt = opts.systemPromptLayers?.stable ?? opts.systemPrompt;
|
||||||
|
capture.customTools = opts.customTools;
|
||||||
|
return {
|
||||||
|
session: {
|
||||||
|
state: {},
|
||||||
|
sessionManager: { getLeafId: vi.fn().mockReturnValue(null) },
|
||||||
|
prompt: vi.fn().mockResolvedValue(undefined),
|
||||||
|
dispose: vi.fn(),
|
||||||
|
navigateTree: vi.fn(),
|
||||||
|
__customTools: opts.customTools,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function captureBasePrompt(task: Task, store: TaskStore): Promise<string> {
|
||||||
|
const capture: { basePrompt?: string } = {};
|
||||||
|
mockSession(capture);
|
||||||
|
mockPromptWithFallback.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
|
await new TriageProcessor(store, "/tmp/root").specifyTask(task);
|
||||||
|
return capture.basePrompt ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runReviewSpec(task: Task, store: TaskStore, rootDir: string): Promise<void> {
|
||||||
|
mockSession();
|
||||||
|
mockPromptWithFallback.mockImplementationOnce(async (session: any) => {
|
||||||
|
const promptPath = join(rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
|
||||||
|
await mkdir(join(rootDir, ".fusion", "tasks", task.id), { recursive: true });
|
||||||
|
await writeFile(promptPath, "# Task: FN-6236\n\n## Mission\n\nVerify fast policy.\n", "utf8");
|
||||||
|
const reviewSpec = session.__customTools.find((tool: any) => tool.name === "fn_review_spec");
|
||||||
|
await reviewSpec.execute();
|
||||||
|
});
|
||||||
|
|
||||||
|
await new TriageProcessor(store, rootDir).specifyTask(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderedFastPlanningPrompt = renderTriagePolicyPlaceholders(builtinSeamPrompt("planning-fast"), {});
|
||||||
|
const renderedStandardPlanningPrompt = renderTriagePolicyPlaceholders(
|
||||||
|
resolvePlanningPromptFromIr(BUILTIN_CODING_WORKFLOW_IR)!,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("fast-mode workflow variant resolution", () => {
|
||||||
|
let tempRoots: string[] = [];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockReviewStep.mockResolvedValue({ verdict: "APPROVE", summary: "ok", review: "" });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await Promise.all(tempRoots.map((root) => rm(root, { recursive: true, force: true })));
|
||||||
|
tempRoots = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves fast tasks to the lean planning-fast workflow prompt", async () => {
|
||||||
|
const task = createTask({ id: "FN-6236-FAST-PROMPT", executionMode: "fast" });
|
||||||
|
const store = createStore(task);
|
||||||
|
|
||||||
|
await expect(captureBasePrompt(task, store)).resolves.toBe(renderedFastPlanningPrompt);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves standard tasks to the standard workflow planning prompt", async () => {
|
||||||
|
const task = createTask({ id: "FN-6236-STANDARD-PROMPT", executionMode: "standard" });
|
||||||
|
const store = createStore(task);
|
||||||
|
|
||||||
|
const basePrompt = await captureBasePrompt(task, store);
|
||||||
|
|
||||||
|
expect(basePrompt).toBe(renderedStandardPlanningPrompt);
|
||||||
|
expect(basePrompt).not.toBe(renderedFastPlanningPrompt);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("auto-approves fast tasks without invoking the reviewer", async () => {
|
||||||
|
const rootDir = await mkdtemp(join(tmpdir(), "fusion-fn-6236-fast-"));
|
||||||
|
tempRoots.push(rootDir);
|
||||||
|
const task = createTask({ id: "FN-6236-FAST-REVIEW", executionMode: "fast" });
|
||||||
|
const store = createStore(task);
|
||||||
|
|
||||||
|
await runReviewSpec(task, store, rootDir);
|
||||||
|
|
||||||
|
expect(mockReviewStep).not.toHaveBeenCalled();
|
||||||
|
expect(store.logEntry).toHaveBeenCalledWith(task.id, "Spec review: APPROVE (auto-approve spec)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("invokes the reviewer for standard tasks without autoApproveSpec", async () => {
|
||||||
|
const rootDir = await mkdtemp(join(tmpdir(), "fusion-fn-6236-standard-"));
|
||||||
|
tempRoots.push(rootDir);
|
||||||
|
const task = createTask({ id: "FN-6236-STANDARD-REVIEW", executionMode: "standard" });
|
||||||
|
const store = createStore(task);
|
||||||
|
|
||||||
|
await runReviewSpec(task, store, rootDir);
|
||||||
|
|
||||||
|
expect(mockReviewStep).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("auto-approves standard tasks when the workflow setting is enabled", async () => {
|
||||||
|
const rootDir = await mkdtemp(join(tmpdir(), "fusion-fn-6236-setting-"));
|
||||||
|
tempRoots.push(rootDir);
|
||||||
|
const task = createTask({ id: "FN-6236-SETTING-REVIEW", executionMode: "standard" });
|
||||||
|
const store = createStore(task, { autoApproveSpec: true });
|
||||||
|
|
||||||
|
await runReviewSpec(task, store, rootDir);
|
||||||
|
|
||||||
|
expect(mockReviewStep).not.toHaveBeenCalled();
|
||||||
|
expect(store.logEntry).toHaveBeenCalledWith(task.id, "Spec review: APPROVE (auto-approve spec)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves user triage prompt override precedence over the fast variant", async () => {
|
||||||
|
const task = createTask({ id: "FN-6236-OVERRIDE", executionMode: "fast" });
|
||||||
|
const overridePrompt = "custom fast 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,11 +2,12 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
|||||||
import type { Settings, Task, TaskDetail, TaskStore, WorkflowIr } from "@fusion/core";
|
import type { Settings, Task, TaskDetail, TaskStore, WorkflowIr } from "@fusion/core";
|
||||||
import {
|
import {
|
||||||
BUILTIN_CODING_WORKFLOW_IR,
|
BUILTIN_CODING_WORKFLOW_IR,
|
||||||
|
builtinSeamPrompt,
|
||||||
renderTriagePolicyPlaceholders,
|
renderTriagePolicyPlaceholders,
|
||||||
resolveAgentPrompt,
|
resolveAgentPrompt,
|
||||||
resolvePlanningPromptFromIr,
|
resolvePlanningPromptFromIr,
|
||||||
} from "@fusion/core";
|
} from "@fusion/core";
|
||||||
import { FAST_TRIAGE_SYSTEM_PROMPT, TriageProcessor } from "../triage.js";
|
import { TriageProcessor } from "../triage.js";
|
||||||
|
|
||||||
const { mockReviewStep, mockCreateFnAgent } = vi.hoisted(() => ({
|
const { mockReviewStep, mockCreateFnAgent } = vi.hoisted(() => ({
|
||||||
mockReviewStep: vi.fn(),
|
mockReviewStep: vi.fn(),
|
||||||
@@ -110,6 +111,7 @@ async function captureBasePrompt(task: Task, store: TaskStore): Promise<string>
|
|||||||
const canonicalPlanningPrompt = resolvePlanningPromptFromIr(BUILTIN_CODING_WORKFLOW_IR)!;
|
const canonicalPlanningPrompt = resolvePlanningPromptFromIr(BUILTIN_CODING_WORKFLOW_IR)!;
|
||||||
const renderedCanonicalPlanningPrompt = renderTriagePolicyPlaceholders(canonicalPlanningPrompt, {});
|
const renderedCanonicalPlanningPrompt = renderTriagePolicyPlaceholders(canonicalPlanningPrompt, {});
|
||||||
const renderedDefaultTriagePrompt = renderTriagePolicyPlaceholders(resolveAgentPrompt("triage"), {});
|
const renderedDefaultTriagePrompt = renderTriagePolicyPlaceholders(resolveAgentPrompt("triage"), {});
|
||||||
|
const renderedFastPlanningPrompt = renderTriagePolicyPlaceholders(builtinSeamPrompt("planning-fast"), {});
|
||||||
|
|
||||||
describe("triage planning prompt single source", () => {
|
describe("triage planning prompt single source", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -145,11 +147,11 @@ describe("triage planning prompt single source", () => {
|
|||||||
await expect(captureBasePrompt(task, store)).resolves.toBe(overridePrompt);
|
await expect(captureBasePrompt(task, store)).resolves.toBe(overridePrompt);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps fast mode on FAST_TRIAGE_SYSTEM_PROMPT", async () => {
|
it("keeps fast mode on the planning-fast workflow prompt", async () => {
|
||||||
const task = createTask({ id: "FN-6232-FAST", executionMode: "fast" });
|
const task = createTask({ id: "FN-6232-FAST", executionMode: "fast" });
|
||||||
const store = createStore(task);
|
const store = createStore(task);
|
||||||
|
|
||||||
await expect(captureBasePrompt(task, store)).resolves.toBe(FAST_TRIAGE_SYSTEM_PROMPT);
|
await expect(captureBasePrompt(task, store)).resolves.toBe(renderedFastPlanningPrompt);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses a selected custom workflow planning prompt", async () => {
|
it("uses a selected custom workflow planning prompt", async () => {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
import type { TaskStore, Task, TaskDetail, Settings } from "@fusion/core";
|
import type { TaskStore, Task, TaskDetail, Settings } from "@fusion/core";
|
||||||
import { renderTriagePolicyPlaceholders, resolveAgentPrompt } from "@fusion/core";
|
import { builtinSeamPrompt, renderTriagePolicyPlaceholders, resolveAgentPrompt } from "@fusion/core";
|
||||||
import {
|
import {
|
||||||
TriageProcessor,
|
TriageProcessor,
|
||||||
FAST_TRIAGE_SYSTEM_PROMPT,
|
|
||||||
buildSpecificationPrompt,
|
buildSpecificationPrompt,
|
||||||
readAttachmentContents,
|
readAttachmentContents,
|
||||||
computeUserCommentFingerprint,
|
computeUserCommentFingerprint,
|
||||||
@@ -22,6 +21,7 @@ const { mockReviewStep, mockCreateFnAgent } = vi.hoisted(() => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const TRIAGE_POLICY_PROMPT = resolveAgentPrompt("triage");
|
const TRIAGE_POLICY_PROMPT = resolveAgentPrompt("triage");
|
||||||
|
const FAST_PLANNING_PROMPT = builtinSeamPrompt("planning-fast");
|
||||||
const RENDERED_TRIAGE_POLICY_PROMPT = renderTriagePolicyPlaceholders(TRIAGE_POLICY_PROMPT, {});
|
const RENDERED_TRIAGE_POLICY_PROMPT = renderTriagePolicyPlaceholders(TRIAGE_POLICY_PROMPT, {});
|
||||||
|
|
||||||
vi.mock("../reviewer.js", () => ({
|
vi.mock("../reviewer.js", () => ({
|
||||||
@@ -661,7 +661,7 @@ describe("FN-5893 invariant regression wording", () => {
|
|||||||
it("requires invariant-level regression coverage in standard, fast, and core triage prompts", () => {
|
it("requires invariant-level regression coverage in standard, fast, and core triage prompts", () => {
|
||||||
for (const prompt of [
|
for (const prompt of [
|
||||||
TRIAGE_POLICY_PROMPT,
|
TRIAGE_POLICY_PROMPT,
|
||||||
FAST_TRIAGE_SYSTEM_PROMPT,
|
FAST_PLANNING_PROMPT,
|
||||||
corePromptSource,
|
corePromptSource,
|
||||||
]) {
|
]) {
|
||||||
expect(prompt).toContain("invariant across all known surfaces");
|
expect(prompt).toContain("invariant across all known surfaces");
|
||||||
@@ -677,7 +677,7 @@ describe("FN-5893 invariant regression wording", () => {
|
|||||||
const missingSectionRevisePattern =
|
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 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_POLICY_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_PLANNING_PROMPT]) {
|
||||||
expect(prompt).toContain("## Surface Enumeration");
|
expect(prompt).toContain("## Surface Enumeration");
|
||||||
expect(prompt).toMatch(missingSectionRevisePattern);
|
expect(prompt).toMatch(missingSectionRevisePattern);
|
||||||
expect(prompt).toContain("docs/testing.md");
|
expect(prompt).toContain("docs/testing.md");
|
||||||
@@ -699,7 +699,7 @@ describe("FN-5893 invariant regression wording", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("requires implementation-step testing guidance to enumerate invariant surfaces in standard and fast prompts", () => {
|
it("requires implementation-step testing guidance to enumerate invariant surfaces in standard and fast prompts", () => {
|
||||||
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_PLANNING_PROMPT]) {
|
||||||
expect(prompt).toContain(
|
expect(prompt).toContain(
|
||||||
"Run targeted tests for changed files, asserting the invariant across all known surfaces",
|
"Run targeted tests for changed files, asserting the invariant across all known surfaces",
|
||||||
);
|
);
|
||||||
@@ -710,7 +710,7 @@ describe("FN-5893 invariant regression wording", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("defines the FN-6229 Symptom Verification contract in standard and fast prompts", () => {
|
it("defines the FN-6229 Symptom Verification contract in standard and fast prompts", () => {
|
||||||
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_PLANNING_PROMPT]) {
|
||||||
expect(prompt).toContain("## Symptom Verification");
|
expect(prompt).toContain("## Symptom Verification");
|
||||||
expect(prompt).toContain("Use the exact heading `## Symptom Verification`");
|
expect(prompt).toContain("Use the exact heading `## Symptom Verification`");
|
||||||
expect(prompt).toContain("**Original symptom** — what the user/issue reported was broken");
|
expect(prompt).toContain("**Original symptom** — what the user/issue reported was broken");
|
||||||
@@ -725,7 +725,7 @@ describe("FN-5893 invariant regression wording", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("requires Surface Enumeration for UI-affordance add/remove tasks regardless of review-level analysis", () => {
|
it("requires Surface Enumeration for UI-affordance add/remove tasks regardless of review-level analysis", () => {
|
||||||
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
for (const prompt of [TRIAGE_POLICY_PROMPT, FAST_PLANNING_PROMPT]) {
|
||||||
expect(prompt).toContain("bug-fix tasks and UI-affordance add/remove tasks");
|
expect(prompt).toContain("bug-fix tasks and UI-affordance add/remove tasks");
|
||||||
expect(prompt).toContain("every component that renders the affordance");
|
expect(prompt).toContain("every component that renders the affordance");
|
||||||
expect(prompt).toContain("searching the codebase for the icon/class/testid");
|
expect(prompt).toContain("searching the codebase for the icon/class/testid");
|
||||||
@@ -733,7 +733,7 @@ describe("FN-5893 invariant regression wording", () => {
|
|||||||
expect(prompt).toContain("empty buttons");
|
expect(prompt).toContain("empty buttons");
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).not.toContain("## Review Level");
|
expect(FAST_PLANNING_PROMPT).not.toContain("## Review Level");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("pins the canonical docs checklist heading", () => {
|
it("pins the canonical docs checklist heading", () => {
|
||||||
@@ -748,19 +748,19 @@ describe("FN-5893 invariant regression wording", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("fast-mode triage", () => {
|
describe("fast-mode triage", () => {
|
||||||
it("exports a lean FAST_TRIAGE_SYSTEM_PROMPT", () => {
|
it("exports a lean FAST_PLANNING_PROMPT", () => {
|
||||||
expect(typeof FAST_TRIAGE_SYSTEM_PROMPT).toBe("string");
|
expect(typeof FAST_PLANNING_PROMPT).toBe("string");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT.length).toBeGreaterThan(0);
|
expect(FAST_PLANNING_PROMPT.length).toBeGreaterThan(0);
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("This task is running in **fast mode**");
|
expect(FAST_PLANNING_PROMPT).toContain("This task is running in **fast mode**");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("fn_review_spec()");
|
expect(FAST_PLANNING_PROMPT).toContain("fn_review_spec()");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).not.toContain("## Review Level");
|
expect(FAST_PLANNING_PROMPT).not.toContain("## Review Level");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).not.toContain("## Triage subtask breakdown");
|
expect(FAST_PLANNING_PROMPT).not.toContain("## Triage subtask breakdown");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).not.toContain("## Proactive Subtask Breakdown");
|
expect(FAST_PLANNING_PROMPT).not.toContain("## Proactive Subtask Breakdown");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).not.toContain("Frontend UX Criteria");
|
expect(FAST_PLANNING_PROMPT).not.toContain("Frontend UX Criteria");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("documents workflow routing in standard and fast prompts", () => {
|
it("documents workflow routing in standard and fast prompts", () => {
|
||||||
for (const prompt of [RENDERED_TRIAGE_POLICY_PROMPT, FAST_TRIAGE_SYSTEM_PROMPT]) {
|
for (const prompt of [RENDERED_TRIAGE_POLICY_PROMPT, FAST_PLANNING_PROMPT]) {
|
||||||
expect(prompt).toContain("## Workflow Routing");
|
expect(prompt).toContain("## Workflow Routing");
|
||||||
expect(prompt).toContain("fn_workflow_list");
|
expect(prompt).toContain("fn_workflow_list");
|
||||||
expect(prompt).toContain("fn_workflow_select");
|
expect(prompt).toContain("fn_workflow_select");
|
||||||
@@ -772,14 +772,14 @@ describe("fast-mode triage", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("includes task-artifact location guidance for forensic/reconciliation tasks", () => {
|
it("includes task-artifact location guidance for forensic/reconciliation tasks", () => {
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("Task Artifact Location");
|
expect(FAST_PLANNING_PROMPT).toContain("Task Artifact Location");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("<rootDir>/.fusion/tasks/{TARGET_ID}/");
|
expect(FAST_PLANNING_PROMPT).toContain("<rootDir>/.fusion/tasks/{TARGET_ID}/");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain(".fusion/fusion.db");
|
expect(FAST_PLANNING_PROMPT).toContain(".fusion/fusion.db");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("project root");
|
expect(FAST_PLANNING_PROMPT).toContain("project root");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("forensic");
|
expect(FAST_PLANNING_PROMPT).toContain("forensic");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("selects FAST_TRIAGE_SYSTEM_PROMPT for fast tasks", async () => {
|
it("selects FAST_PLANNING_PROMPT for fast tasks", async () => {
|
||||||
const task = createTriageTask({ id: "FN-FAST-001", executionMode: "fast" });
|
const task = createTriageTask({ id: "FN-FAST-001", executionMode: "fast" });
|
||||||
const store = createMockStore({
|
const store = createMockStore({
|
||||||
getTask: vi.fn().mockResolvedValue({ ...mockTaskDetail, id: task.id, attachments: [], comments: [] }),
|
getTask: vi.fn().mockResolvedValue({ ...mockTaskDetail, id: task.id, attachments: [], comments: [] }),
|
||||||
@@ -959,7 +959,7 @@ describe("fast-mode triage", () => {
|
|||||||
expect(mockReviewStep).not.toHaveBeenCalled();
|
expect(mockReviewStep).not.toHaveBeenCalled();
|
||||||
expect(verdictRef.current).toBe("APPROVE");
|
expect(verdictRef.current).toBe("APPROVE");
|
||||||
expect(result.content[0]?.text).toBe("APPROVE");
|
expect(result.content[0]?.text).toBe("APPROVE");
|
||||||
expect(store.logEntry).toHaveBeenCalledWith(taskId, "Spec review: APPROVE (auto, fast mode)");
|
expect(store.logEntry).toHaveBeenCalledWith(taskId, "Spec review: APPROVE (auto-approve spec)");
|
||||||
} finally {
|
} finally {
|
||||||
await cleanupTriageFixtureRoot(rootDir);
|
await cleanupTriageFixtureRoot(rootDir);
|
||||||
}
|
}
|
||||||
@@ -1018,7 +1018,7 @@ describe("fast-mode triage", () => {
|
|||||||
|
|
||||||
expect(mockReviewStep).not.toHaveBeenCalled();
|
expect(mockReviewStep).not.toHaveBeenCalled();
|
||||||
expect(store.moveTask).toHaveBeenCalledWith("FN-FAST-004", "todo");
|
expect(store.moveTask).toHaveBeenCalledWith("FN-FAST-004", "todo");
|
||||||
expect(store.logEntry).toHaveBeenCalledWith("FN-FAST-004", "Spec review: APPROVE (auto, fast mode)");
|
expect(store.logEntry).toHaveBeenCalledWith("FN-FAST-004", "Spec review: APPROVE (auto-approve spec)");
|
||||||
} finally {
|
} finally {
|
||||||
await cleanupTriageFixtureRoot(rootDir);
|
await cleanupTriageFixtureRoot(rootDir);
|
||||||
}
|
}
|
||||||
@@ -1789,7 +1789,7 @@ describe("approved triage recovery", () => {
|
|||||||
expect(TRIAGE_POLICY_PROMPT).toContain("**No commits expected:** true");
|
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("Decide whether FN-XYZ needs a fix");
|
||||||
expect(TRIAGE_POLICY_PROMPT).toContain("Investigate FN-XYZ and fix if needed");
|
expect(TRIAGE_POLICY_PROMPT).toContain("Investigate FN-XYZ and fix if needed");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("**No commits expected:** true");
|
expect(FAST_PLANNING_PROMPT).toContain("**No commits expected:** true");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("preserves imported GitHub issue titles during planning recovery", async () => {
|
it("preserves imported GitHub issue titles during planning recovery", async () => {
|
||||||
@@ -4433,16 +4433,16 @@ 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
|
// Regression: FN-4774 (FN-4827 recovery; supersedes FN-4815) — see docs/triage-duplicate-detection-postmortem.md
|
||||||
it("FAST_TRIAGE_SYSTEM_PROMPT guides agents to search done/archived before creating", () => {
|
it("FAST_PLANNING_PROMPT guides agents to search done/archived before creating", () => {
|
||||||
// Fast prompt mentions fn_task_search
|
// Fast prompt mentions fn_task_search
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("fn_task_search");
|
expect(FAST_PLANNING_PROMPT).toContain("fn_task_search");
|
||||||
// Duplicate-check section references done and archived
|
// Duplicate-check section references done and archived
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("done");
|
expect(FAST_PLANNING_PROMPT).toContain("done");
|
||||||
expect(FAST_TRIAGE_SYSTEM_PROMPT).toContain("archived");
|
expect(FAST_PLANNING_PROMPT).toContain("archived");
|
||||||
// Defensive regex: duplicate-check guidance must cross-reference fn_task_search with done/archived
|
// Defensive regex: duplicate-check guidance must cross-reference fn_task_search with done/archived
|
||||||
expect(
|
expect(
|
||||||
/Duplicate check[\s\S]{0,600}fn_task_search[\s\S]{0,400}(done|archived)/i.test(
|
/Duplicate check[\s\S]{0,600}fn_task_search[\s\S]{0,400}(done|archived)/i.test(
|
||||||
FAST_TRIAGE_SYSTEM_PROMPT,
|
FAST_PLANNING_PROMPT,
|
||||||
),
|
),
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ import {
|
|||||||
getTaskDuplicateLineage,
|
getTaskDuplicateLineage,
|
||||||
parseExplicitDuplicateMarker,
|
parseExplicitDuplicateMarker,
|
||||||
resolveAgentPrompt,
|
resolveAgentPrompt,
|
||||||
|
builtinSeamPrompt,
|
||||||
renderTriagePolicyPlaceholders,
|
renderTriagePolicyPlaceholders,
|
||||||
resolveTaskPlanningPrompt,
|
resolveTaskPlanningPrompt,
|
||||||
|
resolveTaskSeamPrompt,
|
||||||
resolvePersistAgentThinkingLog,
|
resolvePersistAgentThinkingLog,
|
||||||
compareTaskPriority,
|
compareTaskPriority,
|
||||||
sortTasksByPriorityThenAgeAndId,
|
sortTasksByPriorityThenAgeAndId,
|
||||||
@@ -90,218 +92,6 @@ import { archiveAsGhostBug } from "./self-healing.js";
|
|||||||
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
|
import { createRunAuditor, generateSyntheticRunId } from "./run-audit.js";
|
||||||
import { resolveAndEmitGoalContext } from "./goal-injection-diagnostics.js";
|
import { resolveAndEmitGoalContext } from "./goal-injection-diagnostics.js";
|
||||||
|
|
||||||
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
|
|
||||||
You are a fast-path spec writer. Keep output lean but executable, with enough precision that an executor can run immediately.
|
|
||||||
|
|
||||||
Your job: turn a rough task description into a focused PROMPT.md another agent can execute autonomously.
|
|
||||||
|
|
||||||
## 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}
|
|
||||||
|
|
||||||
## Mission
|
|
||||||
|
|
||||||
{One paragraph: what to build 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 first})
|
|
||||||
|
|
||||||
## Context to Read First
|
|
||||||
|
|
||||||
{List the minimal, specific files needed for implementation}
|
|
||||||
|
|
||||||
## File Scope
|
|
||||||
|
|
||||||
{List exact files/directories expected to change}
|
|
||||||
|
|
||||||
- \`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: {Implementation step 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
|
|
||||||
- [ ] Build passes
|
|
||||||
|
|
||||||
### Step {N}: Documentation & Delivery
|
|
||||||
|
|
||||||
- [ ] Update relevant documentation
|
|
||||||
- [ ] Save documentation deliverables as task documents via \`fn_task_document_write\` (key="docs", content=...)
|
|
||||||
- [ ] Create out-of-scope follow-up tasks via \`fn_task_create\` when needed
|
|
||||||
|
|
||||||
## 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
|
|
||||||
- Require real automated tests with assertions that run in the project's test runner
|
|
||||||
- Typecheck/build/manual checks are not tests and cannot replace tests
|
|
||||||
- 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\`.
|
|
||||||
- Include targeted tests in implementation steps and full quality-gate runs in final verification
|
|
||||||
|
|
||||||
## Duplicate check
|
|
||||||
Before writing a spec, call \`fn_task_list\` to find existing 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 an existing task already covers the same work, do NOT write a PROMPT.md. Instead write exactly:
|
|
||||||
\`DUPLICATE: {existing-task-id}\`
|
|
||||||
|
|
||||||
## Dependency awareness
|
|
||||||
When adding a dependency in \`## Dependencies\`, first call \`fn_task_get\` for that task and read its PROMPT.md.
|
|
||||||
Use that context to align file paths, APIs, assumptions, and completion expectations. If the dependency has no PROMPT.md yet, note that explicitly.
|
|
||||||
|
|
||||||
## Decision-only task flag (noCommitsExpected)
|
|
||||||
When ALL of the following are true, include this metadata line in the header block after Size:
|
|
||||||
|
|
||||||
- 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 relevant source files before writing the spec
|
|
||||||
- Be specific: reference concrete files, modules, and commands from this repo
|
|
||||||
- Keep steps outcome-focused with 2–4 checkboxes per step
|
|
||||||
- Keep file scope realistic: include tests and integration touchpoints likely required for green quality gates
|
|
||||||
- Always include Testing & Verification and Documentation & Delivery steps
|
|
||||||
- Keep fast-mode scope lean and executable; do not add heavyweight review scoring or subtask-analysis sections
|
|
||||||
|
|
||||||
## Project commands
|
|
||||||
When the user prompt includes explicit test/build commands, use those exact commands in the generated spec.
|
|
||||||
|
|
||||||
## Workflow Routing
|
|
||||||
Call \`fn_workflow_list\` and use workflow descriptions as the routing signal. For investigation/audit/research or decision-only tasks that meet the no-commits criteria above, include \`**No commits expected:** true\` in the PROMPT.md header and prefer \`builtin:quick-fix\` or a custom investigation workflow; standard coding tasks can stay on the default \`builtin:coding\`. Use \`fn_workflow_select\` for the current task or pass \`workflow_id\` to \`fn_task_create\` for subtasks.
|
|
||||||
|
|
||||||
## Task Artifact Location for Forensic / Reconciliation Tasks
|
|
||||||
|
|
||||||
For audit/forensic/historical reconciliation tasks that target a different task ID, explicitly state in generated PROMPT.md context/scope that authoritative artifacts and DB state are at project root, not the worktree.
|
|
||||||
- Target-task files live at \`<rootDir>/.fusion/tasks/{TARGET_ID}/\` (\`task.json\`, \`PROMPT.md\`, \`attachments/\`, logs).
|
|
||||||
- Task DB truth lives at \`<rootDir>/.fusion/fusion.db\` (SQLite/WAL) and should be accessed via \`TaskStore\`/task tools, not direct SQL edits.
|
|
||||||
- \`.fusion/\` is gitignored: fresh worktrees from \`main\` do not contain other tasks' \`.fusion/tasks/{TARGET_ID}/\` or \`.fusion/fusion.db\`; worktree-local \`.fusion/\` is running-task scratch/session state only.
|
|
||||||
|
|
||||||
## Spec Review
|
|
||||||
|
|
||||||
After writing the PROMPT.md, call \`fn_review_spec()\` to confirm the spec.
|
|
||||||
|
|
||||||
Fast-mode specs are auto-approved — the review tool will return APPROVE immediately without spawning an independent reviewer. You do NOT need to wait for or iterate on review feedback.
|
|
||||||
|
|
||||||
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()\` to confirm.`;
|
|
||||||
|
|
||||||
export interface TriageProcessorOptions {
|
export interface TriageProcessorOptions {
|
||||||
pollIntervalMs?: number;
|
pollIntervalMs?: number;
|
||||||
@@ -824,6 +614,10 @@ export class TriageProcessor {
|
|||||||
const settings = await mergeEffectiveSettings(this.store, task, await this.store.getSettings());
|
const settings = await mergeEffectiveSettings(this.store, task, await this.store.getSettings());
|
||||||
const promptPath = `.fusion/tasks/${task.id}/PROMPT.md`;
|
const promptPath = `.fusion/tasks/${task.id}/PROMPT.md`;
|
||||||
const isFast = task.executionMode === "fast";
|
const isFast = task.executionMode === "fast";
|
||||||
|
// FN-6236: this is the only legacy executionMode="fast" bridge. Downstream
|
||||||
|
// triage policy reads resolved workflow flags instead of the raw string.
|
||||||
|
const leanPlanning = settings.leanPlanning === true || isFast;
|
||||||
|
const autoApproveSpec = settings.autoApproveSpec === true || isFast;
|
||||||
|
|
||||||
const agentWork = async () => {
|
const agentWork = async () => {
|
||||||
// Set status only after the semaphore slot has been acquired, so
|
// Set status only after the semaphore slot has been acquired, so
|
||||||
@@ -925,7 +719,7 @@ export class TriageProcessor {
|
|||||||
specReviewVerdictRef,
|
specReviewVerdictRef,
|
||||||
approvedCommentFingerprintRef,
|
approvedCommentFingerprintRef,
|
||||||
settings,
|
settings,
|
||||||
isFast,
|
autoApproveSpec,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -957,7 +751,7 @@ export class TriageProcessor {
|
|||||||
planLog.warn(`${task.id}: failed to resolve triage agent instructions, continuing with defaults: ${msg}`);
|
planLog.warn(`${task.id}: failed to resolve triage agent instructions, continuing with defaults: ${msg}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
planLog.log(`${task.id}: planning in ${isFast ? "fast" : "standard"} mode`);
|
planLog.log(`${task.id}: planning in ${leanPlanning ? "fast" : "standard"} mode`);
|
||||||
const triageIdentitySection = assignedAgent
|
const triageIdentitySection = assignedAgent
|
||||||
? `## Identity\n\nYou are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.title.trim()}` : ""} (agent ID: ${assignedAgent.id}, role: ${assignedAgent.role}).`
|
? `## Identity\n\nYou are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.title.trim()}` : ""} (agent ID: ${assignedAgent.id}, role: ${assignedAgent.role}).`
|
||||||
: "";
|
: "";
|
||||||
@@ -979,16 +773,21 @@ export class TriageProcessor {
|
|||||||
runContext: triageRunContext,
|
runContext: triageRunContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
const workflowPlanningPrompt = isFast
|
const workflowPlanningPrompt = leanPlanning
|
||||||
? undefined
|
? undefined
|
||||||
: await resolveTaskPlanningPrompt(this.store, task.id).catch(() => undefined);
|
: await resolveTaskPlanningPrompt(this.store, task.id).catch(() => undefined);
|
||||||
|
const workflowFastPlanningPrompt = leanPlanning
|
||||||
|
? await resolveTaskSeamPrompt(this.store, task.id, "planning-fast").catch(() => undefined)
|
||||||
|
: undefined;
|
||||||
// FN-6232: standard-mode built-in triage policy is sourced from the workflow IR planning node; the former engine duplicate was removed.
|
// 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
|
const userTriagePrompt = settings.agentPrompts?.roleAssignments?.triage
|
||||||
? resolveAgentPrompt("triage", settings.agentPrompts)
|
? resolveAgentPrompt("triage", settings.agentPrompts)
|
||||||
: "";
|
: "";
|
||||||
const defaultTriagePrompt = resolveAgentPrompt("triage");
|
const defaultTriagePrompt = resolveAgentPrompt("triage");
|
||||||
const resolvedBasePrompt = userTriagePrompt
|
const resolvedBasePrompt = userTriagePrompt
|
||||||
|| (isFast ? FAST_TRIAGE_SYSTEM_PROMPT : (workflowPlanningPrompt || defaultTriagePrompt));
|
|| (leanPlanning
|
||||||
|
? (workflowFastPlanningPrompt || builtinSeamPrompt("planning-fast") || defaultTriagePrompt)
|
||||||
|
: (workflowPlanningPrompt || defaultTriagePrompt));
|
||||||
// Apply the workflow-native triage policy renderer to both standard and
|
// Apply the workflow-native triage policy renderer to both standard and
|
||||||
// fast prompts. Fast mode currently has no policy placeholders, making
|
// fast prompts. Fast mode currently has no policy placeholders, making
|
||||||
// this a no-op there while still guaranteeing no dangling token leaks.
|
// this a no-op there while still guaranteeing no dangling token leaks.
|
||||||
@@ -1970,8 +1769,8 @@ export class TriageProcessor {
|
|||||||
approvedCommentFingerprintRef.current = currentUserComments.length > 0
|
approvedCommentFingerprintRef.current = currentUserComments.length > 0
|
||||||
? computeUserCommentFingerprint(currentUserComments)
|
? computeUserCommentFingerprint(currentUserComments)
|
||||||
: "";
|
: "";
|
||||||
planLog.log(`${taskId}: spec review auto-approved (fast mode)`);
|
planLog.log(`${taskId}: spec review auto-approved (auto-approve spec)`);
|
||||||
await store.logEntry(taskId, "Spec review: APPROVE (auto, fast mode)");
|
await store.logEntry(taskId, "Spec review: APPROVE (auto-approve spec)");
|
||||||
return { content: [{ type: "text" as const, text: "APPROVE" }], details: {} };
|
return { content: [{ type: "text" as const, text: "APPROVE" }], details: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user