FN-5764: expose mission assertion backfill through API and CLI
Expose mission assertion backfill controls and run paths across engine, API, and CLI surfaces. - add mission assertion backfill gating classification and reliability coverage for assertion-linked validation recovery - add dashboard mission routes plus legacy API coverage and e2e checks for running assertion backfill - expose new extension tool support and update Fusion skill docs/capability references - add a changeset for @runfusion/fusion and refresh mission documentation Files changed: .changeset/fn-5764-mission-backfill-assertions.md | 9 ++ docs/missions-completion-contract.md | 7 +- docs/missions.md | 7 +- packages/cli/skill/fusion/SKILL.md | 2 +- .../cli/skill/fusion/references/extension-tools.md | 9 ++ .../skill/fusion/references/fusion-capabilities.md | 1 + packages/cli/src/__tests__/extension.test.ts | 41 +++++++ packages/cli/src/extension.ts | 43 +++++++ .../dashboard/app/__tests__/api-missions.test.ts | 44 +++++++ packages/dashboard/app/api/legacy.ts | 34 ++++++ .../dashboard/src/__tests__/mission-e2e.test.ts | 127 +++++++++++++++++++++ packages/dashboard/src/mission-routes.ts | 29 +++++ .../mission-validation-trigger-gap.test.ts | 66 +++++++++++ .../workflow-step-readonly-allowlist.test.ts | 1 + packages/engine/src/gating-classifications.ts | 1 + 15 files changed, 418 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-5764 Fusion-Task-Lineage: b16123c2-7cfe-4b17-a899-4e30044a7a49
This commit is contained in:
@@ -29,7 +29,7 @@ Mission → Milestone → Slice → Feature → Task
|
||||
<!-- BEGIN: tool-categories (auto-generated by scripts/sync-fusion-skill-tools.mjs — do not edit by hand) -->
|
||||
- **Task tools** — `fn_task_create`, `fn_task_update`, `fn_task_list`, `fn_task_show`, `fn_task_attach`, `fn_task_pause`, `fn_task_unpause`, `fn_task_retry`, `fn_task_duplicate`, `fn_task_refine`, `fn_task_archive`, `fn_task_unarchive`, `fn_task_delete`, `fn_task_plan`
|
||||
- **GitHub tools** — `fn_task_import_github`, `fn_task_import_github_issue`, `fn_task_browse_github_issues`
|
||||
- **Mission tools** — `fn_mission_create`, `fn_mission_list`, `fn_mission_show`, `fn_mission_delete`, `fn_mission_update`, `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`, `fn_milestone_update`
|
||||
- **Mission tools** — `fn_mission_create`, `fn_mission_list`, `fn_mission_show`, `fn_mission_backfill_assertions`, `fn_mission_delete`, `fn_mission_update`, `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`, `fn_milestone_update`
|
||||
- **Goal tools** — `fn_goal_list`, `fn_goal_create`, `fn_goal_archive`, `fn_goal_show`
|
||||
- **Agent tools** — `fn_agent_stop`, `fn_agent_start`, `fn_agent_create`, `fn_agent_delete`, `fn_list_agents`, `fn_delegate_task`, `fn_agent_show`, `fn_agent_org_chart`
|
||||
- **Skills tools** — `fn_skills_search`, `fn_skills_install`
|
||||
|
||||
@@ -196,6 +196,15 @@ Show mission details with full hierarchy: milestones → slices → features.
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Mission ID (e.g., M-001) |
|
||||
|
||||
### fn_mission_backfill_assertions
|
||||
|
||||
Backfill mission assertions by deriving and linking one store-managed assertion for each feature without linked assertions. Supports dry-run mode.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `missionId` | string | — | Mission ID to scope backfill to (e.g., M-001) |
|
||||
| `dryRun` | boolean | — | When true, preview repairs without writing changes (default: true) |
|
||||
|
||||
### fn_mission_delete
|
||||
|
||||
Delete a mission and all its milestones, slices, and features. Cannot be undone.
|
||||
|
||||
@@ -48,6 +48,7 @@ All skill/extension tool invocations in this catalog use the public `fn_*` names
|
||||
| `fn_goal_archive` | Archive a goal by ID. |
|
||||
| `fn_goal_show` | Show full details for a single goal by ID. |
|
||||
| `fn_mission_show` | Show mission details with full hierarchy: milestones → slices → features. |
|
||||
| `fn_mission_backfill_assertions` | Backfill mission assertions by deriving and linking one store-managed assertion for each feature without linked assertions. Supports dry-run mode. |
|
||||
| `fn_mission_delete` | Delete a mission and all its milestones, slices, and features. Cannot be undone. |
|
||||
| `fn_mission_update` | Update an existing mission's title or description. Partial patches leave untouched fields intact. |
|
||||
| `fn_milestone_add` | Add a milestone to a mission. Milestones represent phases of work. |
|
||||
|
||||
@@ -215,6 +215,7 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
|
||||
"fn_mission_create",
|
||||
"fn_mission_list",
|
||||
"fn_mission_show",
|
||||
"fn_mission_backfill_assertions",
|
||||
"fn_mission_delete",
|
||||
"fn_mission_update",
|
||||
"fn_milestone_add",
|
||||
@@ -1215,6 +1216,46 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
|
||||
});
|
||||
});
|
||||
|
||||
describe("fn_mission_backfill_assertions", () => {
|
||||
it("supports dry-run and apply execution", async () => {
|
||||
const missionTool = api.tools.get("fn_mission_create")!;
|
||||
const backfillTool = api.tools.get("fn_mission_backfill_assertions")!;
|
||||
const mission = await missionTool.execute("m1", { title: "Backfill Mission" }, undefined, undefined, makeCtx(tmpDir));
|
||||
|
||||
const store = new TaskStore(tmpDir);
|
||||
await store.init();
|
||||
const missionStore = store.getMissionStore();
|
||||
const milestone = missionStore.addMilestone(mission.details.missionId, { title: "Milestone" });
|
||||
const slice = missionStore.addSlice(milestone.id, { title: "Slice" });
|
||||
const feature = missionStore.addFeature(slice.id, {
|
||||
title: "Feature needing assertion",
|
||||
acceptanceCriteria: "must be true",
|
||||
});
|
||||
expect(missionStore.listAssertionsForFeature(feature.id)).toHaveLength(0);
|
||||
|
||||
const dryRun = await backfillTool.execute(
|
||||
"bf-1",
|
||||
{ missionId: mission.details.missionId, dryRun: true },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
expect(dryRun.details.repaired.length).toBe(1);
|
||||
expect(missionStore.listAssertionsForFeature(feature.id)).toHaveLength(0);
|
||||
|
||||
const apply = await backfillTool.execute(
|
||||
"bf-2",
|
||||
{ missionId: mission.details.missionId, dryRun: false },
|
||||
undefined,
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
expect(apply.details.repaired.length).toBe(1);
|
||||
expect(missionStore.listAssertionsForFeature(feature.id)).toHaveLength(1);
|
||||
expect(apply.content[0].text).toContain("Backfill apply complete");
|
||||
});
|
||||
});
|
||||
|
||||
describe("fn_mission_delete", () => {
|
||||
it("deletes mission and confirms", async () => {
|
||||
// Create mission
|
||||
|
||||
@@ -2674,6 +2674,49 @@ export default function kbExtension(pi: ExtensionAPI) {
|
||||
},
|
||||
});
|
||||
|
||||
// ── fn_mission_backfill_assertions ─────────────────────────────
|
||||
|
||||
pi.registerTool({
|
||||
name: "fn_mission_backfill_assertions",
|
||||
label: "fn: Backfill Mission Assertions",
|
||||
description:
|
||||
"Backfill mission assertions by deriving and linking one store-managed assertion for each feature without linked assertions. Supports dry-run mode.",
|
||||
promptSnippet: "Backfill mission assertions for unlinked features",
|
||||
promptGuidelines: [
|
||||
"Use dryRun=true first to inspect proposed repairs before applying",
|
||||
"Scopes to a single mission when missionId is provided; otherwise scans all missions",
|
||||
"Creates one store-managed assertion per unlinked feature and links it",
|
||||
],
|
||||
parameters: Type.Object({
|
||||
missionId: Type.Optional(Type.String({ description: "Mission ID to scope backfill to (e.g., M-001)" })),
|
||||
dryRun: Type.Optional(Type.Boolean({ description: "When true, preview repairs without writing changes (default: true)" })),
|
||||
}),
|
||||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const store = await getStore(ctx.cwd);
|
||||
const missionStore = store.getMissionStore();
|
||||
const report = missionStore.backfillFeatureAssertions({
|
||||
missionId: params.missionId,
|
||||
dryRun: params.dryRun ?? true,
|
||||
});
|
||||
|
||||
const scopeLabel = params.missionId ? `mission ${params.missionId}` : "all missions";
|
||||
const modeLabel = (params.dryRun ?? true) ? "dry-run" : "apply";
|
||||
const lines = [
|
||||
`Backfill ${modeLabel} complete for ${scopeLabel}.`,
|
||||
`Scanned: ${report.scanned}`,
|
||||
`Already linked: ${report.alreadyLinked}`,
|
||||
`Repaired: ${report.repaired.length}`,
|
||||
`Skipped errors: ${report.skippedErrors.length}`,
|
||||
];
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: lines.join("\n") }],
|
||||
details: report,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// ── fn_mission_delete ───────────────────────────────────────────
|
||||
|
||||
pi.registerTool({
|
||||
|
||||
Reference in New Issue
Block a user