feat(FN-5433): suppress no-op blockedBy refresh in self-healing

Fixed self-healing to suppress unnecessary blocked-by refreshes when there are no changes (no-op), preventing spurious requeues. Added a regression test covering this scenario in the self-healing test suite.

Fusion-Task-Id: FN-5433
This commit is contained in:
Fusion (runfusion.ai)
2026-05-21 07:58:57 -07:00
committed by gsxdsm
parent 04648a4a68
commit f236dc3982
2 changed files with 28 additions and 1 deletions

View File

@@ -6157,6 +6157,29 @@ describe("clearStaleBlockedBy", () => {
manager.stop();
});
it("FN-5433: skips stale blockedBy refresh when next unresolved dependency is unchanged", async () => {
const store = createRunningStore();
const queuedTask = createTask("FN-3170", {
status: "queued",
blockedBy: "FN-DEP",
dependencies: ["FN-DEP", "FN-OTHER"],
});
const depA = createTask("FN-DEP", { column: "todo" });
const depB = createTask("FN-OTHER", { column: "in-progress" });
(store.listTasks as ReturnType<typeof vi.fn>).mockResolvedValue([queuedTask, depA, depB]);
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
const recovered = await manager.clearStaleBlockedBy();
expect(recovered).toBe(0);
expect(store.updateTask).not.toHaveBeenCalledWith("FN-3170", expect.any(Object));
const refreshedLogs = (store.logEntry as ReturnType<typeof vi.fn>).mock.calls.filter(
([taskId, message]) => taskId === "FN-3170" && String(message).includes("refreshed stale blockedBy"),
);
expect(refreshedLogs).toHaveLength(0);
manager.stop();
});
it("FN-3908: refreshes blockedBy to first unresolved dependency when stale blocker changed", async () => {
const store = createRunningStore();
const queuedTask = createTask("FN-3170", {
@@ -6169,8 +6192,9 @@ describe("clearStaleBlockedBy", () => {
(store.listTasks as ReturnType<typeof vi.fn>).mockResolvedValue([queuedTask, depA, depB]);
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
await manager.clearStaleBlockedBy();
const recovered = await manager.clearStaleBlockedBy();
expect(recovered).toBe(1);
expect(store.updateTask).toHaveBeenCalledWith("FN-3170", { blockedBy: "FN-3169", status: "queued" });
expect(store.logEntry).toHaveBeenCalledWith("FN-3170", expect.stringContaining("refreshed stale blockedBy"));
manager.stop();

View File

@@ -3384,6 +3384,9 @@ export class SelfHealingManager {
if (todoTaskIds.has(task.id)) {
if (unresolvedDeps.length > 0) {
const nextBlocker = unresolvedDeps[0]!;
if (nextBlocker === blockerId) {
continue;
}
await this.store.updateTask(task.id, { blockedBy: nextBlocker, status: "queued" });
await this.store.logEntry(task.id, `Auto-recovered: refreshed stale blockedBy — ${reason}; now blocked by ${nextBlocker}`);
} else if (hasActiveOverlapBlocker) {