feat(core,dashboard): allow tasks to be respec'd from in-review

Add `triage` to VALID_TRANSITIONS["in-review"] so the dashboard's
`Request AI Revision` and `Rebuild Spec` actions work for in-review
tasks. moveTask now applies the same full reset on in-review → triage
as on in-review → todo (clears branch/baseBranch/baseCommitSha/summary/
recovery metadata and workflowStepResults) so the respec'd task starts
from scratch. The in-review task card's Move menu also gains Planning
as a destination.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-29 11:53:35 -07:00
parent 03b406676a
commit 1beebc0c45
7 changed files with 75 additions and 17 deletions

View File

@@ -79,7 +79,7 @@ describe("board", () => {
});
it("returns correct transitions for in-review", () => {
expect(getValidTransitions("in-review")).toEqual(["done", "in-progress", "todo"]);
expect(getValidTransitions("in-review")).toEqual(["done", "in-progress", "todo", "triage"]);
});
it("returns correct transitions for done", () => {

View File

@@ -5991,6 +5991,45 @@ Task with acceptance criteria
expect(retried.recoveryRetryCount).toBeUndefined();
expect(retried.nextRecoveryAt).toBeUndefined();
});
it("allows respec'ing in-review tasks back to triage and clears transient fields", async () => {
const task = await store.createTask({ description: "test respec in-review task to triage" });
await store.moveTask(task.id, "todo");
await store.moveTask(task.id, "in-progress");
await store.moveTask(task.id, "in-review");
await store.updateTask(task.id, {
status: "completed",
error: "stale error",
worktree: "stale-worktree",
blockedBy: "FN-456",
branch: "fn/stale-branch",
baseBranch: "main",
baseCommitSha: "abc123",
summary: "stale summary from prior attempt",
recoveryRetryCount: 2,
nextRecoveryAt: new Date().toISOString(),
workflowStepResults: [{
workflowStepId: "wf-1",
workflowStepName: "Workflow step 1",
status: "passed",
startedAt: new Date().toISOString(),
}],
});
const respec = await store.moveTask(task.id, "triage");
expect(respec.column).toBe("triage");
expect(respec.status).toBeUndefined();
expect(respec.error).toBeUndefined();
expect(respec.worktree).toBeUndefined();
expect(respec.blockedBy).toBeUndefined();
expect(respec.workflowStepResults).toBeUndefined();
expect(respec.branch).toBeUndefined();
expect(respec.baseBranch).toBeUndefined();
expect(respec.baseCommitSha).toBeUndefined();
expect(respec.summary).toBeUndefined();
expect(respec.recoveryRetryCount).toBeUndefined();
expect(respec.nextRecoveryAt).toBeUndefined();
});
});
describe("columnMovedAt", () => {

View File

@@ -2640,15 +2640,16 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
// Clear workflow step results when reopening from review/completed states.
// This ensures fresh workflow step runs on retry
if (
(fromColumn === "in-review" && (toColumn === "todo" || toColumn === "in-progress"))
(fromColumn === "in-review" && (toColumn === "todo" || toColumn === "in-progress" || toColumn === "triage"))
|| (fromColumn === "done" && (toColumn === "todo" || toColumn === "triage"))
) {
task.workflowStepResults = undefined;
}
// Full reset when sending an in-review task back to todo: discard prior
// branch/summary/recovery state so the next run starts from scratch.
if (fromColumn === "in-review" && toColumn === "todo") {
// Full reset when sending an in-review task back to todo or triage
// (respec): discard prior branch/summary/recovery state so the next run
// starts from scratch.
if (fromColumn === "in-review" && (toColumn === "todo" || toColumn === "triage")) {
task.branch = undefined;
task.baseBranch = undefined;
task.baseCommitSha = undefined;

View File

@@ -1898,7 +1898,7 @@ export const VALID_TRANSITIONS: Record<Column, Column[]> = {
// NOTE: "in-progress" → "done" is enabled for mission validation tasks that complete directly.
// Regular implementation tasks should move through "in-review" before "done".
"in-progress": ["in-review", "todo", "triage", "done"],
"in-review": ["done", "in-progress", "todo"],
"in-review": ["done", "in-progress", "todo", "triage"],
done: ["todo", "triage", "archived"],
archived: ["done"],
};