FN-5714: reject linking features to non-active tasks
Gracefully handle feature-to-task linking when the referenced task is archived, deleted, or missing. - enforce active-board task validation in mission store linkage path with clear error text - return surfaced validation errors from fn_feature_link_task instead of throwing opaque failures - add regression coverage in core and CLI extension tests for archived/non-active task references - add changeset and sync Fusion skill reference docs with the new fn_feature_link_task behavior Files changed: .changeset/fn-5714-link-archived-task.md | 5 +++ .../cli/skill/fusion/references/extension-tools.md | 2 +- .../skill/fusion/references/fusion-capabilities.md | 2 +- packages/cli/src/__tests__/extension.test.ts | 50 ++++++++++++++++++++++ packages/cli/src/extension.ts | 35 +++++++++------ packages/core/src/__tests__/mission-store.test.ts | 17 ++++++++ packages/core/src/mission-store.ts | 9 ++++ 7 files changed, 106 insertions(+), 14 deletions(-) Fusion-Task-Id: FN-5714 Fusion-Task-Lineage: d84c45cf-0a0d-47a3-81c5-f99d16e03ecf
This commit is contained in:
@@ -1244,6 +1244,23 @@ describe("MissionStore", () => {
|
||||
expect(taskRow.sliceId).toBe(slice.id);
|
||||
});
|
||||
|
||||
it("throws a clear error when linking to a task not on the active board", () => {
|
||||
const mission = store.createMission({ title: "Mission" });
|
||||
const milestone = store.addMilestone(mission.id, { title: "Milestone" });
|
||||
const slice = store.addSlice(milestone.id, { title: "Slice" });
|
||||
const feature = store.addFeature(slice.id, { title: "Linkable" });
|
||||
|
||||
expect(() => store.linkFeatureToTask(feature.id, "FN-ARCHIVED")).toThrow(
|
||||
`Cannot link feature ${feature.id} to task FN-ARCHIVED: task is not on the active board (it may be archived, deleted, or never existed). Only active tasks can be linked to features.`,
|
||||
);
|
||||
|
||||
const unchanged = store.getFeature(feature.id)!;
|
||||
expect(unchanged.taskId).toBeUndefined();
|
||||
expect(unchanged.status).toBe("defined");
|
||||
expect(unchanged.loopState).toBe("idle");
|
||||
expect(unchanged.implementationAttemptCount).toBe(0);
|
||||
});
|
||||
|
||||
it("emits feature:linked event", () => {
|
||||
createTaskInDb(db, "FN-001");
|
||||
|
||||
|
||||
@@ -2085,6 +2085,15 @@ export class MissionStore extends EventEmitter<MissionStoreEvents> {
|
||||
throw new Error(`Feature ${featureId} not found`);
|
||||
}
|
||||
|
||||
const liveTask = this.db
|
||||
.prepare(`SELECT id FROM tasks WHERE id = ? AND "deletedAt" IS NULL`)
|
||||
.get(taskId) as { id: string } | undefined;
|
||||
if (!liveTask) {
|
||||
throw new Error(
|
||||
`Cannot link feature ${featureId} to task ${taskId}: task is not on the active board (it may be archived, deleted, or never existed). Only active tasks can be linked to features.`,
|
||||
);
|
||||
}
|
||||
|
||||
const linkage = this.resolveTaskLinkage(feature.sliceId);
|
||||
|
||||
// When first linking (loopState is idle or falsy), transition to implementing
|
||||
|
||||
Reference in New Issue
Block a user