feat(FN-4371): complete Step 4 — triage decision-only heuristic

Fusion-Task-Id: FN-4371
Fusion-Task-Lineage: 4175ba4c-b7cb-4f3a-87c5-75e51f06c0ff
This commit is contained in:
Fusion
2026-05-14 09:38:48 -07:00
committed by gsxdsm
parent 951d8f6fe9
commit b68a31e76c
2 changed files with 44 additions and 1 deletions

View File

@@ -1513,7 +1513,7 @@ describe("approved triage recovery", () => {
await mkdir(join(rootDir, ".fusion", "tasks", "FN-001"), { recursive: true }); await mkdir(join(rootDir, ".fusion", "tasks", "FN-001"), { recursive: true });
await writeFile( await writeFile(
join(rootDir, ".fusion", "tasks", "FN-001", "PROMPT.md"), join(rootDir, ".fusion", "tasks", "FN-001", "PROMPT.md"),
"# Task: FN-001\n\n**Size:** M\n\n## Review Level: 2\n\nRecovered specification", "# Task: FN-001\n\n**Size:** M\n\n**No commits expected:** true\n\n## Review Level: 2\n\nRecovered specification",
); );
}); });
@@ -1558,6 +1558,7 @@ describe("approved triage recovery", () => {
dependencies: ["FN-1247"], dependencies: ["FN-1247"],
size: "M", size: "M",
reviewLevel: 2, reviewLevel: 2,
noCommitsExpected: true,
}); });
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "todo"); expect(store.moveTask).toHaveBeenCalledWith("FN-001", "todo");
expect(store.logEntry).toHaveBeenCalledWith( expect(store.logEntry).toHaveBeenCalledWith(
@@ -1644,6 +1645,13 @@ 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(FAST_TRIAGE_SYSTEM_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 () => {
await writeFile( await writeFile(
join(rootDir, ".fusion", "tasks", "FN-001", "PROMPT.md"), join(rootDir, ".fusion", "tasks", "FN-001", "PROMPT.md"),

View File

@@ -250,6 +250,21 @@ When the planning conversation produces a structured plan, save it as a document
- Testing & Verification must run before Documentation & Delivery - Testing & Verification must run before Documentation & Delivery
- Avoid giant catch-all steps; split outcomes so execution can be verified incrementally - 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 ## Guidelines
- Read the project structure and relevant source files to understand context BEFORE writing - 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 - Check package.json/scripts and explicit project commands to align real lint/test/build/typecheck commands
@@ -451,6 +466,21 @@ If an existing task already covers the same work, do NOT write a PROMPT.md. Inst
When adding a dependency in \`## Dependencies\`, first call \`fn_task_get\` for that task and read its PROMPT.md. 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. 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 ## Guidelines
- Read relevant source files before writing the spec - Read relevant source files before writing the spec
- Be specific: reference concrete files, modules, and commands from this repo - Be specific: reference concrete files, modules, and commands from this repo
@@ -2085,6 +2115,11 @@ export class TriageProcessor {
taskUpdates.reviewLevel = parseInt(reviewMatch[1], 10); taskUpdates.reviewLevel = parseInt(reviewMatch[1], 10);
} }
const noCommitsExpectedMatch = written.match(/^\*\*No commits expected:\*\*\s*(true|yes)\b/im);
if (noCommitsExpectedMatch) {
taskUpdates.noCommitsExpected = true;
}
// Apply non-title metadata first. The title is held back and applied AFTER // Apply non-title metadata first. The title is held back and applied AFTER
// the column transition (see below) because store.updateTask regenerates // the column transition (see below) because store.updateTask regenerates
// PROMPT.md when title/description change, and the triage-stub regen path // PROMPT.md when title/description change, and the triage-stub regen path