fix(FN-2392): normalize skill-facing tool references to fn_*

- Update Fusion skill docs, prompts, and capability references to use public fn_* tool names consistently
- Align engine system prompts and tool schemas for messaging/task actions with fn_send_message, fn_read_messages, fn_task_* naming
- Refresh related tests across CLI, engine, dashboard, and core to match normalized tool naming and behavior
- Add a patch changeset for @runfusion/fusion describing the skill-tool namespace normalization
This commit is contained in:
Fusion
2026-04-24 03:11:09 -07:00
committed by gsxdsm
parent fa0cbe2327
commit eef56af706
33 changed files with 927 additions and 764 deletions

View File

@@ -21,6 +21,8 @@ Mission → Milestone → Slice → Feature → Task
**Available tools:** Fusion registers tools via the pi extension (prefixed `fn_*`). No CLI commands or Bash needed — use the registered tools directly.
**Naming boundary:** The published skill surface always uses `fn_*` tool names (for example `fn_task_create`, `fn_mission_create`). Internal engine runtime tools like `task_create`, `task_update`, `task_log`, and `task_done` are intentionally unprefixed and not part of this skill.
**Tool categories:**
- **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`

View File

@@ -2,6 +2,8 @@
All tools are registered via the pi extension. They are available in any pi agent session when the Fusion extension is installed.
> Naming contract: all externally exposed Fusion extension tools are `fn_*` (for example `fn_task_create`). Internal engine/executor runtime tools (`task_create`, `task_update`, `task_log`, `task_done`, etc.) are separate and intentionally out of scope for this skill surface.
## Task Tools
### fn_task_create

View File

@@ -7,6 +7,8 @@ Triage → Todo → In Progress → In Review → Done → Archived
## Pi Extension Tools (Available to Agents)
All skill/extension tool invocations in this catalog use the public `fn_*` namespace. Engine runtime tools (for example `task_create`, `task_update`, `task_log`, `task_done`) are internal and intentionally not listed here.
| Tool | Purpose |
|------|---------|
| `fn_task_create` | Create a new task in triage |

View File

@@ -5,6 +5,8 @@
<objective>
Guide the agent through creating well-specified tasks and organizing work using the mission hierarchy for complex multi-phase projects.
All tool examples in this workflow intentionally use the public `fn_*` extension namespace.
</objective>
<process>

View File

@@ -5,6 +5,8 @@
<objective>
Guide the agent through creating, viewing, and managing tasks on the Fusion board using pi extension tools.
Use only the public `fn_*` extension tools in this workflow. Do not substitute internal engine runtime tools like `task_create`, `task_update`, `task_log`, or `task_done`.
</objective>
<process>

View File

@@ -1,6 +1,6 @@
import { describe, it, expect } from "vitest";
import { readFileSync, existsSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { readFileSync, existsSync, readdirSync } from "node:fs";
import { resolve, dirname, relative, join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -52,6 +52,25 @@ function getCapabilitiesToolNames(): string[] {
return matches.map((m) => m[1]).sort();
}
function collectMarkdownFiles(dir: string): string[] {
const files: string[] = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...collectMarkdownFiles(fullPath));
continue;
}
if (entry.isFile() && entry.name.endsWith(".md")) {
files.push(fullPath);
}
}
return files.sort();
}
function escapeRegex(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
describe("Skill-Extension Sync", () => {
it("skill directory structure exists", () => {
expect(existsSync(resolve(skillDir, "SKILL.md"))).toBe(true);
@@ -105,6 +124,65 @@ describe("Skill-Extension Sync", () => {
expect(missingFromCaps).toEqual([]);
});
it("covers the full Fusion skill markdown surface", () => {
const markdownFiles = collectMarkdownFiles(skillDir).map((filePath) =>
relative(skillDir, filePath),
);
expect(markdownFiles).toEqual([
"SKILL.md",
"references/best-practices.md",
"references/cli-commands.md",
"references/extension-tools.md",
"references/fusion-capabilities.md",
"references/skill-patterns.md",
"references/task-structure.md",
"workflows/dashboard-cli.md",
"workflows/specifications.md",
"workflows/task-lifecycle.md",
"workflows/task-management.md",
]);
});
it("enforces fn_* naming across extension + all skill markdown for public tools", () => {
const extensionTools = getExtensionToolNames();
const publicSuffixes = extensionTools.map((toolName) =>
toolName.replace(/^fn_/, ""),
);
// These names are intentionally unprefixed engine/runtime tools and are allowed
// to appear in docs that explain capability boundaries.
const allowedUnprefixedInternalTools = new Set([
"task_create",
"task_update",
"task_log",
"task_done",
"review_step",
"spawn_agent",
]);
const forbiddenSuffixes = publicSuffixes.filter(
(suffix) => !allowedUnprefixedInternalTools.has(suffix),
);
const filesToScan = [extensionPath, ...collectMarkdownFiles(skillDir)];
const violations: string[] = [];
for (const filePath of filesToScan) {
const content = readFileSync(filePath, "utf-8");
const relativePath = relative(cliRoot, filePath);
for (const suffix of forbiddenSuffixes) {
const regex = new RegExp(`(?<!fn_)\\b${escapeRegex(suffix)}\\b`, "g");
if (regex.test(content)) {
violations.push(`${relativePath}: ${suffix}`);
}
}
}
expect(violations).toEqual([]);
});
it("/fn command is documented in the skill", () => {
const skillMd = readFileSync(resolve(skillDir, "SKILL.md"), "utf-8");
expect(skillMd).toContain("/fn");