feat(FN-3056): merge fusion/fn-3056

This merge ships several feature and infrastructure improvements across the codebase. Task title validation is strengthened in triage with stricter rejection of malformed titles and preference for prompt-declared titles (FN-3056), while task creation now preserves priority settings (FN-3210). The Mi

Fusion-Task-Id: FN-3056
This commit is contained in:
Fusion
2026-05-02 11:37:15 -07:00
committed by gsxdsm
parent 9c45d2410b
commit 6b4f28a1fb
11 changed files with 205 additions and 11 deletions

View File

@@ -1335,6 +1335,84 @@ describe("approved triage recovery", () => {
);
});
it("updates malformed metadata title from prompt heading when task ID matches", async () => {
await writeFile(
join(rootDir, ".fusion", "tasks", "FN-001", "PROMPT.md"),
"# Task: FN-001 - Experimental AI Agent Onboarding Flow\n\n**Size:** M\n\n## Review Level: 2\n\nRecovered specification",
);
const store = createMockStore({
getSettings: vi.fn().mockResolvedValue({
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 10000,
groupOverlappingFiles: false,
autoMerge: true,
requirePlanApproval: false,
} as Settings),
});
const processor = new TriageProcessor(store, rootDir);
const recovered = await processor.recoverApprovedTask({
id: "FN-001",
description: "Recovered triage task",
column: "triage",
status: "planning",
title: "Created task **FN-999** in triage",
dependencies: [],
steps: [],
currentStep: 0,
log: [{ timestamp: "2026-01-01T00:00:00.000Z", action: "Spec review: APPROVE" }],
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:02:00.000Z",
});
expect(recovered).toBe(true);
expect(store.updateTask).toHaveBeenCalledWith(
"FN-001",
expect.objectContaining({ title: "Experimental AI Agent Onboarding Flow" }),
);
});
it("does not overwrite title when heading task ID does not match", async () => {
await writeFile(
join(rootDir, ".fusion", "tasks", "FN-001", "PROMPT.md"),
"# Task: FN-999 - Wrong Task\n\n**Size:** M\n\n## Review Level: 2\n\nRecovered specification",
);
const store = createMockStore({
getSettings: vi.fn().mockResolvedValue({
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 10000,
groupOverlappingFiles: false,
autoMerge: true,
requirePlanApproval: false,
} as Settings),
});
const processor = new TriageProcessor(store, rootDir);
const recovered = await processor.recoverApprovedTask({
id: "FN-001",
description: "Recovered triage task",
column: "triage",
status: "planning",
title: "Existing title",
dependencies: [],
steps: [],
currentStep: 0,
log: [{ timestamp: "2026-01-01T00:00:00.000Z", action: "Spec review: APPROVE" }],
createdAt: "2026-01-01T00:00:00.000Z",
updatedAt: "2026-01-01T00:02:00.000Z",
});
expect(recovered).toBe(true);
expect(store.updateTask).toHaveBeenCalledWith(
"FN-001",
expect.not.objectContaining({ title: expect.any(String) }),
);
});
it("clears status and error before moving approved tasks to todo", async () => {
const store = createMockStore({
getSettings: vi.fn().mockResolvedValue({