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:
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user