feat(FN-5091): complete Step 4 — wire self-healing reporter pass
Fusion-Task-Id: FN-5091 Fusion-Task-Lineage: f59dc7dd-abc2-4d1d-a755-1fe446314131
This commit is contained in:
committed by
gsxdsm
parent
2bc1c023c8
commit
e698d44914
@@ -4870,6 +4870,45 @@ describe("SelfHealingManager", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("surfaceDependencyBlockedTodos", () => {
|
||||
it("returns 0 when globalPause is enabled", async () => {
|
||||
const managerWithRecovery = new SelfHealingManager(store, { rootDir: "/tmp/test-project", getProjectId: () => "/tmp/test-project" });
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({ globalPause: true });
|
||||
|
||||
expect(await managerWithRecovery.surfaceDependencyBlockedTodos()).toBe(0);
|
||||
managerWithRecovery.stop();
|
||||
});
|
||||
|
||||
it("returns 0 when dependency-blocked todo reporting is disabled", async () => {
|
||||
const managerWithRecovery = new SelfHealingManager(store, { rootDir: "/tmp/test-project", getProjectId: () => "/tmp/test-project" });
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({ dependencyBlockedTodoReportEnabled: false });
|
||||
|
||||
expect(await managerWithRecovery.surfaceDependencyBlockedTodos()).toBe(0);
|
||||
managerWithRecovery.stop();
|
||||
});
|
||||
|
||||
it("returns groupCount from reporter", async () => {
|
||||
const managerWithRecovery = new SelfHealingManager(store, { rootDir: "/tmp/test-project", getProjectId: () => "/tmp/test-project" });
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({ dependencyBlockedTodoReportEnabled: true });
|
||||
const reportSpy = vi.fn().mockResolvedValue({ alerted: true, groupCount: 1 });
|
||||
(managerWithRecovery as unknown as { dependencyBlockedTodoReporter: { report: typeof reportSpy } }).dependencyBlockedTodoReporter = { report: reportSpy };
|
||||
|
||||
expect(await managerWithRecovery.surfaceDependencyBlockedTodos()).toBe(1);
|
||||
expect(reportSpy).toHaveBeenCalledWith();
|
||||
managerWithRecovery.stop();
|
||||
});
|
||||
|
||||
it("returns 0 and logs error when reporter fails", async () => {
|
||||
const managerWithRecovery = new SelfHealingManager(store, { rootDir: "/tmp/test-project", getProjectId: () => "/tmp/test-project" });
|
||||
(store.getSettings as ReturnType<typeof vi.fn>).mockResolvedValue({ dependencyBlockedTodoReportEnabled: true });
|
||||
const reportSpy = vi.fn().mockRejectedValue(new Error("boom"));
|
||||
(managerWithRecovery as unknown as { dependencyBlockedTodoReporter: { report: typeof reportSpy } }).dependencyBlockedTodoReporter = { report: reportSpy };
|
||||
|
||||
expect(await managerWithRecovery.surfaceDependencyBlockedTodos()).toBe(0);
|
||||
managerWithRecovery.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("surfaceStalePausedReviews", () => {
|
||||
function pausedReviewTask(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
type NtfyNotifier,
|
||||
} from "./notifier.js";
|
||||
import type { GhostBugDecision } from "./triage-preflight.js";
|
||||
import { DependencyBlockedTodoReporter } from "./dependency-blocked-todo-reporter.js";
|
||||
|
||||
const log = createLogger("self-healing");
|
||||
const worktreeMetadataReconcileLog = createLogger("worktree-metadata-reconcile");
|
||||
@@ -250,6 +251,7 @@ export interface SelfHealingOptions {
|
||||
messageStore?: MessageStore;
|
||||
/** Optional notifier for board-stall unrecovered alerts. */
|
||||
ntfyNotifier?: Pick<NtfyNotifier, "notifyBoardStallUnrecovered">;
|
||||
getProjectId?: () => string;
|
||||
}
|
||||
|
||||
const APPROVED_TRIAGE_RECOVERY_GRACE_MS = 60_000;
|
||||
@@ -529,6 +531,8 @@ export class SelfHealingManager {
|
||||
private orphanArchivedAcknowledged = new Set<string>();
|
||||
private finalizeUnprovenWarned = new Set<string>();
|
||||
private maintenanceTickCounter = 0;
|
||||
private dependencyBlockedTodoReporter: DependencyBlockedTodoReporter | null = null;
|
||||
|
||||
private boardStallWindow: {
|
||||
windowStartMs: number;
|
||||
windowStartBlockedDepth: number;
|
||||
@@ -4041,6 +4045,39 @@ export class SelfHealingManager {
|
||||
}
|
||||
}
|
||||
|
||||
private getDependencyBlockedTodoReporter(): DependencyBlockedTodoReporter | null {
|
||||
if (this.dependencyBlockedTodoReporter) {
|
||||
return this.dependencyBlockedTodoReporter;
|
||||
}
|
||||
const projectId = this.options.getProjectId?.();
|
||||
if (!projectId) {
|
||||
return null;
|
||||
}
|
||||
this.dependencyBlockedTodoReporter = new DependencyBlockedTodoReporter({
|
||||
store: this.store,
|
||||
projectId,
|
||||
now: () => Date.now(),
|
||||
});
|
||||
return this.dependencyBlockedTodoReporter;
|
||||
}
|
||||
|
||||
async surfaceDependencyBlockedTodos(): Promise<number> {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
if (settings.globalPause || settings.enginePaused) return 0;
|
||||
if (settings.dependencyBlockedTodoReportEnabled === false) return 0;
|
||||
|
||||
const reporter = this.getDependencyBlockedTodoReporter();
|
||||
if (!reporter) return 0;
|
||||
const result = await reporter.report();
|
||||
return result.groupCount ?? 0;
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
log.error(`Dependency-blocked todo surfacing failed: ${errorMessage}`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
async surfaceStalePausedReviews(): Promise<number> {
|
||||
try {
|
||||
const settings = await this.store.getSettings();
|
||||
|
||||
Reference in New Issue
Block a user