feat(FN-3502): add comment-triggered retriage when task is in triage status

Merged four commits implementing comment-driven retriage: triage rules now respond to specific comment patterns (Step 1) and surface needs-replan feedback inputs in the UI (Step 2), with documentation for the new behavior and a bug fix restoring workspace typecheck defaults. Changes span the core ta

Fusion-Task-Id: FN-3502
This commit is contained in:
Fusion
2026-05-05 13:32:10 -07:00
committed by gsxdsm
parent 7d373286d9
commit 511c0c5442
8 changed files with 200 additions and 40 deletions

View File

@@ -5169,52 +5169,73 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
}
}
// Phase 3: Invalidate stale spec approval when a user comments on
// a triage task that is awaiting manual approval. The new comment
// means the spec is now stale and must be re-specified/re-reviewed.
// Phase 3: user comments on already-planned, non-executing work should
// trigger triage re-specification. This includes awaiting-approval
// invalidation and todo/triage tasks that have a real non-bootstrap spec.
// This remains best-effort: failures are logged for observability but
// never fail the comment add operation itself.
// Note: The `task` returned above reflects the state BEFORE this
// transition. Callers that need the post-transition status should
// re-read the task (e.g., via getTask).
if (
task.column === "triage"
&& task.status === "awaiting-approval"
&& author === "user"
) {
let invalidatedStatus = false;
if (author === "user" && (task.column === "todo" || task.column === "triage")) {
let hasRealPrompt = false;
try {
await this.updateTask(id, {
status: "needs-replan",
});
invalidatedStatus = true;
const promptPath = join(this.taskDir(id), "PROMPT.md");
if (existsSync(promptPath)) {
const prompt = await readFile(promptPath, "utf-8");
hasRealPrompt = !isBootstrapPromptStub(prompt, task.id, task.title, task.description);
}
} catch (err) {
storeLog.warn("Best-effort post-comment awaiting-approval invalidation failed", {
storeLog.warn("Best-effort post-comment re-triage prompt-read failed", {
...commentContextBase,
phase: "addComment:awaiting-approval-invalidation",
stage: "status-update",
nextStatus: "needs-replan",
phase: "addComment:retriage-prompt-read",
error: err instanceof Error ? err.message : String(err),
});
}
if (invalidatedStatus) {
const shouldInvalidateAwaitingApproval =
task.column === "triage" && task.status === "awaiting-approval";
const shouldRetriagePlannedTask = hasRealPrompt
&& (
task.column === "todo"
|| (task.column === "triage" && task.status !== "awaiting-approval")
);
if (shouldInvalidateAwaitingApproval || shouldRetriagePlannedTask) {
const phase = shouldInvalidateAwaitingApproval
? "addComment:awaiting-approval-invalidation"
: "addComment:planned-task-retriage";
const action = shouldInvalidateAwaitingApproval
? "User comment invalidated spec approval — task needs re-specification"
: "User comment requested re-specification of planned task";
let transitioned = false;
try {
await this.logEntry(
id,
`User comment invalidated spec approval — task needs re-specification`,
undefined,
runContext,
);
await this.updateTask(id, { status: "needs-replan" });
transitioned = true;
} catch (err) {
storeLog.warn("Best-effort post-comment awaiting-approval invalidation failed", {
storeLog.warn("Best-effort post-comment re-triage failed", {
...commentContextBase,
phase: "addComment:awaiting-approval-invalidation",
stage: "post-invalidation-log-entry",
phase,
stage: "status-update",
nextStatus: "needs-replan",
error: err instanceof Error ? err.message : String(err),
});
}
if (transitioned) {
try {
await this.logEntry(id, action, text, runContext);
} catch (err) {
storeLog.warn("Best-effort post-comment re-triage failed", {
...commentContextBase,
phase,
stage: "post-invalidation-log-entry",
nextStatus: "needs-replan",
error: err instanceof Error ? err.message : String(err),
});
}
}
}
}