FN-5762: add mission hierarchy delete tools with linked-task guards

Add mission delete tooling across core, dashboard routes, and CLI extension surfaces.

- Add `fn_feature_delete`, `fn_slice_delete`, and `fn_milestone_delete` tools to the CLI extension with `force` override support.
- Enforce live-task linkage guards for feature/slice/milestone deletes in mission store logic and expose conflict behavior through mission routes.
- Extend coverage and docs for delete behavior, allowlist/gating metadata, and published Fusion skill/tool references.

Files changed:
 .changeset/fn-5762-feature-delete.md               |   7 +
 docs/missions.md                                   |  15 +++
 packages/cli/skill/fusion/SKILL.md                 |   2 +-
 .../cli/skill/fusion/references/extension-tools.md |  27 ++++
 .../skill/fusion/references/fusion-capabilities.md |   3 +
 packages/cli/src/__tests__/extension.test.ts       |  34 +++++
 packages/cli/src/extension.ts                      | 106 +++++++++++++++
 packages/core/src/__tests__/mission-store.test.ts  | 146 ++++++++++++++++++++-
 packages/core/src/mission-store.ts                 |  98 ++++++++++++--
 .../dashboard/src/__tests__/mission-e2e.test.ts    |  62 +++++++--
 packages/dashboard/src/mission-routes.ts           |  42 +++++-
 .../workflow-step-readonly-allowlist.test.ts       |   3 +
 packages/engine/src/gating-classifications.ts      |   3 +
 13 files changed, 514 insertions(+), 34 deletions(-)

Fusion-Task-Id: FN-5762
Fusion-Task-Lineage: 9c6eceff-dcb4-4a2b-90c6-59cb0228ca6d
This commit is contained in:
gsxdsm
2026-05-30 23:55:05 -07:00
parent 93d3a58cb8
commit 0ffe7f0604
13 changed files with 516 additions and 36 deletions

View File

@@ -220,6 +220,9 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
"fn_milestone_add",
"fn_slice_add",
"fn_feature_add",
"fn_feature_delete",
"fn_slice_delete",
"fn_milestone_delete",
"fn_slice_activate",
"fn_feature_link_task",
"fn_feature_update",
@@ -1334,6 +1337,37 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
});
});
describe("fn_feature_delete", () => {
it("deletes feature, guards linked task, and handles missing feature", async () => {
const missionTool = api.tools.get("fn_mission_create")!;
const milestoneTool = api.tools.get("fn_milestone_add")!;
const sliceTool = api.tools.get("fn_slice_add")!;
const featureTool = api.tools.get("fn_feature_add")!;
const createTaskTool = api.tools.get("fn_task_create")!;
const linkTool = api.tools.get("fn_feature_link_task")!;
const deleteTool = api.tools.get("fn_feature_delete")!;
const mission = await missionTool.execute("m1", { title: "Mission" }, undefined, undefined, makeCtx(tmpDir));
const milestone = await milestoneTool.execute("ms1", { missionId: mission.details.missionId, title: "Milestone" }, undefined, undefined, makeCtx(tmpDir));
const slice = await sliceTool.execute("sl1", { milestoneId: milestone.details.milestoneId, title: "Slice" }, undefined, undefined, makeCtx(tmpDir));
const feature = await featureTool.execute("f1", { sliceId: slice.details.sliceId, title: "Feature" }, undefined, undefined, makeCtx(tmpDir));
const task = await createTaskTool.execute("t1", { description: "Task for feature" }, undefined, undefined, makeCtx(tmpDir));
await linkTool.execute("l1", { featureId: feature.details.featureId, taskId: task.details.taskId }, undefined, undefined, makeCtx(tmpDir));
const guarded = await deleteTool.execute("d1", { featureId: feature.details.featureId }, undefined, undefined, makeCtx(tmpDir));
expect(guarded.isError).toBe(true);
expect(guarded.content[0].text).toContain("linked to task");
const forced = await deleteTool.execute("d2", { featureId: feature.details.featureId, force: true }, undefined, undefined, makeCtx(tmpDir));
expect(forced.isError).not.toBe(true);
expect(forced.content[0].text).toContain("Deleted");
const missing = await deleteTool.execute("d3", { featureId: feature.details.featureId }, undefined, undefined, makeCtx(tmpDir));
expect(missing.isError).toBe(true);
expect(missing.content[0].text).toContain("not found");
});
});
describe("fn_slice_activate", () => {
it("returns error when slice is already active", async () => {
const missionTool = api.tools.get("fn_mission_create")!;