feat(FN-2569): sync fusion skill docs with runtime tools
- Add generated engine-tools reference and refresh extension/capabilities docs from source definitions - Enhance sync-fusion-skill-tools script to verify and maintain skill documentation consistency - Expand skill-sync tests and enforce sync:fusion-skill:check in the workspace test pipeline - Add FN-2569 changeset documenting the published @runfusion/fusion patch update
This commit is contained in:
@@ -21,7 +21,9 @@ 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.
|
||||
**Naming boundary:** The published pi-extension skill surface uses `fn_*` tool names (for example `fn_task_create`, `fn_mission_create`). Engine runtime sessions also inject additional `fn_*` tools (for example `fn_review_spec`, `fn_review_step`, `fn_spawn_agent`) that are not user-invokable extension tools.
|
||||
|
||||
**Engine runtime tools:** Triage/executor/merger/heartbeat sessions include auto-injected engine tools that do not come from the pi extension registration list. See `references/engine-tools.md` for the canonical runtime-only catalog and usage boundaries.
|
||||
|
||||
**Tool categories:**
|
||||
<!-- BEGIN: tool-categories (auto-generated by scripts/sync-fusion-skill-tools.mjs — do not edit by hand) -->
|
||||
@@ -102,6 +104,7 @@ For these operations, guide the user to the dashboard (`/fn`) or CLI commands do
|
||||
| references/extension-tools.md | All pi extension tools with parameters |
|
||||
| references/best-practices.md | Tips for effective Fusion usage |
|
||||
| references/fusion-capabilities.md | Complete feature catalog |
|
||||
| references/engine-tools.md | Engine session-scoped runtime tools (not extension-invokable) |
|
||||
| references/skill-patterns.md | Patterns used in this skill's design |
|
||||
|
||||
</reference_index>
|
||||
|
||||
54
packages/cli/skill/fusion/references/engine-tools.md
Normal file
54
packages/cli/skill/fusion/references/engine-tools.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Engine Session-Scoped Tools
|
||||
|
||||
These tools are **not** part of the pi extension's user-invokable `extension.ts` surface. They are injected by the engine at runtime for specific agent session types.
|
||||
|
||||
- Source files: `packages/engine/src/agent-tools.ts`, `triage.ts`, `executor.ts`, `merger.ts`, `agent-heartbeat.ts`
|
||||
- Availability: only when the engine creates a session for the matching agent role
|
||||
- Important: do not tell users to call these directly from the generic pi extension tool list
|
||||
|
||||
## Shared runtime tools (`agent-tools.ts`)
|
||||
|
||||
| Tool | Agent Types | Purpose | Parameters |
|
||||
|---|---|---|---|
|
||||
| `fn_task_create` | triage, executor, heartbeat | Create a follow-up task from within an agent run | `description` (string), `dependencies?` (string[]) |
|
||||
| `fn_task_log` | executor, heartbeat | Write significant task log entries | `message` (string), `outcome?` (string) |
|
||||
| `fn_task_document_write` | triage, executor, heartbeat | Save/update a named task document revision | `key` (string), `content` (string), `author?` (string) |
|
||||
| `fn_task_document_read` | triage, executor, heartbeat | Read one task document or list all | `key?` (string) |
|
||||
| `fn_memory_search` | triage, executor, heartbeat | Search project/agent memory snippets | `query` (string), `limit?` (number) |
|
||||
| `fn_memory_get` | triage, executor, heartbeat | Read a bounded memory file window | `path` (string), `startLine?` (number), `lineCount?` (number) |
|
||||
| `fn_memory_append` | executor, heartbeat (when writable backend enabled) | Append long-term/daily memory notes | `scope?` (`project` \| `agent`), `layer` (`long-term` \| `daily`), `content` (string) |
|
||||
| `fn_reflect_on_performance` | executor | Generate reflection insights from prior runs | `focus_area?` (string) |
|
||||
| `fn_list_agents` | triage, executor, heartbeat | List agents (optionally filtered) | `role?` (string), `state?` (string), `includeEphemeral?` (boolean) |
|
||||
| `fn_delegate_task` | triage, executor, heartbeat | Create and assign a new task to a specific agent | `agent_id` (string), `description` (string), `dependencies?` (string[]) |
|
||||
| `fn_send_message` | executor, heartbeat | Send inbox messages to agents/users | `to_id` (string), `content` (string), `type?` (`agent-to-agent` \| `agent-to-user`), `reply_to_message_id?` (string) |
|
||||
| `fn_read_messages` | executor, heartbeat | Read inbox messages | `unread_only?` (boolean), `limit?` (number) |
|
||||
|
||||
## Triage-only runtime tools (`triage.ts`)
|
||||
|
||||
| Tool | Purpose | Parameters |
|
||||
|---|---|---|
|
||||
| `fn_task_list` | List active tasks during specification (duplicate check, discovery) | none |
|
||||
| `fn_task_get` | Fetch full task detail including PROMPT.md | `id` (string) |
|
||||
| `fn_review_spec` | Spawn spec reviewer and return `APPROVE`/`REVISE`/`RETHINK`/`UNAVAILABLE` | none |
|
||||
|
||||
## Executor-only runtime tools (`executor.ts`)
|
||||
|
||||
| Tool | Purpose | Parameters |
|
||||
|---|---|---|
|
||||
| `fn_task_update` | Update a spec step status (`pending`/`in-progress`/`done`/`skipped`) | `step` (number), `status` (enum) |
|
||||
| `fn_task_add_dep` | Add a dependency to current task (confirmation-gated) | `task_id` (string), `confirm?` (boolean) |
|
||||
| `fn_task_done` | Mark task complete and optionally store summary | `summary?` (string) |
|
||||
| `fn_review_step` | Spawn step plan/code reviewer | `step` (number), `type` (`plan` \| `code`), `step_name` (string), `baseline?` (string) |
|
||||
| `fn_spawn_agent` | Spawn child agent in separate worktree | `name` (string), `role` (enum), `task` (string) |
|
||||
|
||||
## Merger-only runtime tools (`merger.ts`)
|
||||
|
||||
| Tool | Purpose | Parameters |
|
||||
|---|---|---|
|
||||
| `fn_report_build_failure` | Explicitly signal merge-time build verification failure | `message` (string) |
|
||||
|
||||
## Heartbeat-only runtime tools (`agent-heartbeat.ts`)
|
||||
|
||||
| Tool | Purpose | Parameters |
|
||||
|---|---|---|
|
||||
| `fn_heartbeat_done` | Signal end of heartbeat run with optional summary | `summary?` (string) |
|
||||
@@ -2,191 +2,187 @@
|
||||
|
||||
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.
|
||||
> Naming contract: all externally exposed Fusion extension tools are `fn_*` (for example `fn_task_create`). Engine runtime sessions also inject additional `fn_*` tools (for example `fn_review_step`, `fn_spawn_agent`, `fn_task_document_write`) that are separate from this extension surface and documented in `engine-tools.md`.
|
||||
|
||||
<!-- BEGIN: extension-tools (auto-generated by scripts/sync-fusion-skill-tools.mjs — do not edit by hand) -->
|
||||
|
||||
## Task Tools
|
||||
|
||||
### fn_task_create
|
||||
|
||||
Create a new task on the Fusion board. Enters triage for AI specification.
|
||||
Create a new task on the Fusion task board. The task enters the triage column where the AI triage agent will specify it into a full prompt with steps, file scope, and acceptance criteria.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `description` | string | ✓ | What needs to be done — be descriptive |
|
||||
| `depends` | string[] | — | Task IDs this depends on (e.g., ["FN-001"]) |
|
||||
|
||||
Returns: task ID, column, dependencies, path
|
||||
| `depends` | array | — | Task IDs this depends on (e.g. ['FN-001', 'FN-002']) |
|
||||
| `agentId` | string | — | Agent ID to assign this task to (e.g. 'agent-abc123') |
|
||||
|
||||
### fn_task_update
|
||||
|
||||
Update fields on an existing task (title, description, dependencies).
|
||||
Update fields on an existing task. Supports modifying the title, description, dependencies, and assigned agent after task creation.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Task ID (e.g., FN-001) |
|
||||
| `id` | string | ✓ | Task ID (e.g. FN-001) |
|
||||
| `title` | string | — | New task title |
|
||||
| `description` | string | — | New task description |
|
||||
| `depends` | string[] | — | New dependency list — replaces existing |
|
||||
|
||||
Returns: task ID, list of updated fields
|
||||
| `depends` | array | — | New dependency list — replaces existing dependencies (e.g. ['FN-001', 'FN-002']) |
|
||||
| `agentId` | union | — | Agent ID to assign this task to, or null to clear (e.g. 'agent-abc123') |
|
||||
|
||||
### fn_task_list
|
||||
|
||||
List all tasks grouped by column.
|
||||
List all tasks on the Fusion board, grouped by column.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `column` | string | — | Filter to specific column |
|
||||
| `limit` | number | — | Max tasks per column (default: 10) |
|
||||
|
||||
Returns: formatted task list grouped by column
|
||||
| `column` | string(enum) | — | Filter to a specific column |
|
||||
| `limit` | number | — | Max tasks to show per column (default: 10) |
|
||||
|
||||
### fn_task_show
|
||||
|
||||
Show full task details including steps, progress, prompt preview, and log.
|
||||
Show full details for a task including steps, progress, and log entries.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Task ID (e.g., FN-001) |
|
||||
|
||||
Returns: task details with steps, prompt preview (500 chars), last 5 log entries
|
||||
| `id` | string | ✓ | Task ID (e.g. FN-001) |
|
||||
|
||||
### fn_task_attach
|
||||
|
||||
Attach a file to a task. Copies file to task's attachments directory.
|
||||
Attach a file to a task. Supports images (png, jpg, gif, webp) and text files (txt, log, json, yaml, yml, toml, csv, xml).
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Task ID |
|
||||
| `path` | string | ✓ | Path to file to attach |
|
||||
|
||||
Supported formats: png, jpg, jpeg, gif, webp, txt, log, json, yaml, yml, toml, csv, xml
|
||||
| `id` | string | ✓ | Task ID (e.g. FN-001) |
|
||||
| `path` | string | ✓ | Path to the file to attach |
|
||||
|
||||
### fn_task_pause
|
||||
|
||||
Pause automation for a task. Scheduler and executor will skip this task.
|
||||
Pause a task — stops all automated agent and scheduler interaction for this task.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Task ID |
|
||||
| `id` | string | ✓ | Task ID (e.g. FN-001) |
|
||||
|
||||
### fn_task_unpause
|
||||
|
||||
Resume automation for a paused task.
|
||||
Unpause a task — resumes automated agent and scheduler interaction.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Task ID |
|
||||
| `id` | string | ✓ | Task ID (e.g. FN-001) |
|
||||
|
||||
### fn_task_retry
|
||||
|
||||
Retry a failed task. Clears error state, moves to todo for re-execution.
|
||||
Retry a failed task — clears the error state and moves it back to the todo column for re-execution.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Task ID (must be in failed state) |
|
||||
| `id` | string | ✓ | Task ID to retry (e.g. FN-001). Must be in 'failed' state. |
|
||||
|
||||
### fn_task_duplicate
|
||||
|
||||
Duplicate a task. Creates a fresh copy in triage with same title and description.
|
||||
Duplicate an existing task, creating a fresh copy in triage. Copies the title and description but resets all execution state. The AI triage agent will re-specify the new task.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Source task ID to duplicate |
|
||||
| `id` | string | ✓ | Source task ID to duplicate (e.g. FN-001) |
|
||||
|
||||
### fn_task_refine
|
||||
|
||||
Create a follow-up task for a completed task. New task depends on the original.
|
||||
Request a refinement of a completed or in-review task. Creates a new follow-up task in triage that references the original task as a dependency. Use this when a done or in-review task needs additional work, improvements, or follow-up changes.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Task ID (must be done or in-review) |
|
||||
| `feedback` | string | ✓ | What needs to be refined (1-2000 chars) |
|
||||
| `id` | string | ✓ | Task ID to refine (e.g. FN-001). Must be in 'done' or 'in-review' column. |
|
||||
| `feedback` | string | ✓ | Description of what needs to be refined or improved |
|
||||
|
||||
### fn_task_archive
|
||||
|
||||
Archive a done task. Moves from done → archived.
|
||||
Archive a done task (move from done → archived). Archived tasks are preserved for historical reference but moved out of the main board view.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Task ID (must be in done column) |
|
||||
| `id` | string | ✓ | Task ID to archive (e.g. FN-001). Must be in 'done' column. |
|
||||
|
||||
### fn_task_unarchive
|
||||
|
||||
Restore an archived task. Moves from archived → done.
|
||||
Unarchive an archived task (move from archived → done). Restores the task to the done column.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Task ID (must be in archived column) |
|
||||
| `id` | string | ✓ | Task ID to unarchive (e.g. FN-001). Must be in 'archived' column. |
|
||||
|
||||
### fn_task_delete
|
||||
|
||||
Permanently delete a task. Cannot be undone.
|
||||
Permanently delete a task from the Fusion board. Tasks are deleted immediately and cannot be recovered.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Task ID |
|
||||
| `id` | string | ✓ | Task ID to delete (e.g. FN-001) |
|
||||
|
||||
### fn_task_plan
|
||||
|
||||
Create a task via AI-guided planning mode. Non-interactive when called from extension.
|
||||
Create a task via AI-guided planning mode — interactive conversation to refine your idea into a well-specified task.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `description` | string | — | Initial plan description |
|
||||
| `description` | string | — | Initial plan description (optional) — the AI will ask clarifying questions if not provided |
|
||||
|
||||
## GitHub Tools
|
||||
|
||||
### fn_task_import_github
|
||||
|
||||
Batch import GitHub issues as Fusion tasks.
|
||||
Import GitHub issues as Fusion tasks. Fetches open issues from a repository and creates tasks in the triage column. Each task includes the issue title and body with a link to the source issue.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `ownerRepo` | string | ✓ | Repository (e.g., "owner/repo") |
|
||||
| `limit` | number | — | Max issues (default: 30, max: 100) |
|
||||
| `labels` | string[] | — | Label names to filter by |
|
||||
| `ownerRepo` | string | ✓ | Repository in owner/repo format (e.g., 'dustinbyrne/fusion') |
|
||||
| `limit` | number | — | Max issues to import (default: 30, max: 100) |
|
||||
| `labels` | array | — | Label names to filter by |
|
||||
|
||||
### fn_task_import_github_issue
|
||||
|
||||
Import a single GitHub issue by number.
|
||||
Import a specific GitHub issue as a Fusion task. Fetches the issue by number and creates a single task in the triage column with the issue title and body.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `owner` | string | ✓ | Repository owner |
|
||||
| `repo` | string | ✓ | Repository name |
|
||||
| `issueNumber` | number | ✓ | GitHub issue number |
|
||||
| `owner` | string | ✓ | Repository owner (e.g., 'dustinbyrne') |
|
||||
| `repo` | string | ✓ | Repository name (e.g., 'fusion') |
|
||||
| `issueNumber` | number | ✓ | GitHub issue number to import |
|
||||
|
||||
### fn_task_browse_github_issues
|
||||
|
||||
Browse open issues from a repository before importing.
|
||||
List open GitHub issues from a repository to browse before importing. Returns issue numbers, titles, and URLs for selection. Use with fn_task_import_github_issue to import specific issues by number.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `owner` | string | ✓ | Repository owner |
|
||||
| `repo` | string | ✓ | Repository name |
|
||||
| `limit` | number | — | Max issues (default: 30, max: 100) |
|
||||
| `labels` | string[] | — | Label names to filter by |
|
||||
| `owner` | string | ✓ | Repository owner (e.g., 'dustinbyrne') |
|
||||
| `repo` | string | ✓ | Repository name (e.g., 'fusion') |
|
||||
| `limit` | number | — | Max issues to show (default: 30, max: 100) |
|
||||
| `labels` | array | — | Label names to filter by |
|
||||
|
||||
## Mission Tools
|
||||
|
||||
### fn_mission_create
|
||||
|
||||
Create a new mission — a high-level objective spanning multiple milestones.
|
||||
Create a new mission — a high-level objective that can span multiple milestones. Missions contain milestones that break down work into phases.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `title` | string | ✓ | Mission title |
|
||||
| `description` | string | — | Detailed objectives and context |
|
||||
| `autoAdvance` | boolean | — | Auto-activate next slice on completion |
|
||||
| `title` | string | ✓ | Mission title — brief but descriptive |
|
||||
| `description` | string | — | Detailed mission objectives and context |
|
||||
| `autoAdvance` | boolean | — | Automatically activate the next pending slice when the current slice completes |
|
||||
|
||||
### fn_mission_list
|
||||
|
||||
List all missions with current status. No parameters.
|
||||
List all missions with their current status.
|
||||
|
||||
No parameters.
|
||||
|
||||
### fn_mission_show
|
||||
|
||||
Show mission details with full hierarchy.
|
||||
Show mission details with full hierarchy: milestones → slices → features.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
@@ -194,63 +190,65 @@ Show mission details with full hierarchy.
|
||||
|
||||
### fn_mission_delete
|
||||
|
||||
Delete a mission and all children. Tasks are NOT deleted.
|
||||
Delete a mission and all its milestones, slices, and features. Cannot be undone.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Mission ID |
|
||||
| `id` | string | ✓ | Mission ID to delete (e.g., M-001) |
|
||||
|
||||
### fn_milestone_add
|
||||
|
||||
Add a milestone to a mission.
|
||||
Add a milestone to a mission. Milestones represent phases of work.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `missionId` | string | ✓ | Parent mission ID |
|
||||
| `missionId` | string | ✓ | Parent mission ID (e.g., M-001) |
|
||||
| `title` | string | ✓ | Milestone title |
|
||||
| `description` | string | — | Milestone description |
|
||||
|
||||
### fn_slice_add
|
||||
|
||||
Add a slice to a milestone.
|
||||
Add a slice to a milestone. Slices are work units that can be activated for implementation.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `milestoneId` | string | ✓ | Parent milestone ID |
|
||||
| `milestoneId` | string | ✓ | Parent milestone ID (e.g., MS-001) |
|
||||
| `title` | string | ✓ | Slice title |
|
||||
| `description` | string | — | Slice description |
|
||||
|
||||
### fn_feature_add
|
||||
|
||||
Add a feature to a slice.
|
||||
Add a feature to a slice. Features are deliverables that can be linked to tasks.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `sliceId` | string | ✓ | Parent slice ID |
|
||||
| `sliceId` | string | ✓ | Parent slice ID (e.g., SL-001) |
|
||||
| `title` | string | ✓ | Feature title |
|
||||
| `description` | string | — | Feature description |
|
||||
| `acceptanceCriteria` | string | — | Acceptance criteria |
|
||||
| `acceptanceCriteria` | string | — | Acceptance criteria for completing the feature |
|
||||
|
||||
### fn_slice_activate
|
||||
|
||||
Activate a pending slice for implementation.
|
||||
Activate a pending slice for implementation. Sets status to 'active' and enables task linking for its features.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `id` | string | ✓ | Slice ID (must be pending) |
|
||||
| `id` | string | ✓ | Slice ID to activate (e.g., SL-001) |
|
||||
|
||||
### fn_feature_link_task
|
||||
|
||||
Link a feature to a Fusion task. Updates feature status to triaged.
|
||||
Link a feature to a fn task for implementation. Updates the feature status to 'triaged' and associates it with the task.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `featureId` | string | ✓ | Feature ID (e.g., F-001) |
|
||||
| `taskId` | string | ✓ | Task ID (e.g., FN-001) |
|
||||
| `featureId` | string | ✓ | Feature ID to link (e.g., F-001) |
|
||||
| `taskId` | string | ✓ | Task ID to link to (e.g., FN-001) |
|
||||
|
||||
## Agent Tools
|
||||
|
||||
### fn_agent_stop
|
||||
|
||||
Stop (pause) a running agent. Transitions the agent from running/active to paused state.
|
||||
Stop a running agent — pauses its execution. Transitions the agent from running/active to paused state.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
@@ -258,7 +256,7 @@ Stop (pause) a running agent. Transitions the agent from running/active to pause
|
||||
|
||||
### fn_agent_start
|
||||
|
||||
Start (resume) a stopped agent. Transitions the agent from paused to active state.
|
||||
Start a stopped agent — resumes its execution. Transitions the agent from paused to active state.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
@@ -268,22 +266,23 @@ Start (resume) a stopped agent. Transitions the agent from paused to active stat
|
||||
|
||||
### fn_skills_search
|
||||
|
||||
Search the skills.sh directory for agent skills. Returns matching skills with names, sources, install counts, and install commands.
|
||||
Search the skills.sh directory for agent skills. Returns matching skills with names, sources (owner/repo), install counts, and install commands. Use fn_skills_install to install a selected skill.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `query` | string | ✓ | Search query — framework, technology, or capability (e.g., "react", "firebase", "testing", "docker") |
|
||||
| `limit` | number | — | Max results (default: 10, max: 50) |
|
||||
| `query` | string | ✓ | Search query — framework name, technology, or capability (e.g., 'react', 'firebase', 'testing', 'docker') |
|
||||
| `limit` | number | — | Max results to return (default: 10, max: 50) |
|
||||
|
||||
### fn_skills_install
|
||||
|
||||
Install an agent skill from skills.sh into the current project. Downloads skill files into the project's skill directories.
|
||||
Install an agent skill from skills.sh into the current project. Downloads skill files into the project's skill directories (.fusion/skills/, legacy .pi/skills/, .agents/skills/). The skill becomes available to AI agents in subsequent sessions.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `source` | string | ✓ | GitHub source in owner/repo format (e.g., "firebase/agent-skills") |
|
||||
| `skill` | string | — | Specific skill name to install. Omit to install all skills from the source. |
|
||||
| `source` | string | ✓ | GitHub source in owner/repo format (e.g., 'firebase/agent-skills') |
|
||||
| `skill` | string | — | Specific skill name to install (e.g., 'firebase-basics'). Omit to install all skills from the source. |
|
||||
|
||||
<!-- END: extension-tools -->
|
||||
## Dashboard Command
|
||||
|
||||
### /fn
|
||||
|
||||
@@ -7,40 +7,42 @@ 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.
|
||||
All skill/extension tool invocations in this catalog use the public `fn_*` namespace. Engine runtime sessions also have additional runtime-only `fn_*` tools that are intentionally not listed here (see `references/engine-tools.md`).
|
||||
|
||||
<!-- BEGIN: fusion-capabilities-tool-table (auto-generated by scripts/sync-fusion-skill-tools.mjs — do not edit by hand) -->
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `fn_task_create` | Create a new task in triage |
|
||||
| `fn_task_update` | Update task title, description, or dependencies |
|
||||
| `fn_task_list` | List all tasks grouped by column |
|
||||
| `fn_task_show` | Show full task details, steps, and log preview |
|
||||
| `fn_task_attach` | Attach a file to a task |
|
||||
| `fn_task_pause` | Pause automation for a task |
|
||||
| `fn_task_unpause` | Resume automation for a task |
|
||||
| `fn_task_retry` | Retry a failed task (clears error, moves to todo) |
|
||||
| `fn_task_duplicate` | Duplicate a task (copy to triage) |
|
||||
| `fn_task_refine` | Create refinement task for follow-up work |
|
||||
| `fn_task_archive` | Archive a done task |
|
||||
| `fn_task_unarchive` | Restore an archived task |
|
||||
| `fn_task_delete` | Permanently delete a task |
|
||||
| `fn_task_import_github` | Batch import GitHub issues as tasks |
|
||||
| `fn_task_import_github_issue` | Import a single GitHub issue |
|
||||
| `fn_task_browse_github_issues` | Browse GitHub issues before importing |
|
||||
| `fn_task_plan` | Create task via AI-guided planning mode |
|
||||
| `fn_mission_create` | Create a new mission |
|
||||
| `fn_mission_list` | List all missions |
|
||||
| `fn_mission_show` | Show mission hierarchy |
|
||||
| `fn_mission_delete` | Delete a mission |
|
||||
| `fn_milestone_add` | Add a milestone to a mission |
|
||||
| `fn_slice_add` | Add a slice to a milestone |
|
||||
| `fn_feature_add` | Add a feature to a slice |
|
||||
| `fn_slice_activate` | Activate a pending slice |
|
||||
| `fn_feature_link_task` | Link a feature to a task |
|
||||
| `fn_agent_stop` | Stop (pause) a running agent |
|
||||
| `fn_agent_start` | Start (resume) a stopped agent |
|
||||
| `fn_skills_search` | Search skills.sh for agent skills |
|
||||
| `fn_skills_install` | Install a skill from skills.sh |
|
||||
| `fn_task_create` | Create a new task on the Fusion task board. The task enters the triage column where the AI triage agent will specify it into a full prompt with steps, file scope, and acceptance criteria. |
|
||||
| `fn_task_update` | Update fields on an existing task. Supports modifying the title, description, dependencies, and assigned agent after task creation. |
|
||||
| `fn_task_list` | List all tasks on the Fusion board, grouped by column. |
|
||||
| `fn_task_show` | Show full details for a task including steps, progress, and log entries. |
|
||||
| `fn_task_attach` | Attach a file to a task. Supports images (png, jpg, gif, webp) and text files (txt, log, json, yaml, yml, toml, csv, xml). |
|
||||
| `fn_task_pause` | Pause a task — stops all automated agent and scheduler interaction for this task. |
|
||||
| `fn_task_unpause` | Unpause a task — resumes automated agent and scheduler interaction. |
|
||||
| `fn_task_retry` | Retry a failed task — clears the error state and moves it back to the todo column for re-execution. |
|
||||
| `fn_task_duplicate` | Duplicate an existing task, creating a fresh copy in triage. Copies the title and description but resets all execution state. The AI triage agent will re-specify the new task. |
|
||||
| `fn_task_refine` | Request a refinement of a completed or in-review task. Creates a new follow-up task in triage that references the original task as a dependency. Use this when a done or in-review task needs additional work, improvements, or follow-up changes. |
|
||||
| `fn_task_archive` | Archive a done task (move from done → archived). Archived tasks are preserved for historical reference but moved out of the main board view. |
|
||||
| `fn_task_unarchive` | Unarchive an archived task (move from archived → done). Restores the task to the done column. |
|
||||
| `fn_task_delete` | Permanently delete a task from the Fusion board. Tasks are deleted immediately and cannot be recovered. |
|
||||
| `fn_task_import_github` | Import GitHub issues as Fusion tasks. Fetches open issues from a repository and creates tasks in the triage column. Each task includes the issue title and body with a link to the source issue. |
|
||||
| `fn_task_import_github_issue` | Import a specific GitHub issue as a Fusion task. Fetches the issue by number and creates a single task in the triage column with the issue title and body. |
|
||||
| `fn_task_browse_github_issues` | List open GitHub issues from a repository to browse before importing. Returns issue numbers, titles, and URLs for selection. Use with fn_task_import_github_issue to import specific issues by number. |
|
||||
| `fn_task_plan` | Create a task via AI-guided planning mode — interactive conversation to refine your idea into a well-specified task. |
|
||||
| `fn_mission_create` | Create a new mission — a high-level objective that can span multiple milestones. Missions contain milestones that break down work into phases. |
|
||||
| `fn_mission_list` | List all missions with their current status. |
|
||||
| `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_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_feature_add` | Add a feature to a slice. Features are deliverables that can be linked to tasks. |
|
||||
| `fn_slice_activate` | Activate a pending slice for implementation. Sets status to 'active' and enables task linking for its features. |
|
||||
| `fn_feature_link_task` | Link a feature to a fn task for implementation. Updates the feature status to 'triaged' and associates it with the task. |
|
||||
| `fn_agent_stop` | Stop a running agent — pauses its execution. Transitions the agent from running/active to paused state. |
|
||||
| `fn_agent_start` | Start a stopped agent — resumes its execution. Transitions the agent from paused to active state. |
|
||||
| `fn_skills_search` | Search the skills.sh directory for agent skills. Returns matching skills with names, sources (owner/repo), install counts, and install commands. Use fn_skills_install to install a selected skill. |
|
||||
| `fn_skills_install` | Install an agent skill from skills.sh into the current project. Downloads skill files into the project's skill directories (.fusion/skills/, legacy .pi/skills/, .agents/skills/). The skill becomes available to AI agents in subsequent sessions. |
|
||||
<!-- END: fusion-capabilities-tool-table -->
|
||||
|
||||
## CLI Commands (fn)
|
||||
|
||||
|
||||
@@ -6,17 +6,243 @@ import { spawnSync } from "node:child_process";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const cliRoot = resolve(__dirname, "../..");
|
||||
const repoRoot = resolve(cliRoot, "../..");
|
||||
const skillDir = resolve(cliRoot, "skill/fusion");
|
||||
const extensionPath = resolve(cliRoot, "src/extension.ts");
|
||||
|
||||
const EXT_TOOLS_BEGIN =
|
||||
"<!-- BEGIN: extension-tools (auto-generated by scripts/sync-fusion-skill-tools.mjs — do not edit by hand) -->";
|
||||
const EXT_TOOLS_END = "<!-- END: extension-tools -->";
|
||||
const CAP_TABLE_BEGIN =
|
||||
"<!-- BEGIN: fusion-capabilities-tool-table (auto-generated by scripts/sync-fusion-skill-tools.mjs — do not edit by hand) -->";
|
||||
const CAP_TABLE_END = "<!-- END: fusion-capabilities-tool-table -->";
|
||||
|
||||
const engineToolSourceFiles = [
|
||||
resolve(repoRoot, "packages/engine/src/agent-tools.ts"),
|
||||
resolve(repoRoot, "packages/engine/src/triage.ts"),
|
||||
resolve(repoRoot, "packages/engine/src/executor.ts"),
|
||||
resolve(repoRoot, "packages/engine/src/merger.ts"),
|
||||
resolve(repoRoot, "packages/engine/src/agent-heartbeat.ts"),
|
||||
];
|
||||
|
||||
function findMatchingBrace(source: string, openIndex: number): number {
|
||||
let depth = 0;
|
||||
let inSingle = false;
|
||||
let inDouble = false;
|
||||
let inTemplate = false;
|
||||
let inLineComment = false;
|
||||
let inBlockComment = false;
|
||||
let escaped = false;
|
||||
|
||||
for (let i = openIndex; i < source.length; i++) {
|
||||
const char = source[i];
|
||||
const next = source[i + 1];
|
||||
|
||||
if (inLineComment) {
|
||||
if (char === "\n") inLineComment = false;
|
||||
continue;
|
||||
}
|
||||
if (inBlockComment) {
|
||||
if (char === "*" && next === "/") {
|
||||
inBlockComment = false;
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inSingle || inDouble || inTemplate) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (char === "\\") {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (inSingle && char === "'") inSingle = false;
|
||||
else if (inDouble && char === '"') inDouble = false;
|
||||
else if (inTemplate && char === "`") inTemplate = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "/" && next === "/") {
|
||||
inLineComment = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (char === "/" && next === "*") {
|
||||
inBlockComment = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "'") {
|
||||
inSingle = true;
|
||||
continue;
|
||||
}
|
||||
if (char === '"') {
|
||||
inDouble = true;
|
||||
continue;
|
||||
}
|
||||
if (char === "`") {
|
||||
inTemplate = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === "{") depth++;
|
||||
if (char === "}") {
|
||||
depth--;
|
||||
if (depth === 0) return i;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Unbalanced braces");
|
||||
}
|
||||
|
||||
function splitTopLevelProperties(objectBody: string): string[] {
|
||||
const props: string[] = [];
|
||||
let start = 0;
|
||||
let depthParen = 0;
|
||||
let depthBrace = 0;
|
||||
let depthBracket = 0;
|
||||
let inSingle = false;
|
||||
let inDouble = false;
|
||||
let inTemplate = false;
|
||||
let inLineComment = false;
|
||||
let inBlockComment = false;
|
||||
let escaped = false;
|
||||
|
||||
for (let i = 0; i < objectBody.length; i++) {
|
||||
const ch = objectBody[i];
|
||||
const next = objectBody[i + 1];
|
||||
|
||||
if (inLineComment) {
|
||||
if (ch === "\n") inLineComment = false;
|
||||
continue;
|
||||
}
|
||||
if (inBlockComment) {
|
||||
if (ch === "*" && next === "/") {
|
||||
inBlockComment = false;
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inSingle || inDouble || inTemplate) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (ch === "\\") {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (inSingle && ch === "'") inSingle = false;
|
||||
else if (inDouble && ch === '"') inDouble = false;
|
||||
else if (inTemplate && ch === "`") inTemplate = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "/" && next === "/") {
|
||||
inLineComment = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (ch === "/" && next === "*") {
|
||||
inBlockComment = true;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "'") {
|
||||
inSingle = true;
|
||||
continue;
|
||||
}
|
||||
if (ch === '"') {
|
||||
inDouble = true;
|
||||
continue;
|
||||
}
|
||||
if (ch === "`") {
|
||||
inTemplate = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "(") depthParen++;
|
||||
else if (ch === ")") depthParen--;
|
||||
else if (ch === "{") depthBrace++;
|
||||
else if (ch === "}") depthBrace--;
|
||||
else if (ch === "[") depthBracket++;
|
||||
else if (ch === "]") depthBracket--;
|
||||
|
||||
if (ch === "," && depthParen === 0 && depthBrace === 0 && depthBracket === 0) {
|
||||
const prop = objectBody.slice(start, i).trim();
|
||||
if (prop) props.push(prop);
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const tail = objectBody.slice(start).trim();
|
||||
if (tail) props.push(tail);
|
||||
return props;
|
||||
}
|
||||
|
||||
function getRegisterToolBlocks(): Array<{ name: string; block: string }> {
|
||||
const src = readFileSync(extensionPath, "utf-8");
|
||||
const blocks: Array<{ name: string; block: string }> = [];
|
||||
const token = "pi.registerTool(";
|
||||
let from = 0;
|
||||
|
||||
while (true) {
|
||||
const start = src.indexOf(token, from);
|
||||
if (start === -1) break;
|
||||
const braceStart = src.indexOf("{", start);
|
||||
const braceEnd = findMatchingBrace(src, braceStart);
|
||||
const block = src.slice(braceStart, braceEnd + 1);
|
||||
const nameMatch = block.match(/name:\s*"(fn_[a-z_]+)"/);
|
||||
if (nameMatch) {
|
||||
blocks.push({ name: nameMatch[1], block });
|
||||
}
|
||||
from = braceEnd + 1;
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all tool names registered via pi.registerTool({ name: "..." })
|
||||
* from the extension source code.
|
||||
*/
|
||||
function getExtensionToolNames(): string[] {
|
||||
const src = readFileSync(extensionPath, "utf-8");
|
||||
const matches = [...src.matchAll(/name:\s*"(fn_[a-z_]+)"/g)];
|
||||
return matches.map((m) => m[1]).sort();
|
||||
return getRegisterToolBlocks()
|
||||
.map((entry) => entry.name)
|
||||
.sort();
|
||||
}
|
||||
|
||||
function getExtensionToolParamNames(): Map<string, string[]> {
|
||||
const result = new Map<string, string[]>();
|
||||
for (const { name, block } of getRegisterToolBlocks()) {
|
||||
const paramsStart = block.indexOf("parameters:");
|
||||
if (paramsStart === -1) {
|
||||
result.set(name, []);
|
||||
continue;
|
||||
}
|
||||
const objectStart = block.indexOf("Type.Object(", paramsStart);
|
||||
if (objectStart === -1) {
|
||||
result.set(name, []);
|
||||
continue;
|
||||
}
|
||||
const braceStart = block.indexOf("{", objectStart);
|
||||
const braceEnd = findMatchingBrace(block, braceStart);
|
||||
const body = block.slice(braceStart + 1, braceEnd);
|
||||
|
||||
const params = splitTopLevelProperties(body)
|
||||
.map((prop) => prop.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*:/)?.[1])
|
||||
.filter((value): value is string => Boolean(value))
|
||||
.sort();
|
||||
|
||||
result.set(name, params);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,13 +257,28 @@ function getDocumentedToolNames(): string[] {
|
||||
return matches.map((m) => m[1]).sort();
|
||||
}
|
||||
|
||||
function getDocumentedToolParamNames(): Map<string, string[]> {
|
||||
const doc = readFileSync(resolve(skillDir, "references/extension-tools.md"), "utf-8");
|
||||
const map = new Map<string, string[]>();
|
||||
|
||||
const toolRegex = /^### (fn_[a-z_]+)\n([\s\S]*?)(?=^### fn_|^## [A-Za-z]|^<!-- END: extension-tools -->)/gm;
|
||||
for (const match of doc.matchAll(toolRegex)) {
|
||||
const [, toolName, section] = match;
|
||||
const params = [...section.matchAll(/\| `([A-Za-z_][A-Za-z0-9_]*)` \|/g)]
|
||||
.map((m) => m[1])
|
||||
.sort();
|
||||
map.set(toolName, params);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract tool names listed in SKILL.md under the tool categories.
|
||||
*/
|
||||
function getSkillMdToolNames(): string[] {
|
||||
const doc = readFileSync(resolve(skillDir, "SKILL.md"), "utf-8");
|
||||
const matches = [...doc.matchAll(/`(fn_[a-z_]+)`/g)];
|
||||
// Deduplicate
|
||||
return [...new Set(matches.map((m) => m[1]))].sort();
|
||||
}
|
||||
|
||||
@@ -53,6 +294,22 @@ function getCapabilitiesToolNames(): string[] {
|
||||
return matches.map((m) => m[1]).sort();
|
||||
}
|
||||
|
||||
function getEngineSessionToolNames(): string[] {
|
||||
const names = new Set<string>();
|
||||
for (const path of engineToolSourceFiles) {
|
||||
const src = readFileSync(path, "utf-8");
|
||||
for (const match of src.matchAll(/name:\s*"(fn_[a-z_]+)"/g)) {
|
||||
names.add(match[1]);
|
||||
}
|
||||
}
|
||||
return [...names].sort();
|
||||
}
|
||||
|
||||
function getDocumentedEngineToolNames(): string[] {
|
||||
const doc = readFileSync(resolve(skillDir, "references/engine-tools.md"), "utf-8");
|
||||
return [...new Set([...doc.matchAll(/`(fn_[a-z_]+)`/g)].map((m) => m[1]))].sort();
|
||||
}
|
||||
|
||||
function collectMarkdownFiles(dir: string): string[] {
|
||||
const files: string[] = [];
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
@@ -105,6 +362,17 @@ describe("Skill-Extension Sync", () => {
|
||||
expect(documentedTools).toEqual(extensionTools);
|
||||
});
|
||||
|
||||
it("extension-tools.md tool parameter tables match extension.ts registrations", () => {
|
||||
const extensionTools = getExtensionToolParamNames();
|
||||
const documentedTools = getDocumentedToolParamNames();
|
||||
|
||||
for (const [toolName, extensionParams] of extensionTools.entries()) {
|
||||
expect(documentedTools.has(toolName), `missing documented section for ${toolName}`).toBe(true);
|
||||
const documentedParams = documentedTools.get(toolName) ?? [];
|
||||
expect(documentedParams).toEqual(extensionParams);
|
||||
}
|
||||
});
|
||||
|
||||
it("SKILL.md tool listing includes all registered tools", () => {
|
||||
const extensionTools = getExtensionToolNames();
|
||||
const skillTools = getSkillMdToolNames();
|
||||
@@ -123,6 +391,26 @@ describe("Skill-Extension Sync", () => {
|
||||
(t) => !capTools.includes(t),
|
||||
);
|
||||
expect(missingFromCaps).toEqual([]);
|
||||
expect(capTools).toEqual(extensionTools);
|
||||
});
|
||||
|
||||
it("fusion-capabilities.md tool table is auto-generated with markers", () => {
|
||||
const doc = readFileSync(resolve(skillDir, "references/fusion-capabilities.md"), "utf-8");
|
||||
expect(doc).toContain(CAP_TABLE_BEGIN);
|
||||
expect(doc).toContain(CAP_TABLE_END);
|
||||
});
|
||||
|
||||
it("extension-tools.md has auto-generated markers", () => {
|
||||
const doc = readFileSync(resolve(skillDir, "references/extension-tools.md"), "utf-8");
|
||||
expect(doc).toContain(EXT_TOOLS_BEGIN);
|
||||
expect(doc).toContain(EXT_TOOLS_END);
|
||||
});
|
||||
|
||||
it("engine-tools.md documents all engine session-scoped tools", () => {
|
||||
const engineTools = getEngineSessionToolNames();
|
||||
const documented = getDocumentedEngineToolNames();
|
||||
const missing = engineTools.filter((name) => !documented.includes(name));
|
||||
expect(missing).toEqual([]);
|
||||
});
|
||||
|
||||
it("covers the full Fusion skill markdown surface", () => {
|
||||
@@ -134,6 +422,7 @@ describe("Skill-Extension Sync", () => {
|
||||
"SKILL.md",
|
||||
"references/best-practices.md",
|
||||
"references/cli-commands.md",
|
||||
"references/engine-tools.md",
|
||||
"references/extension-tools.md",
|
||||
"references/fusion-capabilities.md",
|
||||
"references/skill-patterns.md",
|
||||
@@ -151,8 +440,6 @@ describe("Skill-Extension Sync", () => {
|
||||
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",
|
||||
@@ -195,8 +482,7 @@ describe("Skill-Extension Sync", () => {
|
||||
expect(dashboardCli).toContain("/fn");
|
||||
});
|
||||
|
||||
it("SKILL.md tool-categories block matches the sync script output (no drift)", () => {
|
||||
const repoRoot = resolve(cliRoot, "../..");
|
||||
it("SKILL.md and reference generated blocks match sync script output (no drift)", () => {
|
||||
const script = resolve(repoRoot, "scripts/sync-fusion-skill-tools.mjs");
|
||||
const result = spawnSync("node", [script, "--check"], {
|
||||
encoding: "utf-8",
|
||||
|
||||
Reference in New Issue
Block a user