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:
@@ -1974,15 +1974,23 @@ export class TriageProcessor {
|
||||
taskUpdates.reviewLevel = parseInt(reviewMatch[1], 10);
|
||||
}
|
||||
|
||||
// Apply non-title metadata first. The title is held back and applied AFTER
|
||||
// the column transition (see below) because store.updateTask regenerates
|
||||
// PROMPT.md when title/description change, and the triage-stub regen path
|
||||
// would overwrite the freshly-written specification while column='triage'.
|
||||
// The store now also guards that regen against real specs, but we keep this
|
||||
// ordering as defense in depth so a future change to the guard can't
|
||||
// resurrect the regression.
|
||||
const promptDeclaredTitle = extractPromptDeclaredTitle(written, task.id);
|
||||
if (promptDeclaredTitle) {
|
||||
taskUpdates.title = promptDeclaredTitle;
|
||||
}
|
||||
|
||||
await this.store.updateTask(task.id, taskUpdates);
|
||||
|
||||
if (settings.requirePlanApproval) {
|
||||
await this.store.updateTask(task.id, { status: "awaiting-approval" });
|
||||
const approvalUpdates: Record<string, unknown> = { status: "awaiting-approval" };
|
||||
if (promptDeclaredTitle) {
|
||||
approvalUpdates.title = promptDeclaredTitle;
|
||||
}
|
||||
await this.store.updateTask(task.id, approvalUpdates);
|
||||
await this.store.logEntry(
|
||||
task.id,
|
||||
options.recoveryLogAction ?? "Specification approved by AI — awaiting manual approval",
|
||||
@@ -1993,6 +2001,10 @@ export class TriageProcessor {
|
||||
|
||||
await this.store.moveTask(task.id, "todo");
|
||||
|
||||
if (promptDeclaredTitle) {
|
||||
await this.store.updateTask(task.id, { title: promptDeclaredTitle });
|
||||
}
|
||||
|
||||
if (options.recoveryLogAction) {
|
||||
await this.store.logEntry(task.id, options.recoveryLogAction);
|
||||
planLog.log(`✓ ${task.id} recovered and moved to todo`);
|
||||
|
||||
Reference in New Issue
Block a user