Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/dashboard/app/hooks/__tests__/useBlockerFanout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,16 @@ describe("computeBlockerFanoutMap", () => {

it("keeps the dashboard fallback aligned with the documented self-healing default seed", () => {
const testDir = dirname(fileURLToPath(import.meta.url));
const source = readFileSync(resolve(testDir, "../../../../engine/src/self-healing.ts"), "utf8");
const match = source.match(/export const MAX_AUTO_MERGE_RETRIES = (\d+);/);
/*
FNXC:DashboardTests 2026-07-17-11:45:
Wave-8 peeled MAX_AUTO_MERGE_RETRIES into self-healing-constants.ts (re-exported
from self-healing.ts). Read the constant definition file so this alignment
guard still pins the dashboard seed to the engine default of 3.
*/
const constantsSource = readFileSync(resolve(testDir, "../../../../engine/src/self-healing-constants.ts"), "utf8");
const match = constantsSource.match(/export const MAX_AUTO_MERGE_RETRIES = (\d+);/);
expect(match?.[1]).toBe(String(MAX_AUTO_MERGE_RETRIES));
const source = readFileSync(resolve(testDir, "../../../../engine/src/self-healing.ts"), "utf8");
expect(source).toContain("SelfHealingManager must call resolveMaxAutoMergeRetries(settings)");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ function createStore() {
return {
on: vi.fn(),
getFusionDir: vi.fn(() => "/worktree/.fusion"),
/*
FNXC:EngineTests 2026-07-17-11:45:
getAuthoritativeAssignedAgent requires store.getAsyncLayer() so the fallback
AgentStore runs in PostgreSQL backend mode. A missing method throws, is
swallowed, and the lookup returns null — defeating the runtimeConfig spy.
*/
getAsyncLayer: vi.fn(() => ({ kind: "test-async-layer" })),
} as any;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ const settings = {

function createStore(enabled: boolean): TaskStore {
return {
/*
FNXC:EngineTests 2026-07-17-11:45:
pr-response-run-ops now loads the task via store.getTask so merger model resolution
can honor per-task overrides. Stub a minimal task for the MCP-forwarding path.
*/
async getTask(taskId: string) {
return { id: taskId, column: "in-review" } as any;
},
async getSettingsByScope() {
return {
global: { mcpServers: { enabled: true, servers: [] } },
Expand Down
8 changes: 8 additions & 0 deletions packages/engine/src/__tests__/pi-layers-wiring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ vi.mock("@earendil-works/pi-coding-agent", () => ({
},
discoverAndLoadExtensions: discoverAndLoadExtensionsMock,
getAgentDir: () => "/mock-agent-dir",
/*
FNXC:EngineTests 2026-07-17-11:45:
createFusionModelRegistry awaits ModelRuntime.create before ModelRegistry (FN-8142 / pi 0.80.8+).
Without this export the layers wiring suite fails every createFnAgent path.
*/
ModelRuntime: {
create: async () => ({ getAuth: async () => ({ auth: { headers: {} as Record<string, string> } }) }),
},
ModelRegistry: class {
static create(..._args: unknown[]) {
return new (this as unknown as new () => unknown)();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,20 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
taskStore: taskStore as any,
rootDir: process.cwd(),
});
vi.spyOn(loop as any, "runValidation").mockResolvedValue({ status: "pass", summary: "ok" });
// FNXC:EngineTests 2026-07-17-11:50: runFeatureValidation destructures { result, inspection }.
vi.spyOn(loop as any, "runValidation").mockResolvedValue({
result: { status: "pass", summary: "ok" },
inspection: { rootDir: process.cwd() },
});
loop.start();

const periodicMaintenancePass = async () => loop.recoverActiveMissions();
await periodicMaintenancePass();
await periodicMaintenancePass();

expect(missionStore.ensureFeatureAssertionLinked).toHaveBeenCalledWith("F-001");
expect(missionStore.startValidatorRun).toHaveBeenCalledWith("F-001", "task_completion");
// FNXC:EngineTests 2026-07-17-11:45: startValidatorRun now threads the completing task id.
expect(missionStore.startValidatorRun).toHaveBeenCalledWith("F-001", "task_completion", "FN-001");
const noAssertionEvents = missionStore.logMissionEvent.mock.calls.filter(
([, type, , payload]) => type === "warning" && payload?.code === "validation_auto_passed_no_assertions",
);
Expand Down Expand Up @@ -316,13 +321,18 @@ describe("FN-5715 reliability: mission validation trigger gap", () => {
};

const loop = new MissionExecutionLoop({ missionStore: missionStore as any, taskStore: taskStore as any, rootDir: process.cwd() });
vi.spyOn(loop as any, "runValidation").mockResolvedValue({ status: "pass", summary: "ok" });
// FNXC:EngineTests 2026-07-17-11:50: runFeatureValidation destructures { result, inspection }.
vi.spyOn(loop as any, "runValidation").mockResolvedValue({
result: { status: "pass", summary: "ok" },
inspection: { rootDir: process.cwd() },
});
loop.start();

await loop.recoverActiveMissions();

expect(missionStore.ensureFeatureAssertionLinked).toHaveBeenCalledWith("F-001");
expect(missionStore.startValidatorRun).toHaveBeenCalledWith("F-001", "task_completion");
// FNXC:EngineTests 2026-07-17-11:45: startValidatorRun now threads the completing task id.
expect(missionStore.startValidatorRun).toHaveBeenCalledWith("F-001", "task_completion", "FN-001");
const noAssertionEvents = missionStore.logMissionEvent.mock.calls.filter(
([, type, , payload]) => type === "warning" && payload?.code === "validation_auto_passed_no_assertions",
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ function createMockStore(overrides: Partial<TaskStore> = {}): TaskStore {
updateTask: vi.fn(),
moveTask: vi.fn(),
logEntry: vi.fn(),
// FNXC:EngineTests 2026-07-17-11:45: flagTriageDuplicate records task:auto-archived-duplicate activity.
recordActivity: vi.fn().mockResolvedValue(undefined),
deleteTask: vi.fn(),
on: vi.fn(),
off: vi.fn(),
Expand Down Expand Up @@ -102,8 +104,21 @@ describe("triage finalize duplicate lineage", () => {
expect(vi.mocked(store.updateTask).mock.calls[0]?.[1]).not.toHaveProperty("sourceMetadataPatch");
});

it("preserves duplicate stub delete path", async () => {
const store = createMockStore();
it("preserves opt-in duplicate stub delete path", async () => {
/*
FNXC:EngineTests 2026-07-17-11:50:
Issue #2225 default is prompt (flag + pause). Deletion remains opt-in via
settings.triageDuplicateResolution === "delete" and still requires a live
canonical task for the marker short-circuit.
*/
const store = createMockStore({
getSettings: vi.fn().mockResolvedValue({ requirePlanApproval: false, triageDuplicateResolution: "delete" } as Settings),
getTask: vi.fn().mockImplementation(async (id: string) => (
id === "FN-4894"
? createTask({ id: "FN-4894", title: "Canonical", column: "todo", status: null })
: undefined
)),
});
await runRecovery(createTask(), "DUPLICATE: FN-4894\n", store);

expect(store.deleteTask).toHaveBeenCalledWith("FN-001", expect.objectContaining({
Expand All @@ -113,6 +128,32 @@ describe("triage finalize duplicate lineage", () => {
runId: expect.stringMatching(/^triage-delete-FN-001-/),
}),
}));
expect(store.updateTask).not.toHaveBeenCalled();
});

it("flags and parks DUPLICATE markers under default prompt resolution", async () => {
const store = createMockStore({
getTask: vi.fn().mockImplementation(async (id: string) => (
id === "FN-4894"
? createTask({ id: "FN-4894", title: "Canonical", column: "todo", status: null })
: undefined
)),
});
await runRecovery(createTask(), "DUPLICATE: FN-4894\n", store);

expect(store.recordActivity).toHaveBeenCalledWith(expect.objectContaining({
type: "task:auto-archived-duplicate",
taskId: "FN-001",
}));
expect(store.updateTask).toHaveBeenCalledWith(
"FN-001",
expect.objectContaining({
sourceMetadataPatch: expect.objectContaining({ nearDuplicateOf: "FN-4894", duplicateSource: "triage-marker" }),
}),
);
expect(store.updateTask).toHaveBeenCalledWith(
"FN-001",
expect.objectContaining({ paused: true, pausedReason: "duplicate-decision-required" }),
);
expect(store.deleteTask).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ function createStore(overrides: Partial<TaskStore> = {}): TaskStore {
planningFallbackModelId: "fallback-model",
} as Settings),
logEntry: vi.fn().mockResolvedValue(undefined),
// FNXC:EngineTests 2026-07-17-11:45: flagTriageDuplicate records task:auto-archived-duplicate activity.
recordActivity: vi.fn().mockResolvedValue(undefined),
appendAgentLog: vi.fn().mockResolvedValue(undefined),
parseDependenciesFromPrompt: vi.fn().mockResolvedValue([]),
parseStepsFromPrompt: vi.fn().mockResolvedValue([]),
Expand Down Expand Up @@ -148,9 +150,26 @@ describe("triage split/delete lineage forwarding", () => {
}));
});

it("passes removeLineageReferences on DUPLICATE close", async () => {
it("passes removeLineageReferences on opt-in DUPLICATE delete resolution", async () => {
/*
FNXC:EngineTests 2026-07-17-11:50:
Default triageDuplicateResolution is prompt (flag, not delete). This suite
still covers the delete path when operators opt in.
*/
const store = createStore({
getTask: vi.fn().mockResolvedValue(undefined),
getSettings: vi.fn().mockResolvedValue({
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 10000,
groupOverlappingFiles: false,
autoMerge: true,
triageDuplicateResolution: "delete",
} as Settings),
getTask: vi.fn().mockImplementation(async (id: string) => (
id === "FN-4894"
? createTask({ id: "FN-4894", title: "Canonical", column: "todo", status: null })
: undefined
)),
Comment thread
gsxdsm marked this conversation as resolved.
deleteTask: vi.fn().mockResolvedValue(undefined),
});

Expand Down
Loading
Loading