FN-5713: add mission update tool and docs

Add mission-update support to Fusion tooling and document it across mission references.

- register new `fn_mission_update` extension tool to patch mission title/description with validation and not-found handling
- add extension tests covering successful mission updates and error cases
- allowlist `fn_mission_update` in workflow-step readonly gating policy tests and classifications
- update Fusion skill and mission docs to describe mission updates and extension tool usage
- add a changeset for `@runfusion/fusion` patch release

Files changed:
 .changeset/fn-5713-mission-update-tool.md          |  5 ++
 docs/missions.md                                   | 13 ++++
 packages/cli/skill/fusion/SKILL.md                 |  2 +-
 packages/cli/skill/fusion/references/extension-tools.md | 10 +++
 packages/cli/skill/fusion/references/fusion-capabilities.md |  1 +
 packages/cli/src/__tests__/extension.test.ts       | 71 ++++++++++++++++++++++
 packages/cli/src/extension.ts                      | 69 +++++++++++++++++++++
 packages/engine/src/__tests__/workflow-step-readonly-allowlist.test.ts |  1 +
 packages/engine/src/gating-classifications.ts      |  1 +
 9 files changed, 172 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-5713
Fusion-Task-Lineage: e491973a-2d5d-4ef8-bf13-39b3669918ee
This commit is contained in:
gsxdsm
2026-05-30 07:17:22 -07:00
parent f730451d1a
commit 3b594879a8
9 changed files with 172 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": minor
---
Add a new `fn_mission_update` extension tool to patch mission `title`/`description` without recreating missions, and classify it as a mission mutation tool in readonly/permanent gating policy.

View File

@@ -77,6 +77,7 @@ The canonical per-parameter tool reference lives in `packages/cli/skill/fusion/r
| `fn_mission_list` | List missions and their current status. | | `fn_mission_list` | List missions and their current status. |
| `fn_mission_show` | Show mission details with milestone/slice/feature hierarchy, including milestone/feature acceptance criteria and slice verification when present. | | `fn_mission_show` | Show mission details with milestone/slice/feature hierarchy, including milestone/feature acceptance criteria and slice verification when present. |
| `fn_mission_delete` | Delete a mission and its hierarchy. | | `fn_mission_delete` | Delete a mission and its hierarchy. |
| `fn_mission_update` | Update mission title/description using partial patches. |
| `fn_milestone_add` | Add a milestone to a mission. | | `fn_milestone_add` | Add a milestone to a mission. |
| `fn_milestone_update` | Update milestone fields using partial patches. | | `fn_milestone_update` | Update milestone fields using partial patches. |
| `fn_slice_add` | Add a slice to a milestone. | | `fn_slice_add` | Add a slice to a milestone. |
@@ -85,6 +86,18 @@ The canonical per-parameter tool reference lives in `packages/cli/skill/fusion/r
| `fn_feature_update` | Update feature fields using partial patches. | | `fn_feature_update` | Update feature fields using partial patches. |
| `fn_feature_link_task` | Link a feature to a task for implementation. | | `fn_feature_link_task` | Link a feature to a task for implementation. |
### fn_mission_update
Updates an existing mission's `title` or `description`. Partial patches leave untouched fields intact — fields omitted from the call are not modified.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `id` | string | ✓ | Mission ID to update (e.g., `M-001`) |
| `title` | string | — | Updated mission title |
| `description` | string | — | Updated mission description |
Use this to reconcile mission narrative/state text without recreating the mission.
### fn_milestone_update ### fn_milestone_update
Updates an existing milestone's `title`, `description`, or `acceptanceCriteria`. Partial patches leave untouched fields intact — fields omitted from the call are not modified. Updates an existing milestone's `title`, `description`, or `acceptanceCriteria`. Partial patches leave untouched fields intact — fields omitted from the call are not modified.

View File

@@ -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) --> <!-- 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` - **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` - **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_milestone_add`, `fn_slice_add`, `fn_feature_add`, `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_delete`, `fn_mission_update`, `fn_milestone_add`, `fn_slice_add`, `fn_feature_add`, `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` - **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` - **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` - **Skills tools** — `fn_skills_search`, `fn_skills_install`

View File

@@ -204,6 +204,16 @@ Delete a mission and all its milestones, slices, and features. Cannot be undone.
|-----------|------|----------|-------------| |-----------|------|----------|-------------|
| `id` | string | ✓ | Mission ID to delete (e.g., M-001) | | `id` | string | ✓ | Mission ID to delete (e.g., M-001) |
### fn_mission_update
Update an existing mission's title or description. Partial patches leave untouched fields intact.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | ✓ | Mission ID to update (e.g., M-001) |
| `title` | string | — | Updated mission title |
| `description` | string | — | Updated mission description |
### fn_milestone_add ### fn_milestone_add
Add a milestone to a mission. Milestones represent phases of work. Add a milestone to a mission. Milestones represent phases of work.

View File

@@ -49,6 +49,7 @@ All skill/extension tool invocations in this catalog use the public `fn_*` names
| `fn_goal_show` | Show full details for a single 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_show` | Show mission details with full hierarchy: milestones → slices → features. |
| `fn_mission_delete` | Delete a mission and all its milestones, slices, and features. Cannot be undone. | | `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. | | `fn_milestone_add` | Add a milestone to a mission. Milestones represent phases of work. |
| `fn_slice_add` | Add a slice to a milestone. Slices are work units that can be activated for implementation. | | `fn_slice_add` | Add a slice to a milestone. Slices are work units that can be activated for implementation. |
| `fn_feature_add` | Add a feature to a slice. Features are deliverables that can be linked to tasks. | | `fn_feature_add` | Add a feature to a slice. Features are deliverables that can be linked to tasks. |

View File

@@ -216,6 +216,7 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
"fn_mission_list", "fn_mission_list",
"fn_mission_show", "fn_mission_show",
"fn_mission_delete", "fn_mission_delete",
"fn_mission_update",
"fn_milestone_add", "fn_milestone_add",
"fn_slice_add", "fn_slice_add",
"fn_feature_add", "fn_feature_add",
@@ -1791,6 +1792,76 @@ describe.skipIf(!SHOULD_RUN_LEGACY_EXTENSION_INTEGRATION)("fn pi extension (lega
}); });
}); });
describe("fn_mission_update", () => {
it("updates mission description and persists", async () => {
const missionTool = api.tools.get("fn_mission_create")!;
const updateTool = api.tools.get("fn_mission_update")!;
const mission = await missionTool.execute(
"m1",
{ title: "Mission", description: "Original description" },
undefined,
undefined,
makeCtx(tmpDir),
);
const result = await updateTool.execute(
"mu1",
{ id: mission.details.missionId, description: " Updated description " },
undefined,
undefined,
makeCtx(tmpDir),
);
expect(result.content[0].text).toContain("Updated");
expect(result.details.missionId).toBe(mission.details.missionId);
expect(result.details.description).toBe("Updated description");
const store = new TaskStore(tmpDir);
await store.init();
const persisted = store.getMissionStore().getMission(mission.details.missionId);
expect(persisted?.description).toBe("Updated description");
});
it("returns error when mission not found", async () => {
const updateTool = api.tools.get("fn_mission_update")!;
const result = await updateTool.execute(
"mu2",
{ id: "M-999", title: "Updated" },
undefined,
undefined,
makeCtx(tmpDir),
);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("Mission M-999 not found");
});
it("returns error when no fields supplied", async () => {
const missionTool = api.tools.get("fn_mission_create")!;
const updateTool = api.tools.get("fn_mission_update")!;
const mission = await missionTool.execute(
"m1",
{ title: "Mission", description: "Original description" },
undefined,
undefined,
makeCtx(tmpDir),
);
const result = await updateTool.execute(
"mu3",
{ id: mission.details.missionId },
undefined,
undefined,
makeCtx(tmpDir),
);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("No fields to update");
});
});
describe("fn_milestone_update", () => { describe("fn_milestone_update", () => {
it("patches title, description, and acceptanceCriteria", async () => { it("patches title, description, and acceptanceCriteria", async () => {
const missionTool = api.tools.get("fn_mission_create")!; const missionTool = api.tools.get("fn_mission_create")!;

View File

@@ -2709,6 +2709,75 @@ export default function kbExtension(pi: ExtensionAPI) {
}, },
}); });
// ── fn_mission_update ───────────────────────────────────────────
pi.registerTool({
name: "fn_mission_update",
label: "fn: Update Mission",
description:
"Update an existing mission's title or description. " +
"Partial patches leave untouched fields intact.",
promptSnippet: "Update an existing mission",
promptGuidelines: [
"Use to revise mission framing without re-creating the mission",
"Mission hierarchy and ordering are preserved",
"Provide only the fields you want to change",
],
parameters: Type.Object({
id: Type.String({ description: "Mission ID to update (e.g., M-001)" }),
title: Type.Optional(Type.String({ description: "Updated mission title" })),
description: Type.Optional(Type.String({ description: "Updated mission description" })),
}),
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
const store = await getStore(ctx.cwd);
const missionStore = store.getMissionStore();
const existingMission = missionStore.getMission(params.id);
if (!existingMission) {
return {
content: [{ type: "text", text: `Mission ${params.id} not found` }],
isError: true,
details: { error: "Mission not found" },
};
}
const updates: { title?: string; description?: string } = {};
if ("title" in params) {
updates.title = params.title?.trim();
}
if ("description" in params) {
updates.description = params.description?.trim();
}
if (Object.keys(updates).length === 0) {
return {
content: [
{
type: "text",
text: "No fields to update (provide at least one of: title, description)",
},
],
isError: true,
details: { error: "No fields to update" },
};
}
const mission = missionStore.updateMission(params.id, updates);
return {
content: [{ type: "text", text: `Updated ${mission.id}: "${mission.title}"` }],
details: {
missionId: mission.id,
title: mission.title,
description: mission.description,
status: mission.status,
},
};
},
});
// ── fn_milestone_add ──────────────────────────────────────────── // ── fn_milestone_add ────────────────────────────────────────────
pi.registerTool({ pi.registerTool({

View File

@@ -42,6 +42,7 @@ describe("workflow-step readonly allowlist policy", () => {
"fn_task_plan", "fn_task_plan",
"fn_mission_create", "fn_mission_create",
"fn_mission_delete", "fn_mission_delete",
"fn_mission_update",
"fn_milestone_add", "fn_milestone_add",
"fn_slice_add", "fn_slice_add",
"fn_feature_add", "fn_feature_add",

View File

@@ -29,6 +29,7 @@ const PERMANENT_TASK_AGENT_ONLY_TOOLS = [
"fn_task_plan", "fn_task_plan",
"fn_mission_create", "fn_mission_create",
"fn_mission_delete", "fn_mission_delete",
"fn_mission_update",
"fn_milestone_add", "fn_milestone_add",
"fn_slice_add", "fn_slice_add",
"fn_feature_add", "fn_feature_add",