feat(FN-2604): merge fusion/fn-2604 (auto-resolved)
- feat(FN-2604): complete Step 8 — add changeset and delivery docs - test(FN-2604): complete Step 7 — align tests and regenerate extension skill docs - test(FN-2604): complete Step 6 — update dashboard tests for planning statuses - feat(FN-2604): complete Step 5 — update CLI planning terminology - feat(FN-2604): complete Step 4 — update workflow route status strings - feat(FN-2604): complete Step 3 — update settings planning terminology - feat(FN-2604): complete Step 2 — rename replanning actions in column and spec editor - feat(FN-2604): complete Step 1 — update dashboard component status strings
This commit is contained in:
@@ -70,7 +70,7 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
const [dragOver, setDragOver] = useState(false);
|
||||
const [visibleTaskCount, setVisibleTaskCount] = useState(VISIBLE_TASKS_INITIAL);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [isRespecifying, setIsRespecifying] = useState(false);
|
||||
const [isReplanning, setIsReplanning] = useState(false);
|
||||
const [isPausingAll, setIsPausingAll] = useState(false);
|
||||
const [isMovingAllToTodo, setIsMovingAllToTodo] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement | null>(null);
|
||||
@@ -161,16 +161,16 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
setVisibleTaskCount((current) => Math.min(current + VISIBLE_TASKS_INCREMENT, tasks.length));
|
||||
}, [tasks.length]);
|
||||
|
||||
const handleRespecifyAll = useCallback(async () => {
|
||||
const handleReplanAll = useCallback(async () => {
|
||||
setIsMenuOpen(false);
|
||||
if (tasks.length === 0) return;
|
||||
|
||||
const confirmed = window.confirm(
|
||||
`Move all ${tasks.length} todo task${tasks.length === 1 ? "" : "s"} back to triage to be respecified?`,
|
||||
`Move all ${tasks.length} todo task${tasks.length === 1 ? "" : "s"} back to planning to be replanned?`,
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
setIsRespecifying(true);
|
||||
setIsReplanning(true);
|
||||
try {
|
||||
// Issue moves in parallel — onMoveTask is per-task, no bulk endpoint.
|
||||
const results = await Promise.allSettled(
|
||||
@@ -179,19 +179,19 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
const failed = results.filter((r) => r.status === "rejected").length;
|
||||
const moved = results.length - failed;
|
||||
if (failed === 0) {
|
||||
addToast(`Moved ${moved} task${moved === 1 ? "" : "s"} to triage for respec`, "success");
|
||||
addToast(`Moved ${moved} task${moved === 1 ? "" : "s"} to planning for replanning`, "success");
|
||||
} else {
|
||||
addToast(`Moved ${moved} of ${results.length} tasks; ${failed} failed`, "error");
|
||||
}
|
||||
} finally {
|
||||
setIsRespecifying(false);
|
||||
setIsReplanning(false);
|
||||
}
|
||||
}, [tasks, onMoveTask, addToast]);
|
||||
|
||||
const pauseEligibleTasks = useMemo(() => tasks.filter((task) => !task.paused), [tasks]);
|
||||
const pauseEligibleCount = pauseEligibleTasks.length;
|
||||
const hasColumnBulkActions = column === "todo" || column === "in-progress" || column === "in-review";
|
||||
const isMenuBusy = isRespecifying || isPausingAll || isMovingAllToTodo;
|
||||
const isMenuBusy = isReplanning || isPausingAll || isMovingAllToTodo;
|
||||
|
||||
const handlePauseAll = useCallback(async () => {
|
||||
if (!onPauseTask) return;
|
||||
@@ -332,12 +332,12 @@ function ColumnComponent({ column, tasks, projectId, maxConcurrent, onMoveTask,
|
||||
type="button"
|
||||
role="menuitem"
|
||||
className="column-menu-item"
|
||||
onClick={() => void handleRespecifyAll()}
|
||||
disabled={tasks.length === 0 || isRespecifying}
|
||||
onClick={() => void handleReplanAll()}
|
||||
disabled={tasks.length === 0 || isReplanning}
|
||||
>
|
||||
Respecify All
|
||||
Replan All
|
||||
<span className="column-menu-item-hint">
|
||||
Move {tasks.length} task{tasks.length === 1 ? "" : "s"} to Triage
|
||||
Move {tasks.length} task{tasks.length === 1 ? "" : "s"} to Planning
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -22,7 +22,7 @@ const COLUMN_COLOR_MAP: Record<Column, string> = {
|
||||
archived: "var(--text-secondary)",
|
||||
};
|
||||
|
||||
const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging", "specifying"]);
|
||||
const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging"]);
|
||||
|
||||
type SortField = "id" | "title" | "status" | "column";
|
||||
type SortDirection = "asc" | "desc";
|
||||
|
||||
@@ -882,7 +882,7 @@ export function SettingsModal({
|
||||
globalModelKey: "planningGlobalModelId",
|
||||
projectProviderKey: "planningProvider",
|
||||
projectModelKey: "planningModelId",
|
||||
helperText: "AI model used for task specification (triage).",
|
||||
helperText: "AI model used for task planning.",
|
||||
fallbackOrder: "Project override → Global planning lane → Global default lane → Automatic resolution",
|
||||
},
|
||||
{
|
||||
@@ -2102,7 +2102,7 @@ export function SettingsModal({
|
||||
setForm((f) => ({ ...f, maxTriageConcurrent: val === "" ? undefined : Number(val) } as SettingsFormState));
|
||||
}}
|
||||
/>
|
||||
<small>Maximum concurrent triage/specification agents</small>
|
||||
<small>Maximum concurrent planning agents</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="pollIntervalMs">Poll Interval (ms)</label>
|
||||
@@ -2144,9 +2144,9 @@ export function SettingsModal({
|
||||
setForm((f) => ({ ...f, specStalenessEnabled: e.target.checked }))
|
||||
}
|
||||
/>
|
||||
Enable specification staleness enforcement
|
||||
Enable plan staleness enforcement
|
||||
</label>
|
||||
<small>When enabled, tasks with stale specifications (PROMPT.md older than the threshold) are automatically sent back to triage for re-specification</small>
|
||||
<small>When enabled, tasks with stale plans (PROMPT.md older than the threshold) are automatically sent back to planning for replanning</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="specStalenessMaxAgeMs">Stale Spec Threshold (hours)</label>
|
||||
@@ -2163,7 +2163,7 @@ export function SettingsModal({
|
||||
}}
|
||||
disabled={!form.specStalenessEnabled}
|
||||
/>
|
||||
<small>Maximum age in hours before a specification is considered stale. Default: 6 hours.</small>
|
||||
<small>Maximum age in hours before a plan is considered stale. Default: 6 hours.</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="autoArchiveDoneTasksEnabled" className="checkbox-label">
|
||||
|
||||
@@ -163,7 +163,7 @@ export function SpecEditor({
|
||||
<div className="spec-editor-revision">
|
||||
<h4>Ask AI to Revise</h4>
|
||||
<p className="spec-editor-revision-help">
|
||||
Provide feedback for the AI to improve this specification. The task will move to triage for re-specification.
|
||||
Provide feedback for the AI to improve this specification. The task will move to planning for replanning.
|
||||
</p>
|
||||
<textarea
|
||||
ref={feedbackRef}
|
||||
|
||||
@@ -81,7 +81,7 @@ function abbreviateBadge(text: string, max: number): string {
|
||||
|
||||
const EDITABLE_COLUMNS: Set<Column> = new Set(["triage", "todo"]);
|
||||
|
||||
const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging", "specifying"]);
|
||||
const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging"]);
|
||||
|
||||
const COLUMN_PROGRESS_COLOR_MAP: Record<Column, string> = {
|
||||
triage: "var(--triage)",
|
||||
|
||||
@@ -299,8 +299,8 @@ export function TaskDetailModal({
|
||||
const canRetryTask =
|
||||
task.status === "failed" ||
|
||||
task.status === "stuck-killed" ||
|
||||
task.status === "specifying" ||
|
||||
task.status === "needs-respecify" ||
|
||||
task.status === "planning" ||
|
||||
task.status === "needs-replan" ||
|
||||
(task.stuckKillCount ?? 0) > 0 ||
|
||||
(task.recoveryRetryCount ?? 0) > 0 ||
|
||||
Boolean(task.nextRecoveryAt);
|
||||
@@ -903,7 +903,7 @@ export function TaskDetailModal({
|
||||
if (!confirm("Reject this plan? The specification will be discarded and regenerated.")) return;
|
||||
try {
|
||||
await rejectPlan(task.id, projectId);
|
||||
addToast(`Plan rejected — ${task.id} returned to Triage for re-specification`, "info");
|
||||
addToast(`Plan rejected — ${task.id} returned to Planning for replanning`, "info");
|
||||
onClose();
|
||||
} catch (err) {
|
||||
addToast(getErrorMessage(err), "error");
|
||||
@@ -911,11 +911,11 @@ export function TaskDetailModal({
|
||||
}, [task.id, onClose, addToast]);
|
||||
|
||||
const handleRespecify = useCallback(async () => {
|
||||
if (!confirm("Rebuild the specification for this task? The task will move to triage for re-specification.")) return;
|
||||
if (!confirm("Rebuild the plan for this task? The task will move to planning for replanning.")) return;
|
||||
try {
|
||||
await rebuildTaskSpec(task.id, projectId);
|
||||
onClose();
|
||||
addToast(`Respecifying ${task.id}...`, "info");
|
||||
addToast(`Replanning ${task.id}...`, "info");
|
||||
} catch (err) {
|
||||
addToast(getErrorMessage(err), "error");
|
||||
}
|
||||
@@ -1152,8 +1152,8 @@ export function TaskDetailModal({
|
||||
setIsRequestingRevision(true);
|
||||
try {
|
||||
await requestSpecRevision(task.id, feedback, projectId);
|
||||
addToast("AI revision requested. Task moved to triage.", "success");
|
||||
// Task has been moved to triage, close modal
|
||||
addToast("AI revision requested. Task moved to planning.", "success");
|
||||
// Task has been moved to planning, close modal
|
||||
onClose();
|
||||
} catch (err) {
|
||||
const msg = getErrorMessage(err);
|
||||
@@ -1730,7 +1730,7 @@ export function TaskDetailModal({
|
||||
<div className="spec-editor-revision">
|
||||
<h4>Ask AI to Revise</h4>
|
||||
<p className="spec-editor-revision-help">
|
||||
Provide feedback for the AI to improve this specification. The task will move to triage for re-specification.
|
||||
Provide feedback for the AI to improve this specification. The task will move to planning for replanning.
|
||||
</p>
|
||||
<textarea
|
||||
className="spec-editor-feedback"
|
||||
|
||||
@@ -374,10 +374,10 @@ describe("TaskDetailModal", () => {
|
||||
expect(retryButtons).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("shows Retry for a stranded specifying triage task", () => {
|
||||
it("shows Retry for a stranded planning triage task", () => {
|
||||
render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ column: "triage", status: "specifying", stuckKillCount: 6 })}
|
||||
task={makeTask({ column: "triage", status: "planning", stuckKillCount: 6 })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
@@ -2992,7 +2992,7 @@ describe("TaskDetailModal", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(requestSpecRevision).toHaveBeenCalledWith("FN-099", "Please add more error handling details", undefined);
|
||||
expect(addToast).toHaveBeenCalledWith("AI revision requested. Task moved to triage.", "success");
|
||||
expect(addToast).toHaveBeenCalledWith("AI revision requested. Task moved to planning.", "success");
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -3219,7 +3219,7 @@ describe("TaskDetailModal", () => {
|
||||
<TaskDetailModal
|
||||
task={makeTask({
|
||||
column: "triage",
|
||||
status: "specifying",
|
||||
status: "planning",
|
||||
prompt: "# Task Spec",
|
||||
})}
|
||||
onClose={noop}
|
||||
@@ -3325,7 +3325,7 @@ describe("TaskDetailModal", () => {
|
||||
expect(mockRejectPlan).toHaveBeenCalledWith("FN-001", undefined);
|
||||
});
|
||||
expect(addToast).toHaveBeenCalledWith(
|
||||
"Plan rejected — FN-001 returned to Triage for re-specification",
|
||||
"Plan rejected — FN-001 returned to Planning for replanning",
|
||||
"info"
|
||||
);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
|
||||
@@ -1946,30 +1946,30 @@ describe("POST /tasks/:id/retry", () => {
|
||||
expect(store.logEntry).toHaveBeenCalledWith("KB-001", "Retry requested from dashboard (stuck kill budget reset)");
|
||||
});
|
||||
|
||||
it("retries a stranded specifying triage task in triage and removes stale prompt", async () => {
|
||||
it("retries a stranded planning triage task in triage and removes stale prompt", async () => {
|
||||
const tempRoot = mkdtempSync(join(tmpdir(), "kb-task-retry-spec-"));
|
||||
const taskDir = join(tempRoot, ".fusion", "tasks", "FN-001");
|
||||
mkdirSync(taskDir, { recursive: true });
|
||||
writeFileSync(join(taskDir, "PROMPT.md"), "# stale spec\n");
|
||||
|
||||
const specifyingTask = {
|
||||
const planningTask = {
|
||||
...FAKE_TASK_DETAIL,
|
||||
column: "triage" as const,
|
||||
status: "specifying",
|
||||
status: "planning",
|
||||
stuckKillCount: 6,
|
||||
recoveryRetryCount: 2,
|
||||
nextRecoveryAt: new Date(Date.now() + 60_000).toISOString(),
|
||||
};
|
||||
const retriedTask = {
|
||||
...specifyingTask,
|
||||
status: "needs-respecify",
|
||||
...planningTask,
|
||||
status: "needs-replan",
|
||||
stuckKillCount: 0,
|
||||
recoveryRetryCount: undefined,
|
||||
nextRecoveryAt: undefined,
|
||||
};
|
||||
|
||||
(store.getTask as ReturnType<typeof vi.fn>)
|
||||
.mockResolvedValueOnce(specifyingTask)
|
||||
.mockResolvedValueOnce(planningTask)
|
||||
.mockResolvedValueOnce(retriedTask);
|
||||
(store.updateTask as ReturnType<typeof vi.fn>).mockResolvedValue(retriedTask);
|
||||
(store.getRootDir as ReturnType<typeof vi.fn>).mockReturnValue(tempRoot);
|
||||
@@ -1981,7 +1981,7 @@ describe("POST /tasks/:id/retry", () => {
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(store.updateTask).toHaveBeenCalledWith("KB-001", {
|
||||
status: "needs-respecify",
|
||||
status: "needs-replan",
|
||||
error: null,
|
||||
worktree: null,
|
||||
branch: null,
|
||||
@@ -1995,9 +1995,9 @@ describe("POST /tasks/:id/retry", () => {
|
||||
expect(existsSync(join(taskDir, "PROMPT.md"))).toBe(false);
|
||||
expect(store.logEntry).toHaveBeenCalledWith(
|
||||
"KB-001",
|
||||
"Retry requested from dashboard (specification retry budget reset)",
|
||||
"Retry requested from dashboard (planning retry budget reset)",
|
||||
);
|
||||
expect(res.body.status).toBe("needs-respecify");
|
||||
expect(res.body.status).toBe("needs-replan");
|
||||
} finally {
|
||||
rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -6916,7 +6916,7 @@ describe("POST /tasks/:id/spec/revise", () => {
|
||||
);
|
||||
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "triage");
|
||||
expect(existsSync(join(taskDir, "PROMPT.md"))).toBe(false);
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: "needs-respecify" });
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: "needs-replan" });
|
||||
} finally {
|
||||
rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -6944,7 +6944,7 @@ describe("POST /tasks/:id/spec/revise", () => {
|
||||
|
||||
it("allows spec revision for task already in triage", async () => {
|
||||
const triageTask = { ...FAKE_TASK_DETAIL, column: "triage" as const };
|
||||
const updatedTask = { ...FAKE_TASK_DETAIL, column: "triage" as const, status: "needs-respecify" as const };
|
||||
const updatedTask = { ...FAKE_TASK_DETAIL, column: "triage" as const, status: "needs-replan" as const };
|
||||
const tempRoot = mkdtempSync(join(tmpdir(), "kb-spec-revise-triage-"));
|
||||
const taskDir = join(tempRoot, ".fusion", "tasks", "FN-001");
|
||||
mkdirSync(taskDir, { recursive: true });
|
||||
@@ -6973,7 +6973,7 @@ describe("POST /tasks/:id/spec/revise", () => {
|
||||
);
|
||||
expect(store.moveTask).not.toHaveBeenCalled();
|
||||
expect(existsSync(join(taskDir, "PROMPT.md"))).toBe(false);
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: "needs-respecify" });
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: "needs-replan" });
|
||||
} finally {
|
||||
rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -7149,7 +7149,7 @@ describe("POST /tasks/:id/spec/rebuild", () => {
|
||||
);
|
||||
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "triage");
|
||||
expect(existsSync(join(taskDir, "PROMPT.md"))).toBe(false);
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: "needs-respecify" });
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: "needs-replan" });
|
||||
} finally {
|
||||
rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -7167,7 +7167,7 @@ describe("POST /tasks/:id/spec/rebuild", () => {
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "triage");
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: "needs-respecify" });
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: "needs-replan" });
|
||||
});
|
||||
|
||||
it("rebuilds spec and moves task from done to triage", async () => {
|
||||
@@ -7186,7 +7186,7 @@ describe("POST /tasks/:id/spec/rebuild", () => {
|
||||
|
||||
it("allows rebuild for task already in triage", async () => {
|
||||
const triageTask = { ...FAKE_TASK_DETAIL, column: "triage" as const };
|
||||
const updatedTask = { ...FAKE_TASK_DETAIL, column: "triage" as const, status: "needs-respecify" as const };
|
||||
const updatedTask = { ...FAKE_TASK_DETAIL, column: "triage" as const, status: "needs-replan" as const };
|
||||
const tempRoot = mkdtempSync(join(tmpdir(), "kb-spec-rebuild-triage-"));
|
||||
const taskDir = join(tempRoot, ".fusion", "tasks", "FN-001");
|
||||
mkdirSync(taskDir, { recursive: true });
|
||||
@@ -7208,7 +7208,7 @@ describe("POST /tasks/:id/spec/rebuild", () => {
|
||||
);
|
||||
expect(store.moveTask).not.toHaveBeenCalled();
|
||||
expect(existsSync(join(taskDir, "PROMPT.md"))).toBe(false);
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: "needs-respecify" });
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-001", { status: "needs-replan" });
|
||||
} finally {
|
||||
rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
@@ -7288,7 +7288,7 @@ describe("POST /tasks/:id/approve-plan", () => {
|
||||
});
|
||||
|
||||
it("returns 400 when task does not have awaiting-approval status", async () => {
|
||||
const triageTask = { ...FAKE_TASK_DETAIL, column: "triage" as const, status: "specifying" as const };
|
||||
const triageTask = { ...FAKE_TASK_DETAIL, column: "triage" as const, status: "planning" as const };
|
||||
(store.getTask as ReturnType<typeof vi.fn>).mockResolvedValue(triageTask);
|
||||
|
||||
const res = await REQUEST(buildApp(), "POST", "/api/tasks/KB-001/approve-plan");
|
||||
@@ -7364,7 +7364,7 @@ describe("POST /tasks/:id/reject-plan", () => {
|
||||
});
|
||||
|
||||
it("returns 400 when task does not have awaiting-approval status", async () => {
|
||||
const triageTask = { ...FAKE_TASK_DETAIL, column: "triage" as const, status: "specifying" as const };
|
||||
const triageTask = { ...FAKE_TASK_DETAIL, column: "triage" as const, status: "planning" as const };
|
||||
(store.getTask as ReturnType<typeof vi.fn>).mockResolvedValue(triageTask);
|
||||
|
||||
const res = await REQUEST(buildApp(), "POST", "/api/tasks/KB-001/reject-plan");
|
||||
|
||||
@@ -241,7 +241,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
|
||||
}
|
||||
});
|
||||
|
||||
// Retry failed, stuck-killed, or stranded triage/specification task
|
||||
// Retry failed, stuck-killed, or stranded triage/planning task
|
||||
router.post("/tasks/:id/retry", async (req, res) => {
|
||||
try {
|
||||
const { store: scopedStore } = await getProjectContext(req);
|
||||
@@ -249,14 +249,14 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
|
||||
const retrySpecification =
|
||||
task.column === "triage" &&
|
||||
(task.status === "failed" ||
|
||||
task.status === "specifying" ||
|
||||
task.status === "needs-respecify" ||
|
||||
task.status === "planning" ||
|
||||
task.status === "needs-replan" ||
|
||||
(task.stuckKillCount ?? 0) > 0);
|
||||
if (task.status !== "failed" && task.status !== "stuck-killed" && !retrySpecification) {
|
||||
throw badRequest(`Task is not in a retryable state (current status: ${task.status || 'none'})`);
|
||||
}
|
||||
await scopedStore.updateTask(req.params.id, {
|
||||
status: retrySpecification ? "needs-respecify" : null,
|
||||
status: retrySpecification ? "needs-replan" : null,
|
||||
error: null,
|
||||
worktree: null,
|
||||
branch: null,
|
||||
@@ -272,7 +272,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
|
||||
const { join } = await import("node:path");
|
||||
const promptPath = join(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
||||
await rm(promptPath, { force: true });
|
||||
await scopedStore.logEntry(req.params.id, "Retry requested from dashboard (specification retry budget reset)");
|
||||
await scopedStore.logEntry(req.params.id, "Retry requested from dashboard (planning retry budget reset)");
|
||||
const updated = await scopedStore.getTask(req.params.id);
|
||||
res.json(updated);
|
||||
return;
|
||||
@@ -1110,20 +1110,20 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
|
||||
const task = await scopedStore.getTask(req.params.id);
|
||||
|
||||
// If task is already in triage, skip the transition check and moveTask.
|
||||
// Just reset for re-specification in place.
|
||||
// Just reset for replanning in place.
|
||||
if (task.column === "triage") {
|
||||
// Log the revision request
|
||||
await scopedStore.logEntry(task.id, "AI spec revision requested", feedback);
|
||||
|
||||
// Remove the existing spec so re-specification starts from the task
|
||||
// Remove the existing spec so replanning starts from the task
|
||||
// description and feedback rather than revising stale PROMPT.md content.
|
||||
const { rm } = await import("node:fs/promises");
|
||||
const { join } = await import("node:path");
|
||||
const promptPath = join(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
||||
await rm(promptPath, { force: true });
|
||||
|
||||
// Update status to indicate needs re-specification
|
||||
await scopedStore.updateTask(task.id, { status: "needs-respecify" });
|
||||
// Update status to indicate needs replanning
|
||||
await scopedStore.updateTask(task.id, { status: "needs-replan" });
|
||||
|
||||
const updated = await scopedStore.getTask(task.id);
|
||||
res.json(updated);
|
||||
@@ -1141,18 +1141,18 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
|
||||
// Log the revision request
|
||||
await scopedStore.logEntry(task.id, "AI spec revision requested", feedback);
|
||||
|
||||
// Move to triage for re-specification
|
||||
// Move to triage for replanning
|
||||
const updated = await scopedStore.moveTask(task.id, "triage");
|
||||
|
||||
// Remove the existing spec so re-specification starts from the task
|
||||
// Remove the existing spec so replanning starts from the task
|
||||
// description and feedback rather than revising stale PROMPT.md content.
|
||||
const { rm } = await import("node:fs/promises");
|
||||
const { join } = await import("node:path");
|
||||
const promptPath = join(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
||||
await rm(promptPath, { force: true });
|
||||
|
||||
// Update status to indicate needs re-specification
|
||||
await scopedStore.updateTask(task.id, { status: "needs-respecify" });
|
||||
// Update status to indicate needs replanning
|
||||
await scopedStore.updateTask(task.id, { status: "needs-replan" });
|
||||
|
||||
res.json(updated);
|
||||
} catch (err: unknown) {
|
||||
@@ -1176,7 +1176,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
|
||||
const task = await scopedStore.getTask(req.params.id);
|
||||
|
||||
// If task is already in triage, skip the transition check and moveTask.
|
||||
// Just reset for re-specification in place.
|
||||
// Just reset for replanning in place.
|
||||
if (task.column === "triage") {
|
||||
// Log the rebuild request
|
||||
await scopedStore.logEntry(task.id, "Specification rebuild requested by user");
|
||||
@@ -1188,8 +1188,8 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
|
||||
const promptPath = join(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
||||
await rm(promptPath, { force: true });
|
||||
|
||||
// Update status to indicate needs re-specification
|
||||
await scopedStore.updateTask(task.id, { status: "needs-respecify" });
|
||||
// Update status to indicate needs replanning
|
||||
await scopedStore.updateTask(task.id, { status: "needs-replan" });
|
||||
|
||||
const updated = await scopedStore.getTask(task.id);
|
||||
res.json(updated);
|
||||
@@ -1205,7 +1205,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
|
||||
// Log the rebuild request
|
||||
await scopedStore.logEntry(task.id, "Specification rebuild requested by user");
|
||||
|
||||
// Move to triage for re-specification
|
||||
// Move to triage for replanning
|
||||
const updated = await scopedStore.moveTask(task.id, "triage");
|
||||
|
||||
// Remove the existing spec so rebuilds produce a fresh PROMPT.md instead
|
||||
@@ -1215,8 +1215,8 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
|
||||
const promptPath = join(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
||||
await rm(promptPath, { force: true });
|
||||
|
||||
// Update status to indicate needs re-specification
|
||||
await scopedStore.updateTask(task.id, { status: "needs-respecify" });
|
||||
// Update status to indicate needs replanning
|
||||
await scopedStore.updateTask(task.id, { status: "needs-replan" });
|
||||
|
||||
res.json(updated);
|
||||
} catch (err: unknown) {
|
||||
|
||||
Reference in New Issue
Block a user