feat(FN-3954): blocker staleness fanout and escalation for tasks

Merges FN-3954: adds a blocker fanout system where task staleness automatically escalates to upstream blocking tasks, with a new `blocker-fanout` core module, dashboard hooks wiring that surfaces escalation status on the board and executor status bar, and a new settings toggle to disable escalation.

Fusion-Task-Id: FN-3954
This commit is contained in:
Fusion
2026-05-10 19:47:07 -07:00
committed by gsxdsm
parent c731987dff
commit 4d2f02906d
24 changed files with 411 additions and 143 deletions

View File

@@ -28,6 +28,7 @@ describe("useAppSettings", () => {
enginePaused: false,
prAuthAvailable: true,
taskStuckTimeoutMs: 600000,
staleHighFanoutBlockerAgeThresholdMs: 7200000,
showQuickChatFAB: false,
} as never);
@@ -46,6 +47,7 @@ describe("useAppSettings", () => {
expect(result.current.prAuthAvailable).toBe(true);
expect(result.current.settingsLoaded).toBe(true);
expect(result.current.taskStuckTimeoutMs).toBe(600000);
expect(result.current.staleHighFanoutBlockerAgeThresholdMs).toBe(7200000);
expect(result.current.showQuickChatFAB).toBe(false);
});

View File

@@ -42,6 +42,7 @@ describe("computeBlockerFanoutMap", () => {
dependentIds: ["FN-2"],
staleBlockedByDependentIds: [],
isHighFanout: false,
escalation: undefined,
});
});
@@ -58,6 +59,7 @@ describe("computeBlockerFanoutMap", () => {
dependentIds: ["FN-2", "FN-3"],
staleBlockedByDependentIds: [],
isHighFanout: false,
escalation: undefined,
});
});
@@ -75,6 +77,7 @@ describe("computeBlockerFanoutMap", () => {
dependentIds: ["FN-2", "FN-3", "FN-4"],
staleBlockedByDependentIds: [],
isHighFanout: false,
escalation: undefined,
});
});
@@ -90,6 +93,7 @@ describe("computeBlockerFanoutMap", () => {
dependentIds: ["FN-2", "FN-3"],
staleBlockedByDependentIds: ["FN-3"],
isHighFanout: false,
escalation: undefined,
});
});
@@ -131,6 +135,7 @@ describe("computeBlockerFanoutMap", () => {
dependentIds: ["D1", "D2", "D3", "D4"],
staleBlockedByDependentIds: [],
isHighFanout: false,
escalation: undefined,
});
});
@@ -151,6 +156,7 @@ describe("computeBlockerFanoutMap", () => {
dependentIds: ["D1", "D2", "D3", "D4", "D5", "DONE"],
staleBlockedByDependentIds: [],
isHighFanout: true,
escalation: undefined,
});
});
@@ -170,9 +176,48 @@ describe("computeBlockerFanoutMap", () => {
dependentIds: ["D1", "D2", "D3", "D4", "ARCH"],
staleBlockedByDependentIds: [],
isHighFanout: false,
escalation: undefined,
});
});
it("escalates aged high fan-out blockers only when old enough", () => {
const tasks = [
createTask("B", "in-progress", { columnMovedAt: "2026-01-01T00:00:00.000Z" }),
createTask("D1", "todo", { dependencies: ["B"] }),
createTask("D2", "todo", { dependencies: ["B"] }),
createTask("D3", "todo", { dependencies: ["B"] }),
createTask("D4", "todo", { dependencies: ["B"] }),
createTask("D5", "todo", { dependencies: ["B"] }),
];
const entry = computeBlockerFanoutMap(tasks, {
staleHighFanoutAgeThresholdMs: 60 * 60 * 1000,
}).get("B");
expect(entry?.isHighFanout).toBe(true);
expect(entry?.escalation?.blockerId).toBe("B");
expect(entry?.escalation?.activeTodoCount).toBe(5);
expect((entry?.escalation?.blockingAgeMs ?? 0) / (60 * 60 * 1000)).toBeGreaterThanOrEqual(1);
});
it("keeps short-lived high fan-out blockers quiet", () => {
const tasks = [
createTask("B", "in-progress", { columnMovedAt: new Date().toISOString() }),
createTask("D1", "todo", { dependencies: ["B"] }),
createTask("D2", "todo", { dependencies: ["B"] }),
createTask("D3", "todo", { dependencies: ["B"] }),
createTask("D4", "todo", { dependencies: ["B"] }),
createTask("D5", "todo", { dependencies: ["B"] }),
];
const entry = computeBlockerFanoutMap(tasks, {
staleHighFanoutAgeThresholdMs: 60 * 60 * 1000,
}).get("B");
expect(entry?.isHighFanout).toBe(true);
expect(entry?.escalation).toBeUndefined();
});
it("keeps MAX_AUTO_MERGE_RETRIES aligned with engine self-healing source", () => {
const testDir = dirname(fileURLToPath(import.meta.url));
const source = readFileSync(resolve(testDir, "../../../../engine/src/self-healing.ts"), "utf8");