feat(FN-4990): complete Step 2 — recompute currentStep after lost work reset

Fusion-Task-Id: FN-4990
Fusion-Task-Lineage: 167a6011-d246-45bd-ad7a-1f2fe8b99323
This commit is contained in:
Fusion (runfusion.ai)
2026-05-18 02:18:24 -07:00
committed by gsxdsm
parent f9f8ca04b9
commit 7bcffa5a58
2 changed files with 79 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
import { beforeEach, describe, expect, it } from "vitest";
import "./executor-test-helpers.js";
import { TaskExecutor } from "../executor.js";
import type { Task } from "@fusion/core";
import { createMockStore, mockedExecSync, resetExecutorMocks } from "./executor-test-helpers.js";
describe("TaskExecutor.resetStepsIfWorkLost", () => {
beforeEach(() => {
resetExecutorMocks();
});
it("resets completed steps and recomputes currentStep when branch has no unique commits", async () => {
const store = createMockStore();
const task: Task = {
id: "FN-4990",
title: "Reset steps",
description: "desc",
column: "in-progress",
dependencies: [],
steps: [
{ name: "Step 1", status: "done" },
{ name: "Step 2", status: "done" },
{ name: "Step 3", status: "done" },
{ name: "Step 4", status: "pending" },
{ name: "Step 5", status: "pending" },
],
currentStep: 3,
log: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
branch: "fusion/fn-4990",
};
store.getTask.mockImplementation(async () => task);
store.updateStep.mockImplementation(async (_taskId: string, stepIndex: number, status: Task["steps"][number]["status"]) => {
task.steps[stepIndex].status = status;
return task;
});
store.updateTask.mockImplementation(async (_taskId: string, updates: Partial<Task>) => {
Object.assign(task, updates);
return task;
});
mockedExecSync.mockImplementation((cmd: string) => {
if (cmd.includes("git merge-base")) return "abc123\n";
if (cmd.includes("git rev-parse")) return "abc123\n";
return "";
});
const executor = new TaskExecutor(store as any, "/tmp/test");
await (executor as any).resetStepsIfWorkLost(task);
expect(task.steps[0].status).toBe("pending");
expect(task.steps[1].status).toBe("pending");
expect(task.steps[2].status).toBe("pending");
expect(task.currentStep).toBe(0);
expect(store.logEntry).toHaveBeenCalledWith(
task.id,
expect.stringContaining("currentStep"),
);
});
});

View File

@@ -9417,6 +9417,23 @@ Backward compat fallback: if JSON is unavailable, you may still begin output wit
}
}
const refreshedTask = await this.store.getTask(task.id);
const prevCurrentStep = refreshedTask.currentStep;
if (refreshedTask.steps.length > 0) {
const firstPendingStep = refreshedTask.steps.findIndex((s) => s.status === "pending");
const newCurrentStep = firstPendingStep >= 0 ? firstPendingStep : 0;
if (newCurrentStep !== prevCurrentStep) {
await this.store.updateTask(task.id, { currentStep: newCurrentStep });
executorLog.log(
`${task.id}: reset currentStep to ${newCurrentStep} after lost-work reset (was ${prevCurrentStep})`,
);
await this.store.logEntry(
task.id,
`Reset currentStep to ${newCurrentStep} after lost-work step reset (was ${prevCurrentStep})`,
);
}
}
await this.store.logEntry(
task.id,
`Reset ${completedSteps.length} step(s) to pending — branch had no commits (uncommitted work lost with worktree)`,