fix(triage): stop clobbering freshly-written PROMPT.md specs on title sync

`TriageProcessor.finalizeApprovedTask` (added in FN-3056) called
`store.updateTask({title})` while the task was still in column='triage',
which triggered a pre-existing regen path in `TaskStore.updateTask` that
overwrote the agent's just-written specification with the bootstrap stub
(`# {id}: {title}\n\n{description}\n`). Tasks shipped to `todo` (and
through to `done`) with empty 70–200 byte specs while the executor only
saw the original one-line user description. The same regen path also
silently dropped `## Review Level` / `## Frontend UX Criteria` and any
section outside a fixed whitelist whenever a non-triage task's title or
description was edited.

Replaces the regen with wrapper-shape-exact stub detection (compare to
the bytes `createTask` would have written for the pre-update title and
description) plus surgical edits for real specs: title changes splice
only the leading `# ...` heading, description changes rewrite only the
body of `## Mission`, and every other section is preserved verbatim.
`finalizeApprovedTask` now applies the prompt-declared title after
`moveTask("todo")` as defense in depth. New regression tests cover real
specs surviving title sync, long bootstrap stubs, stubs whose body
contains `##` markdown or `**Created:**` text, and the end-to-end
triage finalize sequence on a real `TaskStore`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-02 23:41:45 -07:00
parent cdb8539795
commit d73070c135
4 changed files with 359 additions and 73 deletions

View File

@@ -0,0 +1,16 @@
---
"@runfusion/fusion": patch
---
Fix triage finalization clobbering its own freshly-written PROMPT.md spec, and fix the older title/description-driven regen path silently dropping `## Review Level` / `## Frontend UX Criteria` and any other sections outside a fixed whitelist. Tasks have been shipping to `todo` (and through to `done`) with empty 70200 byte specs while the executor agent only saw the original one-line user description; tasks that survived that bug could still come out of triage with their review level reset to 0 and frontend guidance dropped.
**Root causes.**
- FN-3056 (May 2) added `taskUpdates.title = promptDeclaredTitle` to `TriageProcessor.finalizeApprovedTask` and called `store.updateTask(task.id, taskUpdates)` while `task.column` was still `'triage'`. A pre-existing block in `TaskStore.updateTask` rewrote PROMPT.md to the bootstrap stub `# {id}: {title}\n\n{description}\n` whenever title/description changed on a triage-column task, overwriting the agent's just-written 6 KB spec with a 150-byte stub before `moveTask` ran.
- The non-triage branch of the same regen block called `regeneratePrompt`, which rebuilt the file from a fixed section whitelist (`Dependencies`, `Steps`, `File Scope`, `Acceptance Criteria`, `Notifications`). Any section the triage prompt emits outside that whitelist — `## Review Level`, `## Frontend UX Criteria`, custom assessment scoring, anything ad-hoc — was silently dropped on every title or description edit.
**Fixes.**
- `packages/core/src/store.ts`: title/description sync is now wrapper-shape-exact, not content-inspecting. The bootstrap stub detector compares the on-disk file against the exact bytes `createTask` would have written for the *pre-update* title/description (shared `buildBootstrapPrompt` helper), so it never inspects the description body. This is robust to imported issue bodies that contain `## Repro`, `**Created:**`, etc. — earlier heuristic checks (size caps, `##` header presence, `**Created:**` / `**Size:**` markers) misclassified those as real specs. Stub files keep getting fully rewritten so the displayed title/description stay in sync. Real specs get surgical edits only: title changes splice the leading `# ...` heading line and preserve the existing heading style (triage's `# Task: {id} - {title}` vs createTask's `# {id}: {title}`); description changes rewrite only the body of `## Mission`, leaving every other section verbatim. Description-only edits with no `## Mission` section are a no-op rather than a wholesale rebuild. The `regeneratePrompt` whitelist function is removed.
- `packages/engine/src/triage.ts`: `finalizeApprovedTask` applies the prompt-declared title *after* `moveTask("todo")` so the column transition happens before any title-driven regen could fire — defense in depth alongside the store-level guard. The `requirePlanApproval` branch folds the title into its existing `awaiting-approval` update.
- New regression tests in `packages/core/src/__tests__/store.test.ts`: the original bug (real spec on a triage task survives a title change), the false-negative cases (long bootstrap stubs and stubs whose description body contains `##` markdown headings or `**Created:**` / `**Size:**` text are still detected and rewritten), the secondary regression (`## Review Level` and `## Frontend UX Criteria` survive a non-triage title edit), and an end-to-end test that mirrors the exact `TriageProcessor.finalizeApprovedTask` sequence (write spec → updateTask without title → moveTask("todo") → updateTask({title})) on a real `TaskStore` to catch any future regression along the actual finalize path.