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:
5
.changeset/fn-5713-mission-update-tool.md
Normal file
5
.changeset/fn-5713-mission-update-tool.md
Normal 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.
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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. |
|
||||||
|
|||||||
@@ -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")!;
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user