feat(FN-1216): rename extension tools to fn_* across CLI and docs

- Rename all pi extension tool registrations from kb_* to fn_* across task, mission, and agent tool families
- Update extension and skill-sync tests plus Fusion skill docs/workflows to assert and document the new fn_* tool names
- Align product-identity strings across core, dashboard, and CLI references, including GitHub import examples, User-Agent headers, and terminal TERM_PROGRAM
- Add a @gsxdsm/fusion minor changeset describing the extension tool-prefix rename
This commit is contained in:
gsxdsm
2026-04-08 14:26:30 -07:00
parent 375d46d31b
commit e78d05e02c
32 changed files with 371 additions and 366 deletions

View File

@@ -74,36 +74,36 @@ describe("fn pi extension", () => {
describe("registration", () => {
it("registers all expected tools", () => {
const expected = [
"kb_task_create",
"kb_task_update",
"kb_task_list",
"kb_task_show",
"kb_task_attach",
"kb_task_pause",
"kb_task_unpause",
"kb_task_retry",
"kb_task_duplicate",
"kb_task_refine",
"kb_task_import_github",
"kb_task_import_github_issue",
"kb_task_browse_github_issues",
"kb_task_archive",
"kb_task_unarchive",
"kb_task_delete",
"kb_task_plan",
"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_import_github",
"fn_task_import_github_issue",
"fn_task_browse_github_issues",
"fn_task_archive",
"fn_task_unarchive",
"fn_task_delete",
"fn_task_plan",
// Mission tools
"kb_mission_create",
"kb_mission_list",
"kb_mission_show",
"kb_mission_delete",
"kb_milestone_add",
"kb_slice_add",
"kb_feature_add",
"kb_slice_activate",
"kb_feature_link_task",
"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",
// Agent tools
"kb_agent_stop",
"kb_agent_start",
"fn_agent_stop",
"fn_agent_start",
];
for (const name of expected) {
@@ -113,10 +113,10 @@ describe("fn pi extension", () => {
});
it("does not register engine-internal tools", () => {
expect(api.tools.has("kb_task_move")).toBe(false);
expect(api.tools.has("kb_task_update_step")).toBe(false);
expect(api.tools.has("kb_task_log")).toBe(false);
expect(api.tools.has("kb_task_merge")).toBe(false);
expect(api.tools.has("fn_task_move")).toBe(false);
expect(api.tools.has("fn_task_update_step")).toBe(false);
expect(api.tools.has("fn_task_log")).toBe(false);
expect(api.tools.has("fn_task_merge")).toBe(false);
});
it("registers the /fn command", () => {
@@ -129,9 +129,9 @@ describe("fn pi extension", () => {
});
});
describe("kb_task_create", () => {
describe("fn_task_create", () => {
it("creates a task and returns its ID", async () => {
const tool = api.tools.get("kb_task_create")!;
const tool = api.tools.get("fn_task_create")!;
const result = await tool.execute(
"call-1",
{ description: "Fix the login button" },
@@ -148,7 +148,7 @@ describe("fn pi extension", () => {
});
it("creates a task with dependencies", async () => {
const tool = api.tools.get("kb_task_create")!;
const tool = api.tools.get("fn_task_create")!;
await tool.execute(
"call-1",
{ description: "First task" },
@@ -171,7 +171,7 @@ describe("fn pi extension", () => {
});
it("creates a task with assigned agent ID", async () => {
const tool = api.tools.get("kb_task_create")!;
const tool = api.tools.get("fn_task_create")!;
const result = await tool.execute(
"call-1",
{ description: "Task with assignee", agentId: "agent-abc123" },
@@ -185,13 +185,13 @@ describe("fn pi extension", () => {
expect(result.content[0].text).toContain("Assigned to: agent-abc123");
// Verify persistence via show
const showTool = api.tools.get("kb_task_show")!;
const showTool = api.tools.get("fn_task_show")!;
const show = await showTool.execute("s1", { id: "FN-001" }, undefined, undefined, makeCtx(tmpDir));
expect(show.details.task.assignedAgentId).toBe("agent-abc123");
});
it("creates a task without assigned agent ID by default", async () => {
const tool = api.tools.get("kb_task_create")!;
const tool = api.tools.get("fn_task_create")!;
const result = await tool.execute(
"call-1",
{ description: "Task without assignee" },
@@ -206,12 +206,12 @@ describe("fn pi extension", () => {
});
});
describe("kb_task_update", () => {
describe("fn_task_update", () => {
it("updates task title", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute("c1", { description: "Original" }, undefined, undefined, makeCtx(tmpDir));
const updateTool = api.tools.get("kb_task_update")!;
const updateTool = api.tools.get("fn_task_update")!;
const result = await updateTool.execute(
"u1",
{ id: "FN-001", title: "New Title" },
@@ -225,16 +225,16 @@ describe("fn pi extension", () => {
expect(result.details.updatedFields).toEqual(["title"]);
// Verify via show
const showTool = api.tools.get("kb_task_show")!;
const showTool = api.tools.get("fn_task_show")!;
const show = await showTool.execute("s1", { id: "FN-001" }, undefined, undefined, makeCtx(tmpDir));
expect(show.content[0].text).toContain("New Title");
});
it("updates task description", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute("c1", { description: "Original desc" }, undefined, undefined, makeCtx(tmpDir));
const updateTool = api.tools.get("kb_task_update")!;
const updateTool = api.tools.get("fn_task_update")!;
const result = await updateTool.execute(
"u1",
{ id: "FN-001", description: "Updated description" },
@@ -248,11 +248,11 @@ describe("fn pi extension", () => {
});
it("updates task dependencies", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute("c1", { description: "First" }, undefined, undefined, makeCtx(tmpDir));
await createTool.execute("c2", { description: "Second" }, undefined, undefined, makeCtx(tmpDir));
const updateTool = api.tools.get("kb_task_update")!;
const updateTool = api.tools.get("fn_task_update")!;
const result = await updateTool.execute(
"u1",
{ id: "FN-002", depends: ["FN-001"] },
@@ -266,10 +266,10 @@ describe("fn pi extension", () => {
});
it("updates multiple fields at once", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute("c1", { description: "Original" }, undefined, undefined, makeCtx(tmpDir));
const updateTool = api.tools.get("kb_task_update")!;
const updateTool = api.tools.get("fn_task_update")!;
const result = await updateTool.execute(
"u1",
{ id: "FN-001", title: "New Title", description: "New desc", depends: [] },
@@ -282,10 +282,10 @@ describe("fn pi extension", () => {
});
it("updates task assigned agent ID", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute("c1", { description: "Original" }, undefined, undefined, makeCtx(tmpDir));
const updateTool = api.tools.get("kb_task_update")!;
const updateTool = api.tools.get("fn_task_update")!;
const result = await updateTool.execute(
"u1",
{ id: "FN-001", agentId: "agent-abc123" },
@@ -298,13 +298,13 @@ describe("fn pi extension", () => {
expect(result.content[0].text).toContain("agentId");
expect(result.details.updatedFields).toEqual(["agentId"]);
const showTool = api.tools.get("kb_task_show")!;
const showTool = api.tools.get("fn_task_show")!;
const show = await showTool.execute("s1", { id: "FN-001" }, undefined, undefined, makeCtx(tmpDir));
expect(show.details.task.assignedAgentId).toBe("agent-abc123");
});
it("clears task assigned agent ID with null", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute(
"c1",
{ description: "Original", agentId: "agent-abc123" },
@@ -313,7 +313,7 @@ describe("fn pi extension", () => {
makeCtx(tmpDir),
);
const updateTool = api.tools.get("kb_task_update")!;
const updateTool = api.tools.get("fn_task_update")!;
const result = await updateTool.execute(
"u1",
{ id: "FN-001", agentId: null },
@@ -326,13 +326,13 @@ describe("fn pi extension", () => {
expect(result.content[0].text).toContain("agentId");
expect(result.details.updatedFields).toEqual(["agentId"]);
const showTool = api.tools.get("kb_task_show")!;
const showTool = api.tools.get("fn_task_show")!;
const show = await showTool.execute("s1", { id: "FN-001" }, undefined, undefined, makeCtx(tmpDir));
expect(show.details.task.assignedAgentId).toBeUndefined();
});
it("returns error when task not found", async () => {
const updateTool = api.tools.get("kb_task_update")!;
const updateTool = api.tools.get("fn_task_update")!;
const result = await updateTool.execute(
"u1",
{ id: "FN-999", title: "Nope" },
@@ -346,9 +346,9 @@ describe("fn pi extension", () => {
});
});
describe("kb_task_list", () => {
describe("fn_task_list", () => {
it("returns empty message when no tasks", async () => {
const tool = api.tools.get("kb_task_list")!;
const tool = api.tools.get("fn_task_list")!;
const result = await tool.execute(
"call-1",
{},
@@ -362,7 +362,7 @@ describe("fn pi extension", () => {
});
it("lists tasks grouped by column", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute(
"c1",
{ description: "Task A" },
@@ -378,7 +378,7 @@ describe("fn pi extension", () => {
makeCtx(tmpDir),
);
const listTool = api.tools.get("kb_task_list")!;
const listTool = api.tools.get("fn_task_list")!;
const result = await listTool.execute(
"call-1",
{},
@@ -394,7 +394,7 @@ describe("fn pi extension", () => {
});
it("filters by column", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute(
"c1",
{ description: "Task A" },
@@ -403,7 +403,7 @@ describe("fn pi extension", () => {
makeCtx(tmpDir),
);
const listTool = api.tools.get("kb_task_list")!;
const listTool = api.tools.get("fn_task_list")!;
const triageResult = await listTool.execute(
"call-1",
{ column: "triage" },
@@ -425,7 +425,7 @@ describe("fn pi extension", () => {
});
it("respects per-column limit", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
for (let i = 0; i < 5; i++) {
await createTool.execute(
`c${i}`,
@@ -436,7 +436,7 @@ describe("fn pi extension", () => {
);
}
const listTool = api.tools.get("kb_task_list")!;
const listTool = api.tools.get("fn_task_list")!;
const result = await listTool.execute(
"call-1",
{ limit: 2 },
@@ -453,9 +453,9 @@ describe("fn pi extension", () => {
});
});
describe("kb_task_show", () => {
describe("fn_task_show", () => {
it("shows task details", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute(
"c1",
{ description: "Implement caching layer" },
@@ -464,7 +464,7 @@ describe("fn pi extension", () => {
makeCtx(tmpDir),
);
const showTool = api.tools.get("kb_task_show")!;
const showTool = api.tools.get("fn_task_show")!;
const result = await showTool.execute(
"call-1",
{ id: "FN-001" },
@@ -481,9 +481,9 @@ describe("fn pi extension", () => {
});
});
describe("kb_task_attach", () => {
describe("fn_task_attach", () => {
it("attaches a file to a task", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute(
"c1",
{ description: "A task" },
@@ -495,7 +495,7 @@ describe("fn pi extension", () => {
const testFile = join(tmpDir, "test.txt");
await writeFile(testFile, "hello world");
const attachTool = api.tools.get("kb_task_attach")!;
const attachTool = api.tools.get("fn_task_attach")!;
const result = await attachTool.execute(
"call-1",
{ id: "FN-001", path: "test.txt" },
@@ -511,7 +511,7 @@ describe("fn pi extension", () => {
});
it("rejects unsupported file types", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute(
"c1",
{ description: "A task" },
@@ -523,7 +523,7 @@ describe("fn pi extension", () => {
const testFile = join(tmpDir, "file.exe");
await writeFile(testFile, "binary");
const attachTool = api.tools.get("kb_task_attach")!;
const attachTool = api.tools.get("fn_task_attach")!;
await expect(
attachTool.execute(
"call-1",
@@ -536,9 +536,9 @@ describe("fn pi extension", () => {
});
});
describe("kb_task_pause / unpause", () => {
describe("fn_task_pause / unpause", () => {
it("pauses and unpauses a task", async () => {
const createTool = api.tools.get("kb_task_create")!;
const createTool = api.tools.get("fn_task_create")!;
await createTool.execute(
"c1",
{ description: "A task" },
@@ -547,7 +547,7 @@ describe("fn pi extension", () => {
makeCtx(tmpDir),
);
const pauseTool = api.tools.get("kb_task_pause")!;
const pauseTool = api.tools.get("fn_task_pause")!;
const pauseResult = await pauseTool.execute(
"call-1",
{ id: "FN-001" },
@@ -558,7 +558,7 @@ describe("fn pi extension", () => {
expect(pauseResult.content[0].text).toContain("Paused FN-001");
// Verify it's paused
const showTool = api.tools.get("kb_task_show")!;
const showTool = api.tools.get("fn_task_show")!;
const show = await showTool.execute(
"call-2",
{ id: "FN-001" },
@@ -569,7 +569,7 @@ describe("fn pi extension", () => {
expect(show.content[0].text).toContain("PAUSED");
// Unpause
const unpauseTool = api.tools.get("kb_task_unpause")!;
const unpauseTool = api.tools.get("fn_task_unpause")!;
const unpauseResult = await unpauseTool.execute(
"call-3",
{ id: "FN-001" },
@@ -581,9 +581,9 @@ describe("fn pi extension", () => {
});
});
describe("kb_mission_create", () => {
describe("fn_mission_create", () => {
it("creates mission and returns mission data", async () => {
const tool = api.tools.get("kb_mission_create")!;
const tool = api.tools.get("fn_mission_create")!;
const result = await tool.execute(
"call-1",
{ title: "Test Mission", description: "Test description", autoAdvance: true },
@@ -601,10 +601,10 @@ describe("fn pi extension", () => {
});
});
describe("kb_mission_list", () => {
describe("fn_mission_list", () => {
it("returns formatted list of missions", async () => {
// First create a mission
const createTool = api.tools.get("kb_mission_create")!;
const createTool = api.tools.get("fn_mission_create")!;
await createTool.execute(
"c1",
{ title: "Mission A" },
@@ -613,7 +613,7 @@ describe("fn pi extension", () => {
makeCtx(tmpDir),
);
const listTool = api.tools.get("kb_mission_list")!;
const listTool = api.tools.get("fn_mission_list")!;
const result = await listTool.execute(
"call-1",
{},
@@ -628,10 +628,10 @@ describe("fn pi extension", () => {
});
});
describe("kb_mission_show", () => {
describe("fn_mission_show", () => {
it("returns mission with hierarchy", async () => {
// Create mission
const createTool = api.tools.get("kb_mission_create")!;
const createTool = api.tools.get("fn_mission_create")!;
const created = await createTool.execute(
"c1",
{ title: "Test Mission" },
@@ -640,7 +640,7 @@ describe("fn pi extension", () => {
makeCtx(tmpDir),
);
const showTool = api.tools.get("kb_mission_show")!;
const showTool = api.tools.get("fn_mission_show")!;
const result = await showTool.execute(
"call-1",
{ id: created.details.missionId },
@@ -654,7 +654,7 @@ describe("fn pi extension", () => {
});
it("returns error when mission not found", async () => {
const showTool = api.tools.get("kb_mission_show")!;
const showTool = api.tools.get("fn_mission_show")!;
const result = await showTool.execute(
"call-1",
{ id: "M-999" },
@@ -668,10 +668,10 @@ describe("fn pi extension", () => {
});
});
describe("kb_mission_delete", () => {
describe("fn_mission_delete", () => {
it("deletes mission and confirms", async () => {
// Create mission
const createTool = api.tools.get("kb_mission_create")!;
const createTool = api.tools.get("fn_mission_create")!;
const created = await createTool.execute(
"c1",
{ title: "Mission to Delete" },
@@ -680,7 +680,7 @@ describe("fn pi extension", () => {
makeCtx(tmpDir),
);
const deleteTool = api.tools.get("kb_mission_delete")!;
const deleteTool = api.tools.get("fn_mission_delete")!;
const result = await deleteTool.execute(
"call-1",
{ id: created.details.missionId },
@@ -694,10 +694,10 @@ describe("fn pi extension", () => {
});
});
describe("kb_milestone_add", () => {
describe("fn_milestone_add", () => {
it("creates a milestone in the mission store", async () => {
const missionTool = api.tools.get("kb_mission_create")!;
const milestoneTool = api.tools.get("kb_milestone_add")!;
const missionTool = api.tools.get("fn_mission_create")!;
const milestoneTool = api.tools.get("fn_milestone_add")!;
const mission = await missionTool.execute("m1", { title: "Mission" }, undefined, undefined, makeCtx(tmpDir));
const result = await milestoneTool.execute(
@@ -718,11 +718,11 @@ describe("fn pi extension", () => {
});
});
describe("kb_slice_add", () => {
describe("fn_slice_add", () => {
it("creates a slice in the mission store", async () => {
const missionTool = api.tools.get("kb_mission_create")!;
const milestoneTool = api.tools.get("kb_milestone_add")!;
const sliceTool = api.tools.get("kb_slice_add")!;
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 mission = await missionTool.execute("m1", { title: "Mission" }, undefined, undefined, makeCtx(tmpDir));
const milestone = await milestoneTool.execute(
"ms1",
@@ -750,12 +750,12 @@ describe("fn pi extension", () => {
});
});
describe("kb_feature_add", () => {
describe("fn_feature_add", () => {
it("creates a feature in the mission store", async () => {
const missionTool = api.tools.get("kb_mission_create")!;
const milestoneTool = api.tools.get("kb_milestone_add")!;
const sliceTool = api.tools.get("kb_slice_add")!;
const featureTool = api.tools.get("kb_feature_add")!;
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 mission = await missionTool.execute("m1", { title: "Mission" }, undefined, undefined, makeCtx(tmpDir));
const milestone = await milestoneTool.execute(
"ms1",
@@ -790,12 +790,12 @@ describe("fn pi extension", () => {
});
});
describe("kb_slice_activate", () => {
describe("fn_slice_activate", () => {
it("returns error when slice is already active", async () => {
const missionTool = api.tools.get("kb_mission_create")!;
const milestoneTool = api.tools.get("kb_milestone_add")!;
const sliceTool = api.tools.get("kb_slice_add")!;
const activateTool = api.tools.get("kb_slice_activate")!;
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 activateTool = api.tools.get("fn_slice_activate")!;
const mission = await missionTool.execute("m1", { title: "Mission" }, undefined, undefined, makeCtx(tmpDir));
const milestone = await milestoneTool.execute(
@@ -821,10 +821,10 @@ describe("fn pi extension", () => {
});
it("activates slice and updates status", async () => {
const missionTool = api.tools.get("kb_mission_create")!;
const milestoneTool = api.tools.get("kb_milestone_add")!;
const sliceTool = api.tools.get("kb_slice_add")!;
const activateTool = api.tools.get("kb_slice_activate")!;
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 activateTool = api.tools.get("fn_slice_activate")!;
const mission = await missionTool.execute("m1", { title: "Mission" }, undefined, undefined, makeCtx(tmpDir));
const milestone = await milestoneTool.execute(
@@ -860,13 +860,13 @@ describe("fn pi extension", () => {
});
});
describe("kb_feature_link_task", () => {
describe("fn_feature_link_task", () => {
it("returns error when task is missing", async () => {
const missionTool = api.tools.get("kb_mission_create")!;
const milestoneTool = api.tools.get("kb_milestone_add")!;
const sliceTool = api.tools.get("kb_slice_add")!;
const featureTool = api.tools.get("kb_feature_add")!;
const linkTool = api.tools.get("kb_feature_link_task")!;
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 linkTool = api.tools.get("fn_feature_link_task")!;
const mission = await missionTool.execute("m1", { title: "Mission" }, undefined, undefined, makeCtx(tmpDir));
const milestone = await milestoneTool.execute(
@@ -904,12 +904,12 @@ describe("fn pi extension", () => {
});
it("links feature to task", async () => {
const missionTool = api.tools.get("kb_mission_create")!;
const milestoneTool = api.tools.get("kb_milestone_add")!;
const sliceTool = api.tools.get("kb_slice_add")!;
const featureTool = api.tools.get("kb_feature_add")!;
const createTaskTool = api.tools.get("kb_task_create")!;
const linkTool = api.tools.get("kb_feature_link_task")!;
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 mission = await missionTool.execute("m1", { title: "Mission" }, undefined, undefined, makeCtx(tmpDir));
const milestone = await milestoneTool.execute(

View File

@@ -14,19 +14,19 @@ const extensionPath = resolve(cliRoot, "src/extension.ts");
*/
function getExtensionToolNames(): string[] {
const src = readFileSync(extensionPath, "utf-8");
const matches = [...src.matchAll(/name:\s*"(kb_[a-z_]+)"/g)];
const matches = [...src.matchAll(/name:\s*"(fn_[a-z_]+)"/g)];
return matches.map((m) => m[1]).sort();
}
/**
* Extract tool names documented in extension-tools.md (### kb_* headings).
* Extract tool names documented in extension-tools.md (### fn_* headings).
*/
function getDocumentedToolNames(): string[] {
const doc = readFileSync(
resolve(skillDir, "references/extension-tools.md"),
"utf-8",
);
const matches = [...doc.matchAll(/^### (kb_[a-z_]+)/gm)];
const matches = [...doc.matchAll(/^### (fn_[a-z_]+)/gm)];
return matches.map((m) => m[1]).sort();
}
@@ -35,7 +35,7 @@ function getDocumentedToolNames(): string[] {
*/
function getSkillMdToolNames(): string[] {
const doc = readFileSync(resolve(skillDir, "SKILL.md"), "utf-8");
const matches = [...doc.matchAll(/`(kb_[a-z_]+)`/g)];
const matches = [...doc.matchAll(/`(fn_[a-z_]+)`/g)];
// Deduplicate
return [...new Set(matches.map((m) => m[1]))].sort();
}
@@ -48,7 +48,7 @@ function getCapabilitiesToolNames(): string[] {
resolve(skillDir, "references/fusion-capabilities.md"),
"utf-8",
);
const matches = [...doc.matchAll(/\| `(kb_[a-z_]+)` \|/g)];
const matches = [...doc.matchAll(/\| `(fn_[a-z_]+)` \|/g)];
return matches.map((m) => m[1]).sort();
}

View File

@@ -695,7 +695,7 @@ export async function runTaskImportGitHubInteractive(
const match = ownerRepo.match(/^([^/]+)\/([^/]+)$/);
if (!match) {
console.error(`Invalid owner/repo format: ${ownerRepo}`);
console.error(`Expected format: owner/repo (e.g., dustinbyrne/kb)`);
console.error(`Expected format: owner/repo (e.g., dustinbyrne/fusion)`);
process.exit(1);
}
@@ -861,7 +861,7 @@ export async function fetchGitHubIssues(
const headers: Record<string, string> = {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "kb-cli/1.0",
"User-Agent": "fn/1.0",
};
if (token) {
@@ -913,7 +913,7 @@ export async function runTaskImportFromGitHub(
const match = ownerRepo.match(/^([^/]+)\/([^/]+)$/);
if (!match) {
console.error(`Invalid owner/repo format: ${ownerRepo}`);
console.error(`Expected format: owner/repo (e.g., dustinbyrne/kb)`);
console.error(`Expected format: owner/repo (e.g., dustinbyrne/fusion)`);
process.exit(1);
}

View File

@@ -54,10 +54,10 @@ function formatTaskLine(t: Task): string {
// ── Extension entry point ──────────────────────────────────────────
export default function kbExtension(pi: ExtensionAPI) {
// ── kb_task_create ───────────────────────────────────────────────
// ── fn_task_create ───────────────────────────────────────────────
pi.registerTool({
name: "kb_task_create",
name: "fn_task_create",
label: "KB: Create Task",
description:
"Create a new task on the Fusion task board. The task enters the triage column " +
@@ -65,7 +65,7 @@ export default function kbExtension(pi: ExtensionAPI) {
"file scope, and acceptance criteria.",
promptSnippet: "Create a task on the Fusion AI-orchestrated task board",
promptGuidelines: [
"Use kb_task_create for task tracking — be descriptive so the triage agent can write a good spec.",
"Use fn_task_create for task tracking — be descriptive so the triage agent can write a good spec.",
"Include the problem AND desired outcome. For bugs, describe current vs expected behavior.",
],
parameters: Type.Object({
@@ -121,17 +121,17 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_update ────────────────────────────────────────────────
// ── fn_task_update ────────────────────────────────────────────────
pi.registerTool({
name: "kb_task_update",
name: "fn_task_update",
label: "KB: Update Task",
description:
"Update fields on an existing task. Supports modifying the title, " +
"description, dependencies, and assigned agent after task creation.",
promptSnippet: "Update fields on an existing Fusion task",
promptGuidelines: [
"Use kb_task_update to modify task title, description, dependencies, or assigned agent after creation.",
"Use fn_task_update to modify task title, description, dependencies, or assigned agent after creation.",
"At least one field must be provided to update.",
],
parameters: Type.Object({
@@ -211,10 +211,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_list ─────────────────────────────────────────────────
// ── fn_task_list ─────────────────────────────────────────────────
pi.registerTool({
name: "kb_task_list",
name: "fn_task_list",
label: "KB: List Tasks",
description: "List all tasks on the Fusion board, grouped by column.",
promptSnippet: "List all tasks on the Fusion board grouped by column",
@@ -269,10 +269,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_show ─────────────────────────────────────────────────
// ── fn_task_show ─────────────────────────────────────────────────
pi.registerTool({
name: "kb_task_show",
name: "fn_task_show",
label: "KB: Show Task",
description: "Show full details for a task including steps, progress, and log entries.",
promptSnippet: "Show full details for a Fusion task",
@@ -348,10 +348,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_attach ───────────────────────────────────────────────
// ── fn_task_attach ───────────────────────────────────────────────
pi.registerTool({
name: "kb_task_attach",
name: "fn_task_attach",
label: "KB: Attach File",
description:
"Attach a file to a task. Supports images (png, jpg, gif, webp) and " +
@@ -399,10 +399,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_pause ────────────────────────────────────────────────
// ── fn_task_pause ────────────────────────────────────────────────
pi.registerTool({
name: "kb_task_pause",
name: "fn_task_pause",
label: "KB: Pause Task",
description:
"Pause a task — stops all automated agent and scheduler interaction for this task.",
@@ -422,10 +422,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_unpause ──────────────────────────────────────────────
// ── fn_task_unpause ──────────────────────────────────────────────
pi.registerTool({
name: "kb_task_unpause",
name: "fn_task_unpause",
label: "KB: Unpause Task",
description:
"Unpause a task — resumes automated agent and scheduler interaction.",
@@ -445,10 +445,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_retry ────────────────────────────────────────────────
// ── fn_task_retry ────────────────────────────────────────────────
pi.registerTool({
name: "kb_task_retry",
name: "fn_task_retry",
label: "KB: Retry Task",
description:
"Retry a failed task — clears the error state and moves it back to the todo column for re-execution.",
@@ -502,10 +502,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_duplicate ─────────────────────────────────────────────
// ── fn_task_duplicate ─────────────────────────────────────────────
pi.registerTool({
name: "kb_task_duplicate",
name: "fn_task_duplicate",
label: "KB: Duplicate Task",
description:
"Duplicate an existing task, creating a fresh copy in triage. " +
@@ -532,10 +532,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_refine ──────────────────────────────────────────────
// ── fn_task_refine ──────────────────────────────────────────────
pi.registerTool({
name: "kb_task_refine",
name: "fn_task_refine",
label: "KB: Refine Task",
description:
"Request a refinement of a completed or in-review task. " +
@@ -570,10 +570,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_archive ───────────────────────────────────────────────
// ── fn_task_archive ───────────────────────────────────────────────
pi.registerTool({
name: "kb_task_archive",
name: "fn_task_archive",
label: "KB: Archive Task",
description:
"Archive a done task (move from done → archived). " +
@@ -599,10 +599,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_unarchive ─────────────────────────────────────────────
// ── fn_task_unarchive ─────────────────────────────────────────────
pi.registerTool({
name: "kb_task_unarchive",
name: "fn_task_unarchive",
label: "KB: Unarchive Task",
description:
"Unarchive an archived task (move from archived → done). " +
@@ -627,10 +627,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_delete ─────────────────────────────────────────────────
// ── fn_task_delete ─────────────────────────────────────────────────
pi.registerTool({
name: "kb_task_delete",
name: "fn_task_delete",
label: "KB: Delete Task",
description:
"Permanently delete a task from the Fusion board. " +
@@ -656,10 +656,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_import_github ─────────────────────────────────────────
// ── fn_task_import_github ─────────────────────────────────────────
pi.registerTool({
name: "kb_task_import_github",
name: "fn_task_import_github",
label: "KB: Import GitHub Issues",
description:
"Import GitHub issues as Fusion tasks. Fetches open issues from a repository " +
@@ -675,7 +675,7 @@ export default function kbExtension(pi: ExtensionAPI) {
],
parameters: Type.Object({
ownerRepo: Type.String({
description: "Repository in owner/repo format (e.g., 'dustinbyrne/kb')",
description: "Repository in owner/repo format (e.g., 'dustinbyrne/fusion')",
pattern: "^[^/]+/[^/]+$",
}),
limit: Type.Optional(
@@ -745,11 +745,11 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_import_github_issue ───────────────────────────────────
// ── fn_task_import_github_issue ───────────────────────────────────
// Import a single GitHub issue by its issue number
pi.registerTool({
name: "kb_task_import_github_issue",
name: "fn_task_import_github_issue",
label: "KB: Import GitHub Issue",
description:
"Import a specific GitHub issue as a Fusion task. Fetches the issue by number " +
@@ -766,7 +766,7 @@ export default function kbExtension(pi: ExtensionAPI) {
description: "Repository owner (e.g., 'dustinbyrne')",
}),
repo: Type.String({
description: "Repository name (e.g., 'kb')",
description: "Repository name (e.g., 'fusion')",
}),
issueNumber: Type.Number({
description: "GitHub issue number to import",
@@ -867,15 +867,15 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_browse_github_issues ──────────────────────────────────
// ── fn_task_browse_github_issues ──────────────────────────────────
// Browse available GitHub issues before importing
pi.registerTool({
name: "kb_task_browse_github_issues",
name: "fn_task_browse_github_issues",
label: "KB: Browse GitHub Issues",
description:
"List open GitHub issues from a repository to browse before importing. " +
"Returns issue numbers, titles, and URLs for selection. Use with kb_task_import_github_issue " +
"Returns issue numbers, titles, and URLs for selection. Use with fn_task_import_github_issue " +
"to import specific issues by number.",
promptSnippet: "Browse open GitHub issues in a repository",
promptGuidelines: [
@@ -890,7 +890,7 @@ export default function kbExtension(pi: ExtensionAPI) {
description: "Repository owner (e.g., 'dustinbyrne')",
}),
repo: Type.String({
description: "Repository name (e.g., 'kb')",
description: "Repository name (e.g., 'fusion')",
}),
limit: Type.Optional(
Type.Number({
@@ -990,7 +990,7 @@ export default function kbExtension(pi: ExtensionAPI) {
lines.push(` ${issue.html_url}`);
}
lines.push("\nUse kb_task_import_github_issue to import a specific issue by number.");
lines.push("\nUse fn_task_import_github_issue to import a specific issue by number.");
return {
content: [{ type: "text", text: lines.join("\n") }],
@@ -1008,11 +1008,11 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_task_plan ────────────────────────────────────────────────
// ── fn_task_plan ────────────────────────────────────────────────
// Create a task via AI-guided planning mode
pi.registerTool({
name: "kb_task_plan",
name: "fn_task_plan",
label: "KB: Plan Task",
description:
"Create a task via AI-guided planning mode — interactive conversation to refine your idea into a well-specified task.",
@@ -1082,10 +1082,10 @@ export default function kbExtension(pi: ExtensionAPI) {
// ── Mission Tools ───────────────────────────────────────────────
// Mission hierarchy management for multi-phase project planning
// ── kb_mission_create ───────────────────────────────────────────
// ── fn_mission_create ───────────────────────────────────────────
pi.registerTool({
name: "kb_mission_create",
name: "fn_mission_create",
label: "KB: Create Mission",
description:
"Create a new mission — a high-level objective that can span multiple milestones. " +
@@ -1138,17 +1138,17 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_mission_list ──────────────────────────────────────────────
// ── fn_mission_list ──────────────────────────────────────────────
pi.registerTool({
name: "kb_mission_list",
name: "fn_mission_list",
label: "KB: List Missions",
description: "List all missions with their current status.",
promptSnippet: "List all missions",
promptGuidelines: [
"Use to see all missions and their current status",
"Missions are grouped by status (active, planning, complete, etc.)",
"Use before kb_mission_show to find a specific mission ID",
"Use before fn_mission_show to find a specific mission ID",
],
parameters: Type.Object({}),
@@ -1192,10 +1192,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_mission_show ──────────────────────────────────────────────
// ── fn_mission_show ──────────────────────────────────────────────
pi.registerTool({
name: "kb_mission_show",
name: "fn_mission_show",
label: "KB: Show Mission",
description: "Show mission details with full hierarchy: milestones → slices → features.",
promptSnippet: "Show mission details with hierarchy",
@@ -1257,10 +1257,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_mission_delete ───────────────────────────────────────────
// ── fn_mission_delete ───────────────────────────────────────────
pi.registerTool({
name: "kb_mission_delete",
name: "fn_mission_delete",
label: "KB: Delete Mission",
description: "Delete a mission and all its milestones, slices, and features. Cannot be undone.",
promptSnippet: "Delete a mission and all its contents",
@@ -1295,10 +1295,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_milestone_add ────────────────────────────────────────────
// ── fn_milestone_add ────────────────────────────────────────────
pi.registerTool({
name: "kb_milestone_add",
name: "fn_milestone_add",
label: "KB: Add Milestone",
description: "Add a milestone to a mission. Milestones represent phases of work.",
promptSnippet: "Add a milestone to a mission",
@@ -1339,10 +1339,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_slice_add ─────────────────────────────────────────────────
// ── fn_slice_add ─────────────────────────────────────────────────
pi.registerTool({
name: "kb_slice_add",
name: "fn_slice_add",
label: "KB: Add Slice",
description: "Add a slice to a milestone. Slices are work units that can be activated for implementation.",
promptSnippet: "Add a work slice to a milestone",
@@ -1384,17 +1384,17 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_feature_add ────────────────────────────────────────────────
// ── fn_feature_add ────────────────────────────────────────────────
pi.registerTool({
name: "kb_feature_add",
name: "fn_feature_add",
label: "KB: Add Feature",
description: "Add a feature to a slice. Features are deliverables that can be linked to tasks.",
promptSnippet: "Add a feature to a slice",
promptGuidelines: [
"Features represent deliverables within a slice",
"Features start as 'defined' and progress through 'triaged' → 'in-progress' → 'done'",
"Link features to tasks using kb_feature_link_task",
"Link features to tasks using fn_feature_link_task",
],
parameters: Type.Object({
sliceId: Type.String({ description: "Parent slice ID (e.g., SL-001)" }),
@@ -1433,10 +1433,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_slice_activate ────────────────────────────────────────────
// ── fn_slice_activate ────────────────────────────────────────────
pi.registerTool({
name: "kb_slice_activate",
name: "fn_slice_activate",
label: "KB: Activate Slice",
description:
"Activate a pending slice for implementation. " +
@@ -1486,10 +1486,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_feature_link_task ──────────────────────────────────────────
// ── fn_feature_link_task ──────────────────────────────────────────
pi.registerTool({
name: "kb_feature_link_task",
name: "fn_feature_link_task",
label: "KB: Link Feature to Task",
description:
"Link a feature to a fn task for implementation. " +
@@ -1545,10 +1545,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_agent_stop ─────────────────────────────────────────────────
// ── fn_agent_stop ─────────────────────────────────────────────────
pi.registerTool({
name: "kb_agent_stop",
name: "fn_agent_stop",
label: "KB: Stop Agent",
description:
"Stop a running agent — pauses its execution. " +
@@ -1556,7 +1556,7 @@ export default function kbExtension(pi: ExtensionAPI) {
promptSnippet: "Stop (pause) a running Fusion agent",
promptGuidelines: [
"Use to pause an agent that is currently running or active",
"Stopped agents can be resumed with kb_agent_start",
"Stopped agents can be resumed with fn_agent_start",
"Agents in 'idle', 'error', or 'terminated' state cannot be stopped",
],
parameters: Type.Object({
@@ -1608,10 +1608,10 @@ export default function kbExtension(pi: ExtensionAPI) {
},
});
// ── kb_agent_start ────────────────────────────────────────────────
// ── fn_agent_start ────────────────────────────────────────────────
pi.registerTool({
name: "kb_agent_start",
name: "fn_agent_start",
label: "KB: Start Agent",
description:
"Start a stopped agent — resumes its execution. " +