diff --git a/README.md b/README.md index ff2a481ad..5910508dd 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ One command. Walk away. Come back to a built project with clean git history. ### Auto-Mode Resilience - **Credential cooldown recovery** — autonomous mode survives transient 429 rate-limit responses with structured cooldown errors and a bounded retry budget. -- **Fire-and-forget auto start** — auto start is detached from active turns to prevent blocking. +- **Fire-and-forget autonomous start** — autonomous startup is detached from active turns to prevent blocking. - **Scoped forensics** — stuck-loop forensics are now scoped to auto sessions only, preventing false positives in interactive use. ### TUI Improvements @@ -121,7 +121,7 @@ Full documentation is in the [`docs/`](./docs/) directory: ### User Guides - **[Getting Started](./docs/user-docs/getting-started.md)** — install, first run, basic usage -- **[Autonomous Mode](./docs/user-docs/autonomous-mode.md)** — autonomous execution deep-dive +- **[Autonomous Mode](./docs/user-docs/autonomous mode.md)** — autonomous execution deep-dive - **[Configuration](./docs/user-docs/configuration.md)** — all preferences, models, git, and hooks - **[Custom Models](./docs/user-docs/custom-models.md)** — add custom providers (Ollama, vLLM, LM Studio, proxies) - **[Token Optimization](./docs/user-docs/token-optimization.md)** — profiles, context compression, complexity routing @@ -247,7 +247,7 @@ Autonomous mode is governed by the Unified Operation Kernel (UOK), not by the LL 4. **Crash recovery** — A lock file tracks the current unit. If the session dies, the next `/sf autonomous` reads the surviving session file, synthesizes a recovery briefing from every tool call that made it to disk, and resumes with full context. Parallel orchestrator state is persisted to disk with PID liveness detection, so multi-worker sessions survive crashes too. Through the machine surface, crashes trigger automatic restart with exponential backoff (default 3 attempts). -5. **Provider error recovery** — Transient provider errors (rate limits, 500/503 server errors, overloaded) auto-resume after a delay. Permanent errors (auth, billing) pause for manual review. The model fallback chain retries transient network errors before switching models. +5. **Provider error recovery** — Transient provider errors (rate limits, 500/503 server errors, overloaded) resume automatically after a delay. Permanent errors (auth, billing) pause for manual review. The model fallback chain retries transient network errors before switching models. 6. **Stuck detection** — A sliding-window detector identifies repeated dispatch patterns (including multi-unit cycles). On detection, it retries once with a deep diagnostic. If it fails again, autonomous mode stops with the exact file it expected. diff --git a/copilot-thoughts.md b/copilot-thoughts.md new file mode 100644 index 000000000..6c38e4ca3 --- /dev/null +++ b/copilot-thoughts.md @@ -0,0 +1,115 @@ +# Copilot CLI Autopilot Notes For SF + +Sources checked 2026-05-08: + +- GitHub Docs, "Allowing GitHub Copilot CLI to work autonomously" + +- GitHub Docs, "GitHub Copilot CLI command reference" + +- GitHub Changelog, "GitHub Copilot CLI is now generally available" + +- GitHub Copilot CLI product page + +- GitHub Changelog, "Copilot CLI now supports BYOK and local models" + + +## Useful Pattern + +Copilot CLI keeps three concepts separate: + +- `--autopilot` controls whether the agent keeps continuing through multiple + model/tool turns until completion, a blocker, interruption, or a continuation + limit. +- `--allow-all` / `--yolo` expands permission to use tools, paths, and URLs. +- `--no-ask-user` suppresses clarifying questions, but does not itself create a + multi-turn continuation loop. + +That separation is the part SF should copy. + +GitHub's documented programmatic autopilot example is: + +```bash +copilot --autopilot --yolo --max-autopilot-continues 10 -p "YOUR PROMPT HERE" +``` + +So the important shape is not the word "autopilot"; it is the explicit split +between continuation (`--autopilot`), permission expansion (`--yolo` / +`--allow-all`), and a runaway-loop limiter (`--max-autopilot-continues`). + +GitHub also presents a strong interactive handoff: plan first, then accept the +plan and build on autopilot. SF's equivalent should be "accept plan and run +autonomously", backed by UOK state rather than a separate mode. + +## Copilot CLI Capabilities Worth Tracking + +- Plan mode can transition directly into autopilot. +- `/fleet` runs parallel subagents. +- `/remote` supports steering from another device. +- `/tasks` exposes background tasks. +- `/session` exposes session info, checkpoints, files, plans, cleanup, and + pruning. +- `/skills`, `/plugin`, `/mcp`, and `/agent` customize behavior and tool access. +- BYOK/local-model/offline mode exists; built-in subagents inherit the selected + provider configuration. + +## SF Competitive Read + +Copilot CLI's public autopilot story is polished: plan, approve, continue +without step-by-step approval, cap continuation, steer remotely, inspect tasks +and sessions. SF already has deeper autonomous machinery: UOK policy gates, +DB-backed state, recovery, verification, scheduling, captures, forensics, +projections, and self-reporting. + +The gap to close is presentation and control surface clarity, not core +autonomous capability. + +## SF Names + +SF should not import Copilot's `autopilot` product name. In SF, run control is: + +- `manual` +- `assisted` +- `autonomous` + +SF permission profile is separate: + +- `restricted` +- `normal` +- `trusted` +- `unrestricted` + +SF surfaces and encodings are also separate: + +- Surface: TUI, CLI, web, editor, machine surface. +- Output format: `text`, `json`, `stream-json`. +- Protocol: RPC, stdio JSON-RPC, ACP, HTTP/RPC, wire. + +## Decisions + +- Use `/autonomous` for continuous run control. +- Use `sf headless` only for the machine surface command name. +- Use `--autonomous` to chain milestone creation into autonomous mode. +- Reject `--auto`, `--full`, and `--auto-dispatch` instead of silently mapping + them. +- Do not use "autopilot" as SF product copy. Keep it as competitor context + only; the SF product term is autonomous mode. +- Keep question behavior driven by run control and policy gates, not by the word + `headless`. +- Keep permission expansion driven by permission profile, not by autonomous run + control. + +## Implementation Pull-Through + +- UOK lifecycle records carry `runControl`. +- UOK lifecycle records and execution-policy decisions carry + `permissionProfile`. +- Schedule command state uses `autonomous_dispatch`. +- Human docs describe docs/specs as exports from `.sf`/SQLite working state. +- User-facing planning should offer an obvious "accept plan and run + autonomously" route. +- Status surfaces should make autonomous background work as inspectable as + Copilot's `/tasks` and `/session` surfaces. +- Continuation limits should be explicit in autonomous settings and status. + +The target model is simple: same flow, different surfaces; same run-control +names, different permission profiles; same output, different encodings. diff --git a/docs/specs/sf-operating-model.md b/docs/specs/sf-operating-model.md index b83bb69f9..c8c2a3ff2 100644 --- a/docs/specs/sf-operating-model.md +++ b/docs/specs/sf-operating-model.md @@ -72,11 +72,10 @@ Run control describes how far SF continues through the flow before stopping for `auto` is not a run-control mode. Use **autonomous** for continuous run control; use **assisted** for bounded human-guided progression. -> Implementation note: if product language uses "autopilot", it maps to -> autonomous run control. It must not introduce a separate flow, protocol, -> output format, or compatibility alias. Autopilot means the UOK-governed -> autonomous controller keeps moving until one of its explicit stop conditions -> fires. +> Competitor note: Copilot CLI calls continuous run control autopilot. +> SF does not use that product name. The SF term is autonomous mode, +> and it stays separate from permission profiles, surfaces, protocols, +> and output formats. UOK kernel records carry `runControl` as a first-class lifecycle field. Workflow phases such as planning, building, verification, and finalization are separate execution stages, not run-control modes. @@ -114,18 +113,13 @@ Markdown under `.sf/` has two roles: Markdown under `docs/specs/` is a human export for review, navigation, and git history. Generated docs can change; Git records that human-facing history. If SF needs its own operational history, it should store that in `.sf`/DB-backed state. Plans should record any surface, protocol, output-format, run-control, or permission-profile impact explicitly when a milestone changes integration behavior. -Reflection notes, capture files, and session thoughts are input material, not a -parallel backlog. Autonomous mode may triage them, but durable outcomes must -graduate into DB-backed requirements, decisions, knowledge, roadmap rows, -tests, or tracked documentation. - ## Source Placement SF source placement follows the same axis model. New code should extend the owning axis instead of creating parallel trees. ### Core Flow -- `src/resources/extensions/sf/` owns the SF workflow extension: planning tools, UOK/runtime state, `/sf` commands, prompts, templates, doctors, schedule, and DB-backed state. +- `src/resources/extensions/sf/` owns the SF workflow extension: planning tools, UOK/runtime state, `/next` commands, prompts, templates, doctors, schedule, and DB-backed state. - `src/resources/extensions/` owns bundled extension packages loaded into the runtime. - `src/resources/agents/`, `src/resources/skills/`, and `src/resources/workflows/` own bundled runtime resources, not independent product flows. diff --git a/docs/specs/sf-schedule.md b/docs/specs/sf-schedule.md index 20924410d..dfba82bc9 100644 --- a/docs/specs/sf-schedule.md +++ b/docs/specs/sf-schedule.md @@ -101,7 +101,7 @@ Legacy `schedule.jsonl` files are import-only compatibility inputs. Rows without ## CLI Reference -All commands are invoked as `/sf schedule ` in the TUI or `sf schedule ` from the shell. +All commands are invoked as `/schedule ` in the TUI or `sf schedule ` from the shell. ### `sf schedule add` @@ -207,7 +207,7 @@ sf schedule run 01ARZ3ND On every SF startup, `loader.ts` calls `findDue()` for both project and global scopes. If any items are due, it prints: ``` -[forge] N scheduled item(s) due now. Manage: /sf schedule list +[forge] N scheduled item(s) due now. Manage: /schedule list ``` ### Machine Snapshot (`sf headless query`) diff --git a/docs/user-docs/autonomous-mode.md b/docs/user-docs/autonomous-mode.md index 0ea134972..888369f76 100644 --- a/docs/user-docs/autonomous-mode.md +++ b/docs/user-docs/autonomous-mode.md @@ -1,11 +1,11 @@ # Autonomous Mode -Autonomous mode is SF's product-development execution engine for the purpose-to-software compiler. It advances only from structured state: bounded intent, PDD fields, research assumptions, tests or executable evidence, implementation, verification, and recorded outcomes. Run `/sf autonomous`, walk away, come back to built software with clean git history. +Autonomous mode is SF's product-development execution engine for the purpose-to-software compiler. It advances only from structured state: bounded intent, PDD fields, research assumptions, tests or executable evidence, implementation, verification, and recorded outcomes. Run `/autonomous`, walk away, come back to built software with clean git history. -> Terminology: "autopilot" is user-facing shorthand for autonomous mode. It is -> not a second mode, not a looser `auto` compatibility path, and not a permission -> bypass. The same UOK policy, evidence, budget, blocker, and completion gates -> decide how far it continues. +> Terminology: SF uses **autonomous mode** for continuous run control. It is +> not `auto`, not `autopilot`, not `headless`, and not a permission bypass. The +> same UOK policy, evidence, budget, blocker, and completion gates decide how +> far it continues. ## How It Works @@ -64,13 +64,13 @@ When your project has independent milestones, you can run them simultaneously. E ### Crash Recovery -A lock file tracks the current unit. If the session dies, the next `/sf autonomous` reads the surviving session file, synthesizes a recovery briefing from every tool call that made it to disk, and resumes with full context. +A lock file tracks the current unit. If the session dies, the next `/autonomous` reads the surviving session file, synthesizes a recovery briefing from every tool call that made it to disk, and resumes with full context. -**Machine-surface auto-restart (v2.26):** When running `sf headless autonomous`, crashes trigger automatic restart with exponential backoff (5s → 10s → 30s cap, default 3 attempts). Configure with `--max-restarts N`. SIGINT/SIGTERM bypasses restart. Combined with crash recovery, this enables true overnight "run until done" execution. `headless` selects the non-interactive surface; `autonomous` selects run control. +**Machine-surface automatic restart (v2.26):** When running `sf headless autonomous`, crashes trigger automatic restart with exponential backoff (5s → 10s → 30s cap, default 3 attempts). Configure with `--max-restarts N`. SIGINT/SIGTERM bypasses restart. Combined with crash recovery, this enables true overnight "run until done" execution. `headless` selects the non-interactive surface; `autonomous` selects run control. ### Provider Error Recovery -SF classifies provider errors and auto-resumes when safe: +SF classifies provider errors and resumes automatically when safe: | Error type | Examples | Action | |-----------|----------|--------| @@ -106,16 +106,16 @@ The sliding-window approach reduces false positives on legitimate retries (e.g., ### Post-Mortem Investigation (v2.40) -`/sf forensics` is a full-access SF debugger for post-mortem analysis of autonomous mode failures. It provides: +`/forensics` is a full-access SF debugger for post-mortem analysis of autonomous mode failures. It provides: - **Anomaly detection** — structured identification of stuck loops, cost spikes, timeouts, missing artifacts, and crashes with severity levels - **Unit traces** — last 10 unit executions with error details and execution times - **Metrics analysis** — cost, token counts, and execution time breakdowns -- **Doctor integration** — includes structural health issues from `/sf doctor` +- **Doctor integration** — includes structural health issues from `/doctor` - **LLM-guided investigation** — an agent session with full tool access to investigate root causes ``` -/sf forensics [optional problem description] +/forensics [optional problem description] ``` See [Troubleshooting](./troubleshooting.md) for more on diagnosing issues. @@ -183,7 +183,7 @@ After a milestone completes, SF auto-generates a self-contained HTML report in ` auto_report: true # enabled by default ``` -Generate manually anytime with `/sf export --html`, or generate reports for all milestones at once with `/sf export --html --all` (v2.28). +Generate manually anytime with `/export --html`, or generate reports for all milestones at once with `/export --html --all` (v2.28). ### Failure Recovery (v2.28) @@ -203,7 +203,7 @@ This linear flow is easier to debug, uses less memory (no recursive call stack), ### Real-Time Health Visibility (v2.40) -Doctor issues (from `/sf doctor`) now surface in real time across three places: +Doctor issues (from `/doctor`) now surface in real time across three places: - **Dashboard widget** — health indicator with issue count and severity - **Workflow visualizer** — issues shown in the status panel @@ -226,7 +226,7 @@ See [Configuration](./configuration.md) for skill routing preferences. ### Start ``` -/sf autonomous +/autonomous ``` ### Pause @@ -236,7 +236,7 @@ Press **Escape**. The conversation is preserved. You can interact with the agent ### Resume ``` -/sf autonomous +/autonomous ``` Autonomous mode reads disk state and picks up where it left off. @@ -244,7 +244,7 @@ Autonomous mode reads disk state and picks up where it left off. ### Stop ``` -/sf stop +/stop ``` Stops autonomous mode gracefully. Can be run from a different terminal. @@ -252,7 +252,7 @@ Stops autonomous mode gracefully. Can be run from a different terminal. ### Steer ``` -/sf steer +/steer ``` Hard-steer plan documents during execution without stopping the pipeline. Changes are picked up at the next phase boundary. @@ -260,7 +260,7 @@ Hard-steer plan documents during execution without stopping the pipeline. Change ### Capture ``` -/sf capture "add rate limiting to API endpoints" +/capture "add rate limiting to API endpoints" ``` Fire-and-forget thought capture. Captures are triaged automatically between tasks. See [Captures & Triage](./captures-triage.md). @@ -268,14 +268,14 @@ Fire-and-forget thought capture. Captures are triaged automatically between task ### Visualize ``` -/sf visualize +/visualize ``` Open the workflow visualizer — interactive tabs for progress, dependencies, metrics, and timeline. See [Workflow Visualizer](./visualizer.md). ## Dashboard -`Ctrl+Alt+G` or `/sf status` shows real-time progress: +`Ctrl+Alt+G` or `/status` shows real-time progress: - Current milestone, slice, and task - Autonomous mode elapsed time and phase diff --git a/docs/user-docs/captures-triage.md b/docs/user-docs/captures-triage.md index c1ff34369..4c41bd111 100644 --- a/docs/user-docs/captures-triage.md +++ b/docs/user-docs/captures-triage.md @@ -9,8 +9,8 @@ Captures let you fire-and-forget thoughts during autonomous mode execution. Inst While autonomous mode is running (or any time): ``` -/sf capture "add rate limiting to the API endpoints" -/sf capture "the auth flow should support OAuth, not just JWT" +/capture "add rate limiting to the API endpoints" +/capture "the auth flow should support OAuth, not just JWT" ``` Captures are appended to `.sf/CAPTURES.md` and triaged automatically between tasks. @@ -23,7 +23,7 @@ Captures are appended to `.sf/CAPTURES.md` and triaged automatically between tas capture → triage → confirm → resolve → resume ``` -1. **Capture** — `/sf capture "thought"` appends to `.sf/CAPTURES.md` with a timestamp and unique ID +1. **Capture** — `/capture "thought"` appends to `.sf/CAPTURES.md` with a timestamp and unique ID 2. **Triage** — at natural seams between tasks (in `handleAgentEnd`), SF detects pending captures and classifies them 3. **Confirm** — the user is shown the proposed resolution and confirms or adjusts 4. **Resolve** — the resolution is applied (task injection, replan trigger, deferral, etc.) @@ -55,7 +55,7 @@ The LLM classifies each capture and proposes a resolution. Plan-modifying resolu Trigger triage manually at any time: ``` -/sf triage +/triage ``` This is useful when you've accumulated several captures and want to process them before the next natural seam. @@ -78,5 +78,5 @@ Captures always resolve to the **original project root's** `.sf/CAPTURES.md`, no | Command | Description | |---------|-------------| -| `/sf capture "text"` | Capture a thought (quotes optional for single words) | -| `/sf triage` | Manually trigger triage of pending captures | +| `/capture "text"` | Capture a thought (quotes optional for single words) | +| `/triage` | Manually trigger triage of pending captures | diff --git a/docs/user-docs/commands.md b/docs/user-docs/commands.md index 22776d6d8..12c348389 100644 --- a/docs/user-docs/commands.md +++ b/docs/user-docs/commands.md @@ -4,78 +4,78 @@ | Command | Description | |---------|-------------| -| `/sf` | Assisted mode — execute one unit at a time, pause between each | -| `/sf next` | Explicit assisted mode (same as `/sf`) | -| `/sf autonomous` | Autonomous product loop — research, plan, execute, commit, repeat | -| `/sf quick` | Execute a quick task with SF guarantees (atomic commits, state tracking) without full planning overhead | -| `/sf stop` | Stop autonomous mode gracefully | -| `/sf pause` | Pause autonomous mode (preserves state, `/sf autonomous` to resume) | -| `/sf steer` | Hard-steer plan documents during execution | -| `/sf discuss` | Discuss architecture and decisions (works alongside autonomous mode) | -| `/sf status` | Progress dashboard | -| `/sf widget` | Cycle dashboard widget: full / small / min / off | -| `/sf queue` | Queue and reorder future milestones (safe during autonomous mode) | -| `/sf capture` | Fire-and-forget thought capture (works during autonomous mode) | -| `/sf triage` | Manually trigger triage of pending captures | -| `/sf dispatch` | Dispatch a specific phase directly (research, plan, execute, complete, reassess, uat, replan) | -| `/sf history` | View execution history (supports `--cost`, `--phase`, `--model` filters) | -| `/sf forensics` | Full-access SF debugger — structured anomaly detection, unit traces, and LLM-guided root-cause analysis for autonomous mode failures | -| `/sf cleanup` | Clean up SF state files and stale worktrees | -| `/sf visualize` | Open workflow visualizer (progress, deps, metrics, timeline) | -| `/sf export --html` | Generate self-contained HTML report for current or completed milestone | -| `/sf export --html --all` | Generate retrospective reports for all milestones at once | -| `/sf update` | Update SF to the latest version in-session | -| `/sf knowledge` | Add persistent project knowledge (rule, pattern, or lesson) | -| `/sf fast` | Toggle service tier for supported models (prioritized API routing) | -| `/sf rate` | Rate last unit's model tier (over/ok/under) — improves adaptive routing | -| `/sf changelog` | Show categorized release notes | -| `/sf logs` | Browse activity logs, debug logs, and metrics | -| `/sf remote` | Configure remote question delivery | -| `/sf help` | Categorized command reference with descriptions for all SF subcommands | +| `/next` | Assisted mode — execute one unit at a time, pause between each | +| `/next` | Explicit assisted mode (same as `/next`) | +| `/autonomous` | Autonomous product loop — research, plan, execute, commit, repeat | +| `/quick` | Execute a quick task with SF guarantees (atomic commits, state tracking) without full planning overhead | +| `/stop` | Stop autonomous mode gracefully | +| `/pause` | Pause autonomous mode (preserves state, `/autonomous` to resume) | +| `/steer` | Hard-steer plan documents during execution | +| `/discuss` | Discuss architecture and decisions (works alongside autonomous mode) | +| `/status` | Progress dashboard | +| `/widget` | Cycle dashboard widget: full / small / min / off | +| `/queue` | Queue and reorder future milestones (safe during autonomous mode) | +| `/capture` | Fire-and-forget thought capture (works during autonomous mode) | +| `/triage` | Manually trigger triage of pending captures | +| `/dispatch` | Dispatch a specific phase directly (research, plan, execute, complete, reassess, uat, replan) | +| `/history` | View execution history (supports `--cost`, `--phase`, `--model` filters) | +| `/forensics` | Full-access SF debugger — structured anomaly detection, unit traces, and LLM-guided root-cause analysis for autonomous mode failures | +| `/cleanup` | Clean up SF state files and stale worktrees | +| `/visualize` | Open workflow visualizer (progress, deps, metrics, timeline) | +| `/export --html` | Generate self-contained HTML report for current or completed milestone | +| `/export --html --all` | Generate retrospective reports for all milestones at once | +| `/update` | Update SF to the latest version in-session | +| `/knowledge` | Add persistent project knowledge (rule, pattern, or lesson) | +| `/fast` | Toggle service tier for supported models (prioritized API routing) | +| `/rate` | Rate last unit's model tier (over/ok/under) — improves adaptive routing | +| `/changelog` | Show categorized release notes | +| `/logs` | Browse activity logs, debug logs, and metrics | +| `/remote` | Configure remote question delivery | +| `/help` | Categorized command reference with descriptions for all SF subcommands | ## Configuration & Diagnostics | Command | Description | |---------|-------------| -| `/sf prefs` | Model selection, timeouts, budget ceiling | -| `/sf mode` | Switch workflow mode (solo/team) with coordinated defaults for milestone IDs, git commit behavior, and documentation | -| `/sf config` | Re-run the provider setup wizard (LLM provider + tool keys) | -| `/sf keys` | API key manager — list, add, remove, test, rotate, doctor | -| `/sf doctor` | Runtime health checks with auto-fix — issues surface in real time across widget, visualizer, and HTML reports (v2.40) | -| `/sf inspect` | Show SQLite DB diagnostics | -| `/sf init` | Project init wizard — detect, configure, bootstrap `.sf/` | -| `/sf setup` | Global setup status and configuration | -| `/sf skill-health` | Skill lifecycle dashboard — usage stats, success rates, token trends, staleness warnings | -| `/sf skill-health ` | Detailed view for a single skill | -| `/sf skill-health --declining` | Show only skills flagged for declining performance | -| `/sf skill-health --stale N` | Show skills unused for N+ days | -| `/sf hooks` | Show configured post-unit and pre-dispatch hooks | -| `/sf run-hook` | Manually trigger a specific hook | -| `/sf migrate` | Migrate a v1 `.planning` directory to `.sf` format | +| `/prefs` | Model selection, timeouts, budget ceiling | +| `/mode` | Switch workflow mode (solo/team) with coordinated defaults for milestone IDs, git commit behavior, and documentation | +| `/config` | Re-run the provider setup wizard (LLM provider + tool keys) | +| `/keys` | API key manager — list, add, remove, test, rotate, doctor | +| `/doctor` | Runtime health checks with auto-fix — issues surface in real time across widget, visualizer, and HTML reports (v2.40) | +| `/inspect` | Show SQLite DB diagnostics | +| `/init` | Project init wizard — detect, configure, bootstrap `.sf/` | +| `/setup` | Global setup status and configuration | +| `/skill-health` | Skill lifecycle dashboard — usage stats, success rates, token trends, staleness warnings | +| `/skill-health ` | Detailed view for a single skill | +| `/skill-health --declining` | Show only skills flagged for declining performance | +| `/skill-health --stale N` | Show skills unused for N+ days | +| `/hooks` | Show configured post-unit and pre-dispatch hooks | +| `/run-hook` | Manually trigger a specific hook | +| `/migrate` | Migrate a v1 `.planning` directory to `.sf` format | ## Milestone Management | Command | Description | |---------|-------------| -| `/sf new-milestone` | Create a new milestone | -| `/sf skip` | Prevent a unit from autonomous mode dispatch | -| `/sf undo` | Revert last completed unit | -| `/sf undo-task` | Reset a specific task's completion state (DB + markdown) | -| `/sf reset-slice` | Reset a slice and all its tasks (DB + markdown) | -| `/sf park` | Park a milestone — skip without deleting | -| `/sf unpark` | Reactivate a parked milestone | -| Discard milestone | Available via `/sf` wizard → "Milestone actions" → "Discard" | +| `/new-milestone` | Create a new milestone | +| `/skip` | Prevent a unit from autonomous mode dispatch | +| `/undo` | Revert last completed unit | +| `/undo-task` | Reset a specific task's completion state (DB + markdown) | +| `/reset-slice` | Reset a slice and all its tasks (DB + markdown) | +| `/park` | Park a milestone — skip without deleting | +| `/unpark` | Reactivate a parked milestone | +| Discard milestone | Available via `/next` wizard → "Milestone actions" → "Discard" | ## Parallel Orchestration | Command | Description | |---------|-------------| -| `/sf parallel start` | Analyze eligibility, confirm, and start workers | -| `/sf parallel status` | Show all workers with state, progress, and cost | -| `/sf parallel stop [MID]` | Stop all workers or a specific milestone's worker | -| `/sf parallel pause [MID]` | Pause all workers or a specific one | -| `/sf parallel resume [MID]` | Resume paused workers | -| `/sf parallel merge [MID]` | Merge completed milestones back to main | +| `/parallel start` | Analyze eligibility, confirm, and start workers | +| `/parallel status` | Show all workers with state, progress, and cost | +| `/parallel stop [MID]` | Stop all workers or a specific milestone's worker | +| `/parallel pause [MID]` | Pause all workers or a specific one | +| `/parallel resume [MID]` | Resume paused workers | +| `/parallel merge [MID]` | Merge completed milestones back to main | See [Parallel Orchestration](./parallel-orchestration.md) for full documentation. @@ -83,43 +83,43 @@ See [Parallel Orchestration](./parallel-orchestration.md) for full documentation | Command | Description | |---------|-------------| -| `/sf start` | Start a workflow template (bugfix, spike, feature, hotfix, refactor, security-audit, dep-upgrade, full-project) | -| `/sf start resume` | Resume an in-progress workflow | -| `/sf templates` | List available workflow templates | -| `/sf templates info ` | Show detailed template info | +| `/start` | Start a workflow template (bugfix, spike, feature, hotfix, refactor, security-audit, dep-upgrade, full-project) | +| `/start resume` | Resume an in-progress workflow | +| `/templates` | List available workflow templates | +| `/templates info ` | Show detailed template info | ## Custom Workflows (v2.42) | Command | Description | |---------|-------------| -| `/sf workflow new` | Create a new workflow definition (via skill) | -| `/sf workflow run ` | Create a run and start autonomous mode | -| `/sf workflow list` | List workflow runs | -| `/sf workflow validate ` | Validate a workflow definition YAML | -| `/sf workflow pause` | Pause custom workflow autonomous mode | -| `/sf workflow resume` | Resume paused custom workflow autonomous mode | +| `/workflow new` | Create a new workflow definition (via skill) | +| `/workflow run ` | Create a run and start autonomous mode | +| `/workflow list` | List workflow runs | +| `/workflow validate ` | Validate a workflow definition YAML | +| `/workflow pause` | Pause custom workflow autonomous mode | +| `/workflow resume` | Resume paused custom workflow autonomous mode | -`/sf autonomous` is the product-development loop that chooses the next useful unit from project state. `/sf start` is guided workflow kickoff and may ask clarifying questions. `/sf workflow run` executes an explicit YAML workflow definition. There is no separate `/sf auto` mode. +`/autonomous` is the product-development loop that chooses the next useful unit from project state. `/start` is guided workflow kickoff and may ask clarifying questions. `/workflow run` executes an explicit YAML workflow definition. There is no separate `/auto` mode. ## Extensions | Command | Description | |---------|-------------| -| `/sf extensions list` | List all extensions and their status | -| `/sf extensions enable ` | Enable a disabled extension | -| `/sf extensions disable ` | Disable an extension | -| `/sf extensions info ` | Show extension details | +| `/extensions list` | List all extensions and their status | +| `/extensions enable ` | Enable a disabled extension | +| `/extensions disable ` | Disable an extension | +| `/extensions info ` | Show extension details | ## cmux Integration | Command | Description | |---------|-------------| -| `/sf cmux status` | Show cmux detection, prefs, and capabilities | -| `/sf cmux on` | Enable cmux integration | -| `/sf cmux off` | Disable cmux integration | -| `/sf cmux notifications on/off` | Toggle cmux desktop notifications | -| `/sf cmux sidebar on/off` | Toggle cmux sidebar metadata | -| `/sf cmux splits on/off` | Toggle cmux visual subagent splits | +| `/cmux status` | Show cmux detection, prefs, and capabilities | +| `/cmux on` | Enable cmux integration | +| `/cmux off` | Disable cmux integration | +| `/cmux notifications on/off` | Toggle cmux desktop notifications | +| `/cmux sidebar on/off` | Toggle cmux sidebar metadata | +| `/cmux splits on/off` | Toggle cmux visual subagent splits | ## GitHub Sync (v2.39) @@ -236,7 +236,7 @@ echo "Build a CLI tool" | sf headless new-milestone --context - **Exit codes:** `0` = complete, `1` = error or timeout, `2` = blocked. -Any `/sf` subcommand works as a positional argument — `sf headless status`, `sf headless doctor`, `sf headless dispatch execute`, etc. +Any `/next` subcommand works as a positional argument — `sf headless status`, `sf headless doctor`, `sf headless dispatch execute`, etc. ### `sf headless query` @@ -280,15 +280,15 @@ sf headless query | jq '.cost.total' ## MCP Integrations -`/sf mcp` shows configured external MCP tool servers. SF does not expose its own -workflow as an MCP server; run SF directly with `sf` or `/sf autonomous`. +`/mcp` shows configured external MCP tool servers. SF does not expose its own +workflow as an MCP server; run SF directly with `sf` or `/autonomous`. ## In-Session Update -`/sf update` checks npm for a newer version of SF and installs it without leaving the session. +`/update` checks npm for a newer version of SF and installs it without leaving the session. ```bash -/sf update +/update # Current version: v2.36.0 # Checking npm registry... # Updated to v2.37.0. Restart SF to use the new version. @@ -298,14 +298,14 @@ If already up to date, it reports so and takes no action. ## Export -`/sf export` generates reports of milestone work. +`/export` generates reports of milestone work. ```bash # Generate HTML report for the active milestone -/sf export --html +/export --html # Generate retrospective reports for ALL milestones at once -/sf export --html --all +/export --html --all ``` Reports are saved to `.sf/reports/` with a browseable `index.html` that links to all generated snapshots. diff --git a/docs/user-docs/configuration.md b/docs/user-docs/configuration.md index a67cd9fa1..236e11533 100644 --- a/docs/user-docs/configuration.md +++ b/docs/user-docs/configuration.md @@ -1,20 +1,20 @@ # Configuration -SF preferences live in `~/.sf/PREFERENCES.md` (global) or `.sf/PREFERENCES.md` (project-local). Manage interactively with `/sf prefs`. +SF preferences live in `~/.sf/PREFERENCES.md` (global) or `.sf/PREFERENCES.md` (project-local). Manage interactively with `/prefs`. -## `/sf prefs` Commands +## `/prefs` Commands | Command | Description | |---------|-------------| -| `/sf prefs` | Open the global preferences wizard (default) | -| `/sf prefs global` | Interactive wizard for global preferences (`~/.sf/PREFERENCES.md`) | -| `/sf prefs project` | Interactive wizard for project preferences (`.sf/PREFERENCES.md`) | -| `/sf prefs status` | Show current preference files, merged values, and skill resolution status | -| `/sf prefs wizard` | Alias for `/sf prefs global` | -| `/sf prefs setup` | Alias for `/sf prefs wizard` — creates preferences file if missing | -| `/sf prefs import-claude` | Import Claude marketplace plugins and skills as namespaced SF components | -| `/sf prefs import-claude global` | Import to global scope | -| `/sf prefs import-claude project` | Import to project scope | +| `/prefs` | Open the global preferences wizard (default) | +| `/prefs global` | Interactive wizard for global preferences (`~/.sf/PREFERENCES.md`) | +| `/prefs project` | Interactive wizard for project preferences (`.sf/PREFERENCES.md`) | +| `/prefs status` | Show current preference files, merged values, and skill resolution status | +| `/prefs wizard` | Alias for `/prefs global` | +| `/prefs setup` | Alias for `/prefs wizard` — creates preferences file if missing | +| `/prefs import-claude` | Import Claude marketplace plugins and skills as namespaced SF components | +| `/prefs import-claude global` | Import to global scope | +| `/prefs import-claude project` | Import to project scope | ## Preferences File Format @@ -50,12 +50,12 @@ token_profile: balanced - **Array fields** (`always_use_skills`, etc.): concatenated (global first, then project) - **Object fields** (`models`, `git`, `auto_supervisor`): shallow-merged, project overrides per-key -## Global API Keys (`/sf config`) +## Global API Keys (`/config`) -Tool API keys are stored globally in `~/.sf/agent/auth.json` and apply to all projects automatically. Set them once with `/sf config` — no need to configure per-project `.env` files. +Tool API keys are stored globally in `~/.sf/agent/auth.json` and apply to all projects automatically. Set them once with `/config` — no need to configure per-project `.env` files. ```bash -/sf config +/config ``` This opens an interactive wizard showing which keys are configured and which are missing. Select a tool to enter its key. @@ -71,7 +71,7 @@ This opens an interactive wizard showing which keys are configured and which are ### How it works -1. `/sf config` saves keys to `~/.sf/agent/auth.json` +1. `/config` saves keys to `~/.sf/agent/auth.json` 2. On every session start, `loadToolApiKeys()` reads the file and sets environment variables 3. Keys apply to all projects — no per-project setup required 4. Environment variables (`export BRAVE_API_KEY=...`) take precedence over saved keys @@ -649,7 +649,7 @@ custom_instructions: - "Prefer functional patterns over classes" ``` -For project-specific knowledge (patterns, gotchas, lessons learned), use `.sf/KNOWLEDGE.md` instead — it's injected into every agent prompt automatically. Add entries with `/sf knowledge rule|pattern|lesson `. +For project-specific knowledge (patterns, gotchas, lessons learned), use `.sf/KNOWLEDGE.md` instead — it's injected into every agent prompt automatically. Add entries with `/knowledge rule|pattern|lesson `. ### `RUNTIME.md` — Runtime Context (v2.39) @@ -708,7 +708,7 @@ context_management: ### `service_tier` (v2.42) -OpenAI service tier preference for supported models. Toggle with `/sf fast`. +OpenAI service tier preference for supported models. Toggle with `/fast`. | Value | Behavior | |-------|----------| @@ -722,7 +722,7 @@ service_tier: priority ### `forensics_dedup` (v2.43) -Opt-in: search existing issues and PRs before filing from `/sf forensics`. Uses additional AI tokens. +Opt-in: search existing issues and PRs before filing from `/forensics`. Uses additional AI tokens. ```yaml forensics_dedup: true # default: false @@ -823,7 +823,7 @@ notifications: auto_visualize: true # Service tier -service_tier: priority # "priority" or "flex" (for /sf fast) +service_tier: priority # "priority" or "flex" (for /fast) # Diagnostics forensics_dedup: true # deduplicate before filing forensics issues diff --git a/docs/user-docs/cost-management.md b/docs/user-docs/cost-management.md index aa449547e..4020d0ee8 100644 --- a/docs/user-docs/cost-management.md +++ b/docs/user-docs/cost-management.md @@ -16,7 +16,7 @@ Data is stored in `.sf/metrics.json` and survives across sessions. ### Viewing Costs -**Dashboard:** `Ctrl+Alt+G` or `/sf status` shows real-time cost breakdown. +**Dashboard:** `Ctrl+Alt+G` or `/status` shows real-time cost breakdown. **Aggregations available:** - By phase (research, planning, execution, completion, reassessment) @@ -85,9 +85,9 @@ See [Token Optimization](./token-optimization.md) for details. ## Tips - Start with `balanced` profile and a generous `budget_ceiling` to establish baseline costs -- Check `/sf status` after a few slices to see per-slice cost averages +- Check `/status` after a few slices to see per-slice cost averages - Switch to `budget` profile for well-understood, repetitive work - Use `quality` only when architectural decisions are being made - Per-phase model selection lets you use Opus only for planning while keeping execution on Sonnet - Enable `dynamic_routing` for automatic model downgrading on simple tasks — see [Dynamic Model Routing](./dynamic-model-routing.md) -- Use `/sf visualize` → Metrics tab to see where your budget is going +- Use `/visualize` → Metrics tab to see where your budget is going diff --git a/docs/user-docs/getting-started.md b/docs/user-docs/getting-started.md index 8c4054cea..8645b1231 100644 --- a/docs/user-docs/getting-started.md +++ b/docs/user-docs/getting-started.md @@ -168,9 +168,9 @@ Or configure per-phase models in preferences — see [Configuration](./configura ## Two Ways to Work -### Step Mode — `/sf` +### Assisted Mode — `/next` -Type `/sf` inside a session. SF executes one unit of work at a time, pausing between each with a wizard showing what completed and what's next. +Type `/next` inside a session. SF executes one unit of work at a time, pausing between each with a wizard showing what completed and what's next. - **No `.sf/` directory** — starts a discussion flow to capture your project vision - **Milestone exists, no roadmap** — discuss or research the milestone @@ -179,12 +179,12 @@ Type `/sf` inside a session. SF executes one unit of work at a time, pausing bet Assisted mode keeps you in the loop, reviewing output between each step. -### Autonomous Mode — `/sf autonomous` +### Autonomous Mode — `/autonomous` -Type `/sf autonomous` and walk away. SF researches, plans, executes, verifies, commits, and advances through every slice until the milestone is complete. `/sf autonomous` remains available as a short alias. +Type `/autonomous` and walk away. SF researches, plans, executes, verifies, commits, and advances through every slice until the milestone is complete. `/autonomous` remains available as a short alias. ``` -/sf autonomous +/autonomous ``` See [Autonomous Mode](./autonomous-mode.md) for full details. @@ -199,16 +199,16 @@ Run autonomous mode in one terminal, steer from another. ```bash sf -/sf autonomous +/autonomous ``` **Terminal 2 — steer while it works:** ```bash sf -/sf discuss # talk through architecture decisions -/sf status # check progress -/sf queue # queue the next milestone +/discuss # talk through architecture decisions +/status # check progress +/queue # queue the next milestone ``` Both terminals read and write the same `.sf/` files. Decisions in terminal 2 are picked up at the next phase boundary automatically. @@ -296,7 +296,7 @@ npm update -g singularity-forge Or from within a session: ``` -/sf update +/update ``` --- diff --git a/docs/user-docs/git-strategy.md b/docs/user-docs/git-strategy.md index 4e8c13e8e..20d846829 100644 --- a/docs/user-docs/git-strategy.md +++ b/docs/user-docs/git-strategy.md @@ -180,7 +180,7 @@ SF includes automatic recovery for common git issues: - **Stale lock files** — removes `index.lock` files from crashed processes - **Orphaned worktrees** — detects and offers to clean up abandoned worktrees (worktree mode only) -Run `/sf doctor` to check git health manually. +Run `/doctor` to check git health manually. ## Native Git Operations diff --git a/docs/user-docs/migration.md b/docs/user-docs/migration.md index f99bb1444..60d9d0ecc 100644 --- a/docs/user-docs/migration.md +++ b/docs/user-docs/migration.md @@ -6,10 +6,10 @@ If you have projects with `.planning` directories from the original Singularity ```bash # From within the project directory -/sf migrate +/migrate # Or specify a path -/sf migrate ~/projects/my-old-project +/migrate ~/projects/my-old-project ``` ## What Gets Migrated @@ -42,7 +42,7 @@ Migration works best with a `ROADMAP.md` file for milestone structure. Without o After migrating, verify the output with: ``` -/sf doctor +/doctor ``` This checks `.sf/` integrity and flags any structural issues. diff --git a/docs/user-docs/parallel-orchestration.md b/docs/user-docs/parallel-orchestration.md index fd7c1dbe0..23b954d3c 100644 --- a/docs/user-docs/parallel-orchestration.md +++ b/docs/user-docs/parallel-orchestration.md @@ -19,7 +19,7 @@ parallel: 2. Start parallel execution: ``` -/sf parallel start +/parallel start ``` SF scans your milestones, checks dependencies and file overlap, shows an eligibility report, and spawns workers for eligible milestones. @@ -27,13 +27,13 @@ SF scans your milestones, checks dependencies and file overlap, shows an eligibi 3. Monitor progress: ``` -/sf parallel status +/parallel status ``` 4. Stop when done: ``` -/sf parallel stop +/parallel stop ``` ## How It Works @@ -143,26 +143,26 @@ parallel: | Key | Type | Default | Description | |-----|------|---------|-------------| -| `enabled` | boolean | `false` | Master toggle. Must be `true` for `/sf parallel` commands to work. | +| `enabled` | boolean | `false` | Master toggle. Must be `true` for `/parallel` commands to work. | | `max_workers` | number (1-4) | `2` | Maximum concurrent worker processes. Higher values use more memory and API budget. | | `budget_ceiling` | number | none | Aggregate cost ceiling in USD across all workers. When reached, no new units are dispatched. | | `merge_strategy` | `"per-slice"` or `"per-milestone"` | `"per-milestone"` | When worktree changes merge back to main. Per-milestone waits for the full milestone to complete. | -| `auto_merge` | `"auto"`, `"confirm"`, `"manual"` | `"confirm"` | How merge-back is handled. `confirm` prompts before merging. `manual` requires explicit `/sf parallel merge`. | +| `auto_merge` | `"auto"`, `"confirm"`, `"manual"` | `"confirm"` | How merge-back is handled. `confirm` prompts before merging. `manual` requires explicit `/parallel merge`. | ## Commands | Command | Description | |---------|-------------| -| `/sf parallel start` | Analyze eligibility, confirm, and start workers | -| `/sf parallel status` | Show all workers with state, units completed, and cost | -| `/sf parallel stop` | Stop all workers (sends SIGTERM) | -| `/sf parallel stop M002` | Stop a specific milestone's worker | -| `/sf parallel pause` | Pause all workers (finish current unit, then wait) | -| `/sf parallel pause M002` | Pause a specific worker | -| `/sf parallel resume` | Resume all paused workers | -| `/sf parallel resume M002` | Resume a specific worker | -| `/sf parallel merge` | Merge all completed milestones back to main | -| `/sf parallel merge M002` | Merge a specific milestone back to main | +| `/parallel start` | Analyze eligibility, confirm, and start workers | +| `/parallel status` | Show all workers with state, units completed, and cost | +| `/parallel stop` | Stop all workers (sends SIGTERM) | +| `/parallel stop M002` | Stop a specific milestone's worker | +| `/parallel pause` | Pause all workers (finish current unit, then wait) | +| `/parallel pause M002` | Pause a specific worker | +| `/parallel resume` | Resume all paused workers | +| `/parallel resume M002` | Resume a specific worker | +| `/parallel merge` | Merge all completed milestones back to main | +| `/parallel merge M002` | Merge a specific milestone back to main | ## Signal Lifecycle @@ -201,12 +201,12 @@ When milestones complete, their worktree changes need to merge back to main. ### Conflict Handling 1. `.sf/` state files (STATE.md, metrics.json, etc.) — **auto-resolved** by accepting the milestone branch version -2. Code conflicts — **stop and report**. The merge halts, showing which files conflict. Resolve manually and retry with `/sf parallel merge `. +2. Code conflicts — **stop and report**. The merge halts, showing which files conflict. Resolve manually and retry with `/parallel merge `. ### Example ``` -/sf parallel merge +/parallel merge # Merge Results @@ -214,7 +214,7 @@ When milestones complete, their worktree changes need to merge back to main. - **M003** — CONFLICT (2 file(s)): - `src/types.ts` - `src/middleware.ts` - Resolve conflicts manually and run `/sf parallel merge M003` to retry. + Resolve conflicts manually and run `/parallel merge M003` to retry. ``` ## Budget Management @@ -229,11 +229,11 @@ When `budget_ceiling` is set, the coordinator tracks aggregate cost across all w ### Doctor Integration -`/sf doctor` detects parallel session issues: +`/doctor` detects parallel session issues: - **Stale parallel sessions** — Worker process died without cleanup. Doctor finds `.sf/parallel/*.status.json` files with dead PIDs or expired heartbeats and removes them. -Run `/sf doctor --fix` to clean up automatically. +Run `/doctor --fix` to clean up automatically. ### Stale Detection @@ -288,22 +288,22 @@ Set `parallel.enabled: true` in your preferences file. ### "No milestones are eligible for parallel execution" -All milestones are either complete or blocked by dependencies. Check `/sf queue` to see milestone status and dependency chains. +All milestones are either complete or blocked by dependencies. Check `/queue` to see milestone status and dependency chains. ### Worker crashed — how to recover Workers now persist their state to disk automatically. If a worker process dies, the coordinator detects the dead PID via heartbeat expiry and marks the worker as crashed. On restart, the worker picks up from disk state — crash recovery, worktree re-entry, and completed-unit tracking carry over from the crashed session. -1. Run `/sf doctor --fix` to clean up stale sessions -2. Run `/sf parallel status` to see current state -3. Re-run `/sf parallel start` to spawn new workers for remaining milestones +1. Run `/doctor --fix` to clean up stale sessions +2. Run `/parallel status` to see current state +3. Re-run `/parallel start` to spawn new workers for remaining milestones ### Merge conflicts after parallel completion -1. Run `/sf parallel merge` to see which milestones have conflicts +1. Run `/parallel merge` to see which milestones have conflicts 2. Resolve conflicts in the worktree at `.sf/worktrees//` -3. Retry with `/sf parallel merge ` +3. Retry with `/parallel merge ` ### Workers seem stuck -Check if budget ceiling was reached: `/sf parallel status` shows per-worker costs. Increase `parallel.budget_ceiling` or remove it to continue. +Check if budget ceiling was reached: `/parallel status` shows per-worker costs. Increase `parallel.budget_ceiling` or remove it to continue. diff --git a/docs/user-docs/providers.md b/docs/user-docs/providers.md index e49f3b7e1..7b725b119 100644 --- a/docs/user-docs/providers.md +++ b/docs/user-docs/providers.md @@ -70,7 +70,7 @@ Or run `sf config` and paste your key when prompted. **Runtime boundary:** SF may use Claude Code, Codex, or Gemini CLI core as model/runtime adapters when explicitly configured. These adapters are not project MCP dependencies, and SF does not expose its own workflow as an MCP server. Run -SF directly with `sf` or `/sf autonomous`; reserve MCP configuration for external +SF directly with `sf` or `/autonomous`; reserve MCP configuration for external tools that SF may call. ### OpenAI @@ -596,4 +596,4 @@ If the model doesn't appear, check: - `models.json` is valid JSON (use `cat ~/.sf/agent/models.json | python3 -m json.tool`) - The server is running (for local providers) -For additional help, see [Troubleshooting](./troubleshooting.md) or run `/sf doctor` inside a session. +For additional help, see [Troubleshooting](./troubleshooting.md) or run `/doctor` inside a session. diff --git a/docs/user-docs/remote-questions.md b/docs/user-docs/remote-questions.md index ab4b13db8..56ef6ae31 100644 --- a/docs/user-docs/remote-questions.md +++ b/docs/user-docs/remote-questions.md @@ -7,7 +7,7 @@ Remote questions allow SF to ask for user input via Slack, Discord, or Telegram ### Discord ``` -/sf remote discord +/remote discord ``` The setup wizard: @@ -30,7 +30,7 @@ The setup wizard: ### Slack ``` -/sf remote slack +/remote slack ``` The setup wizard: @@ -48,7 +48,7 @@ The setup wizard: ### Telegram ``` -/sf remote telegram +/remote telegram ``` The setup wizard: @@ -105,11 +105,11 @@ If no response is received within `timeout_minutes`, the prompt times out and SF | Command | Description | |---------|-------------| -| `/sf remote` | Show remote questions menu and current status | -| `/sf remote slack` | Set up Slack integration | -| `/sf remote discord` | Set up Discord integration | -| `/sf remote status` | Show current configuration and last prompt status | -| `/sf remote disconnect` | Remove remote questions configuration | +| `/remote` | Show remote questions menu and current status | +| `/remote slack` | Set up Slack integration | +| `/remote discord` | Set up Discord integration | +| `/remote status` | Show current configuration and last prompt status | +| `/remote disconnect` | Remove remote questions configuration | ## Discord vs Slack Feature Comparison diff --git a/docs/user-docs/skills.md b/docs/user-docs/skills.md index 6118c888a..292ad5e61 100644 --- a/docs/user-docs/skills.md +++ b/docs/user-docs/skills.md @@ -155,13 +155,13 @@ Every autonomous mode unit records which skills were available and actively load ### Skill Health Dashboard -View skill performance with `/sf skill-health`: +View skill performance with `/skill-health`: ``` -/sf skill-health # overview table: name, uses, success%, tokens, trend, last used -/sf skill-health rust-core # detailed view for one skill -/sf skill-health --stale 30 # skills unused for 30+ days -/sf skill-health --declining # skills with falling success rates +/skill-health # overview table: name, uses, success%, tokens, trend, last used +/skill-health rust-core # detailed view for one skill +/skill-health --stale 30 # skills unused for 30+ days +/skill-health --declining # skills with falling success rates ``` The dashboard flags skills that may need attention: diff --git a/docs/user-docs/token-optimization.md b/docs/user-docs/token-optimization.md index 3af5a68ba..6009827bf 100644 --- a/docs/user-docs/token-optimization.md +++ b/docs/user-docs/token-optimization.md @@ -176,12 +176,12 @@ SF tracks the success and failure of each tier assignment over time and adjusts ### User Feedback -Use `/sf rate` to submit feedback on the last completed unit's model tier: +Use `/rate` to submit feedback on the last completed unit's model tier: ``` -/sf rate over # model was overpowered — encourage cheaper next time -/sf rate ok # model was appropriate — no adjustment -/sf rate under # model was too weak — encourage stronger next time +/rate over # model was overpowered — encourage cheaper next time +/rate ok # model was appropriate — no adjustment +/rate under # model was too weak — encourage stronger next time ``` Feedback signals are weighted 2× compared to automatic outcomes. Requires dynamic routing to be active (the last unit must have tier data). diff --git a/docs/user-docs/troubleshooting.md b/docs/user-docs/troubleshooting.md index 34b7721f8..d59984366 100644 --- a/docs/user-docs/troubleshooting.md +++ b/docs/user-docs/troubleshooting.md @@ -1,11 +1,11 @@ # Troubleshooting -## `/sf doctor` +## `/doctor` The built-in diagnostic tool validates `.sf/` integrity: ``` -/sf doctor +/doctor ``` It checks: @@ -25,13 +25,13 @@ It checks: - Stale cache after a crash — the in-memory file listing doesn't reflect new artifacts - The LLM didn't produce the expected artifact file -**Fix:** Run `/sf doctor` to repair state, then resume with `/sf autonomous`. If the issue persists, check that the expected artifact file exists on disk. +**Fix:** Run `/doctor` to repair state, then resume with `/autonomous`. If the issue persists, check that the expected artifact file exists on disk. ### Autonomous mode stops with "Loop detected" **Cause:** A unit failed to produce its expected artifact twice in a row. -**Fix:** Check the task plan for clarity. If the plan is ambiguous, refine it manually, then `/sf autonomous` to resume. +**Fix:** Check the task plan for clarity. If the plan is ambiguous, refine it manually, then `/autonomous` to resume. ### Wrong files in worktree @@ -58,7 +58,7 @@ echo 'export PATH="$(npm prefix -g)/bin:$PATH"' >> ~/.bashrc source ~/.bashrc ``` -**Workaround:** Run `npx singularity-forge` or `$(npm prefix -g)/bin/sf` directly. +**Workaround:** Run `npx singularity-forge` or `$(npm prefix -g)/bin/next` directly. **Common causes:** - **Version manager (nvm, fnm, mise)** — global bin is version-specific; ensure your version manager initializes in your shell config @@ -93,7 +93,7 @@ models: - openrouter/minimax/minimax-m2.5 ``` -**Machine surface:** `sf headless autonomous` auto-restarts the entire process on crash (default 3 attempts with exponential backoff). Combined with provider error auto-resume, this enables true overnight unattended execution. +**Machine surface:** `sf headless autonomous` restarts the process automatically on crash (default 3 attempts with exponential backoff). Combined with provider error recovery, this enables true overnight unattended execution. For common provider setup issues (role errors, streaming errors, model ID mismatches), see the [Provider Setup Guide — Common Pitfalls](./providers.md#common-pitfalls). @@ -101,13 +101,13 @@ For common provider setup issues (role errors, streaming errors, model ID mismat **Symptoms:** Autonomous mode pauses with "Budget ceiling reached." -**Fix:** Increase `budget_ceiling` in preferences, or switch to `budget` token profile to reduce per-unit cost, then resume with `/sf autonomous`. +**Fix:** Increase `budget_ceiling` in preferences, or switch to `budget` token profile to reduce per-unit cost, then resume with `/autonomous`. ### Stale lock file **Symptoms:** Autonomous mode won't start, says another session is running. -**Fix:** SF automatically detects stale locks — if the owning PID is dead, the lock is cleaned up and re-acquired on the next `/sf autonomous`. This includes stranded `.sf.lock/` directories left by `proper-lockfile` after crashes. If automatic recovery fails, delete `.sf/auto.lock` and the `.sf.lock/` directory manually: +**Fix:** SF automatically detects stale locks — if the owning PID is dead, the lock is cleaned up and re-acquired on the next `/autonomous`. This includes stranded `.sf.lock/` directories left by `proper-lockfile` after crashes. If automatic recovery fails, delete `.sf/auto.lock` and the `.sf.lock/` directory manually: ```bash rm -f .sf/auto.lock @@ -122,7 +122,7 @@ rm -rf "$(dirname .sf)/.sf.lock" ### Pre-dispatch says the milestone integration branch no longer exists -**Symptoms:** Autonomous mode or `/sf doctor` reports that a milestone recorded an integration branch that no longer exists in git. +**Symptoms:** Autonomous mode or `/doctor` reports that a milestone recorded an integration branch that no longer exists in git. **What it means:** The milestone's `.sf/milestones//-META.json` still points at the branch that was active when the milestone started, but that branch has since been renamed or deleted. @@ -131,11 +131,11 @@ rm -rf "$(dirname .sf)/.sf.lock" - Safe fallbacks are: - explicit `git.main_branch` when configured and present - the repo's detected default integration branch (for example `main` or `master`) -- In that case `/sf doctor` reports a warning and `/sf doctor fix` rewrites the stale metadata to the effective branch. +- In that case `/doctor` reports a warning and `/doctor fix` rewrites the stale metadata to the effective branch. - SF still blocks when no safe fallback branch can be determined. **Fix:** -- Run `/sf doctor fix` to rewrite the stale milestone metadata automatically when the fallback is obvious. +- Run `/doctor fix` to rewrite the stale milestone metadata automatically when the fallback is obvious. - If SF still blocks, recreate the missing branch or update your git preferences so `git.main_branch` points at a real branch. ### Transient `EBUSY` / `EPERM` / `EACCES` while writing `.sf/` files @@ -149,7 +149,7 @@ rm -rf "$(dirname .sf)/.sf.lock" **Fix:** - Re-run the operation; most transient lock races clear quickly. - If the error persists, close tools that may be holding the file open and then retry. -- If repeated failures continue, run `/sf doctor` to confirm the repo state is still healthy and report the exact path + error code. +- If repeated failures continue, run `/doctor` to confirm the repo state is still healthy and report the exact path + error code. ### Node v24 web boot failure @@ -256,11 +256,11 @@ rm -rf "$(dirname .sf)/.sf.lock" - Set required environment variables in the MCP config's `env` block - If needed, set `cwd` explicitly in the server definition -### Session lock stolen by `/sf` in another terminal +### Session lock stolen by `/next` in another terminal -**Symptoms:** Running `/sf` (assisted mode) in a second terminal causes a running autonomous mode session to lose its lock. +**Symptoms:** Running `/next` (assisted mode) in a second terminal causes a running autonomous mode session to lose its lock. -**Fix:** Fixed in v2.36.0. Bare `/sf` no longer steals the session lock from a running autonomous mode session. Upgrade to the latest version. +**Fix:** Fixed in v2.36.0. Bare `/next` no longer steals the session lock from a running autonomous mode session. Upgrade to the latest version. ### Worktree commits landing on main instead of milestone branch @@ -285,7 +285,7 @@ rm .sf/auto.lock rm .sf/completed-units.json ``` -Then `/sf autonomous` to restart from current disk state. +Then `/autonomous` to restart from current disk state. ### Reset routing history @@ -298,7 +298,7 @@ rm .sf/routing-history.json ### Full state rebuild ``` -/sf doctor +/doctor ``` Doctor derives current state from the DB-backed runtime model when available, regenerates projections such as `STATE.md`, and fixes detected inconsistencies. File-based plan and roadmap parsing is only a recovery path for unmigrated or damaged state. @@ -306,8 +306,8 @@ Doctor derives current state from the DB-backed runtime model when available, re ## Getting Help - **GitHub Issues:** [github.com/singularity-ng/singularity-forge/issues](https://github.com/singularity-ng/singularity-forge/issues) -- **Dashboard:** `Ctrl+Alt+G` or `/sf status` for real-time diagnostics -- **Forensics:** `/sf forensics` for structured post-mortem analysis of autonomous mode failures +- **Dashboard:** `Ctrl+Alt+G` or `/status` for real-time diagnostics +- **Forensics:** `/forensics` for structured post-mortem analysis of autonomous mode failures - **Session logs:** `.sf/activity/` contains JSONL session dumps for crash forensics ## Database Issues @@ -316,7 +316,7 @@ Doctor derives current state from the DB-backed runtime model when available, re **Symptoms:** `sf_decision_save`, `sf_requirement_update`, or `sf_summary_save` fail with this error. -**Cause:** The SQLite database wasn't initialized. This happens in manual `/sf` sessions (non-autonomous mode) on versions before v2.29. +**Cause:** The SQLite database wasn't initialized. This happens in manual `/next` sessions (non-autonomous mode) on versions before v2.29. **Fix:** Updated in v2.29+ to auto-initialize the database on first tool call. Upgrade to the latest version. diff --git a/docs/user-docs/visualizer.md b/docs/user-docs/visualizer.md index 74c8149ab..e855e64ea 100644 --- a/docs/user-docs/visualizer.md +++ b/docs/user-docs/visualizer.md @@ -7,7 +7,7 @@ The workflow visualizer is a full-screen TUI overlay that shows project progress ## Opening the Visualizer ``` -/sf visualize +/visualize ``` Or configure automatic display after milestone completion: @@ -89,7 +89,7 @@ The visualizer refreshes data from disk every 2 seconds, so it stays current if ## HTML Export (v2.26) -For shareable reports outside the terminal, use `/sf export --html`. This generates a self-contained HTML file in `.sf/reports/` with the same data as the TUI visualizer — progress tree, dependency graph (SVG DAG), cost/token bar charts, execution timeline, changelog, and knowledge base. All CSS and JS are inlined — no external dependencies. Printable to PDF from any browser. +For shareable reports outside the terminal, use `/export --html`. This generates a self-contained HTML file in `.sf/reports/` with the same data as the TUI visualizer — progress tree, dependency graph (SVG DAG), cost/token bar charts, execution timeline, changelog, and knowledge base. All CSS and JS are inlined — no external dependencies. Printable to PDF from any browser. An auto-generated `index.html` shows all reports with progression metrics across milestones. diff --git a/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts b/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts index 70698cd75..1e2cefdc7 100644 --- a/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +++ b/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts @@ -101,22 +101,48 @@ describe("Discovery adapter resolution", () => { // ─── AuthStorage hasAuth for discovery ─────────────────────────────────────── +function withoutProviderEnvAuth(fn: () => void): void { + const original = { + OPENAI_API_KEY: process.env.OPENAI_API_KEY, + OLLAMA_API_KEY: process.env.OLLAMA_API_KEY, + ZAI_API_KEY: process.env.ZAI_API_KEY, + }; + delete process.env.OPENAI_API_KEY; + delete process.env.OLLAMA_API_KEY; + delete process.env.ZAI_API_KEY; + try { + fn(); + } finally { + for (const [key, value] of Object.entries(original)) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + } +} + describe("AuthStorage — hasAuth for discovery providers", () => { it("returns false for providers without auth", () => { - const storage = AuthStorage.inMemory({}); - assert.equal(storage.hasAuth("openai"), false); - assert.equal(storage.hasAuth("ollama"), false); - assert.equal(storage.hasAuth("zai"), false); + withoutProviderEnvAuth(() => { + const storage = AuthStorage.inMemory({}); + assert.equal(storage.hasAuth("openai"), false); + assert.equal(storage.hasAuth("ollama"), false); + assert.equal(storage.hasAuth("zai"), false); + }); }); it("returns true for providers with stored keys", () => { - const storage = AuthStorage.inMemory({ - openai: { type: "api_key" as const, key: "sk-test" }, - zai: { type: "api_key" as const, key: "zai-test" }, + withoutProviderEnvAuth(() => { + const storage = AuthStorage.inMemory({ + openai: { type: "api_key" as const, key: "sk-test" }, + zai: { type: "api_key" as const, key: "zai-test" }, + }); + assert.equal(storage.hasAuth("openai"), true); + assert.equal(storage.hasAuth("ollama"), false); + assert.equal(storage.hasAuth("zai"), true); }); - assert.equal(storage.hasAuth("openai"), true); - assert.equal(storage.hasAuth("ollama"), false); - assert.equal(storage.hasAuth("zai"), true); }); }); diff --git a/scripts/check-sf-extension-inventory.mjs b/scripts/check-sf-extension-inventory.mjs index 2d3266541..ec98a2cfb 100644 --- a/scripts/check-sf-extension-inventory.mjs +++ b/scripts/check-sf-extension-inventory.mjs @@ -9,7 +9,34 @@ const manifestPath = join(sfRoot, "extension-manifest.json"); const RESOURCE_SOURCE_RE = /\.(?:js|mjs|cjs|json|md|yaml|yml|d\.ts)$/; const DYNAMIC_TOOL_NAMES = ["bash", "edit", "read", "write"]; -const DIRECT_COMMAND_NAMES = ["exit", "kill", "sf", "worktree", "wt"]; +const BASE_DIRECT_COMMAND_NAMES = ["exit", "kill", "wt"]; +const BASE_RUNTIME_COMMAND_NAMES = new Set([ + "settings", + "model", + "scoped-models", + "export", + "share", + "copy", + "name", + "session", + "changelog", + "hotkeys", + "fork", + "tree", + "provider", + "login", + "logout", + "new", + "compact", + "resume", + "reload", + "thinking", + "edit-mode", + "terminal", + "stop", + "exit", + "quit", +]); const HIDDEN_OR_ALIAS_SUBCOMMANDS = new Set([ "?", "auto", @@ -213,6 +240,14 @@ function main() { const manifest = parseManifest(); const registeredTools = parseRegisteredTools(); + const catalogCommands = parseTopLevelCatalogCommands(); + const directCommandNames = uniqueSorted( + BASE_DIRECT_COMMAND_NAMES.concat( + catalogCommands.filter( + (command) => !BASE_RUNTIME_COMMAND_NAMES.has(command), + ), + ), + ); const missingManifestTools = registeredTools.filter( (tool) => !manifest.tools.includes(tool), ); @@ -236,11 +271,11 @@ function main() { ); } - const missingManifestCommands = DIRECT_COMMAND_NAMES.filter( + const missingManifestCommands = directCommandNames.filter( (command) => !manifest.commands.includes(command), ); const staleManifestCommands = manifest.commands.filter( - (command) => !DIRECT_COMMAND_NAMES.includes(command), + (command) => !directCommandNames.includes(command), ); if (missingManifestCommands.length > 0) { failures.push( @@ -259,7 +294,6 @@ function main() { ); } - const catalogCommands = parseTopLevelCatalogCommands(); const handledCommands = parseHandledTopLevelCommands().filter( (command) => !HIDDEN_OR_ALIAS_SUBCOMMANDS.has(command), ); @@ -272,7 +306,7 @@ function main() { if (missingCatalogCommands.length > 0) { failures.push( failSection( - "Handled /sf commands missing from TOP_LEVEL_SUBCOMMANDS", + "Handled SF commands missing from TOP_LEVEL_SUBCOMMANDS", missingCatalogCommands, ), ); @@ -280,7 +314,7 @@ function main() { if (unroutedCatalogCommands.length > 0) { failures.push( failSection( - "Catalog /sf commands with no routed handler", + "Catalog SF commands with no routed handler", unroutedCatalogCommands, ), ); @@ -292,7 +326,7 @@ function main() { } console.log( - `SF extension inventory OK: ${registeredTools.length} tools, ${DIRECT_COMMAND_NAMES.length} direct commands, ${catalogCommands.length} /sf subcommands.`, + `SF extension inventory OK: ${registeredTools.length} tools, ${directCommandNames.length} direct commands, ${catalogCommands.length} catalog commands.`, ); } diff --git a/src/headless-events.ts b/src/headless-events.ts index b36b03afd..b2697fbdd 100644 --- a/src/headless-events.ts +++ b/src/headless-events.ts @@ -201,12 +201,12 @@ export function isPauseNotification(event: Record): boolean { ); } -export function isAutoResumeScheduledNotification( +export function isScheduledResumeNotification( event: Record, ): boolean { if (event.type !== "extension_ui_request" || event.method !== "notify") return false; - return /auto-resuming in \d+s/i.test(String(event.message ?? "")); + return /resuming automatically in \d+s/i.test(String(event.message ?? "")); } export function isBlockedNotification(event: Record): boolean { diff --git a/src/headless.ts b/src/headless.ts index 34bc5422f..b393a685b 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -42,13 +42,13 @@ import { EXIT_SUCCESS, FIRE_AND_FORGET_METHODS, IDLE_TIMEOUT_MS, - isAutoResumeScheduledNotification, isBlockedNotification, isInteractiveHeadlessTool, isMilestoneReadyNotification, isMilestoneReadyText, isPauseNotification, isQuickCommand, + isScheduledResumeNotification, isTerminalNotification, MULTI_TURN_DEADLOCK_BACKSTOP_MS, mapStatusToExitCode, @@ -193,7 +193,7 @@ export interface HeadlessOptions { contextText?: string; // inline text chainAutonomous?: boolean; // chain into autonomous mode after milestone creation verbose?: boolean; // show tool calls in output - maxRestarts?: number; // auto-restart on crash (default 3, 0 to disable) + maxRestarts?: number; // automatic restart on crash (default 3, 0 to disable) supervised?: boolean; // supervised mode: forward interactive requests to orchestrator responseTimeout?: number; // timeout for orchestrator response (default 30000ms) answers?: string; // path to answers JSON file @@ -828,7 +828,7 @@ async function runHeadlessOnce( process.stderr.write(`[headless] doctor failed: ${msg}\n`); exitCode = 1; } - // Bypass the auto-restart loop in runHeadless — doctor is a one-shot + // Bypass the automatic restart loop in runHeadless — doctor is a one-shot // diagnostic; exit 1 means "issues detected", not "crashed". process.exit(exitCode); } @@ -1628,7 +1628,7 @@ async function runHeadlessOnce( const waitForProviderAutoResume = providerAutoResumePending && isPauseNotification(eventObj); - if (isAutoResumeScheduledNotification(eventObj)) { + if (isScheduledResumeNotification(eventObj)) { providerAutoResumePending = true; } diff --git a/src/resources/extensions/sf/auto-direct-dispatch.js b/src/resources/extensions/sf/auto-direct-dispatch.js index 2051a862d..26cc4612d 100644 --- a/src/resources/extensions/sf/auto-direct-dispatch.js +++ b/src/resources/extensions/sf/auto-direct-dispatch.js @@ -1,5 +1,5 @@ /** - * Direct phase dispatch — handles manual /sf dispatch commands. + * Direct phase dispatch — handles manual /dispatch commands. * Resolves phase name → unit type + prompt, creates a session, and sends the message. */ import { pauseAuto } from "./auto.js"; @@ -62,7 +62,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) { ?.require_slice_discussion; if (requireDiscussion && !sliceContextFile) { ctx.ui.notify( - `Slice ${sid} requires discussion before planning. Run /sf discuss to discuss this slice, then /sf autonomous to resume.`, + `Slice ${sid} requires discussion before planning. Run /discuss to discuss this slice, then /autonomous to resume.`, "info", ); await pauseAuto(ctx, pi); diff --git a/src/resources/extensions/sf/auto-dispatch.js b/src/resources/extensions/sf/auto-dispatch.js index cb32db55d..7cce22e2b 100644 --- a/src/resources/extensions/sf/auto-dispatch.js +++ b/src/resources/extensions/sf/auto-dispatch.js @@ -115,7 +115,7 @@ const PARALLEL_RESEARCH_BLOCKING_PHASES = new Set([ function missingSliceStop(mid, phase) { return { action: "stop", - reason: `${mid}: phase "${phase}" has no active slice — run /sf doctor.`, + reason: `${mid}: phase "${phase}" has no active slice — run /doctor.`, level: "error", }; } @@ -149,7 +149,7 @@ async function readMilestoneValidationForDispatch(basePath, mid) { function canonicalPlanStop(mid, plan) { return { action: "stop", - reason: `${mid}: canonical milestone plan unavailable (${plan.source}): ${plan.reason} Run /sf doctor or regenerate structured roadmap state before dispatching autonomous mode work.`, + reason: `${mid}: canonical milestone plan unavailable (${plan.source}): ${plan.reason} Run /doctor or regenerate structured roadmap state before dispatching autonomous mode work.`, level: "error", }; } @@ -563,7 +563,7 @@ export const DISPATCH_RULES = [ // ADR-011 Phase 2 (SF ADR): mid-execution escalation handling. // Autonomous mode is autonomous, so by default we accept the agent's // recommendation and continue — the user can review/override later via - // `/sf escalate list --all`. Set `phases.escalation_auto_accept: false` + // `/escalate list --all`. Set `phases.escalation_auto_accept: false` // to keep SF's pause-and-ask behavior. // Must evaluate FIRST — phase-agnostic rules below (rewrite-docs gate, // UAT checks, reassess) cannot run while a task is paused. @@ -583,7 +583,7 @@ export const DISPATCH_RULES = [ state.activeSlice.id, state.activeTask.id, "accept", - "autonomous mode: accepted agent recommendation; user can override via /sf escalate", + "autonomous mode: accepted agent recommendation; user can override via /escalate", "autonomous mode", ); if (result.status === "resolved") { @@ -601,7 +601,7 @@ export const DISPATCH_RULES = [ action: "stop", reason: state.nextAction || - `${mid}: task escalation awaits user resolution. Run /sf escalate list to see pending items.`, + `${mid}: task escalation awaits user resolution. Run /escalate list to see pending items.`, level: "info", }; }, @@ -783,7 +783,7 @@ export const DISPATCH_RULES = [ ) { return { action: "stop", - reason: `UAT verdict for ${check.sliceId} is "${check.verdict}" — blocking progression until resolved.\nReview the UAT result and update the verdict to PASS, or re-run /sf auto after fixing.`, + reason: `UAT verdict for ${check.sliceId} is "${check.verdict}" — blocking progression until resolved.\nReview the UAT result and update the verdict to PASS, or re-run /auto after fixing.`, level: "warning", }; } @@ -1602,7 +1602,7 @@ export const DISPATCH_RULES = [ if (missingSlices.length > 0) { return { action: "stop", - reason: `Cannot complete milestone ${mid}: slices ${missingSlices.join(", ")} are missing SUMMARY files. Run /sf doctor to diagnose.`, + reason: `Cannot complete milestone ${mid}: slices ${missingSlices.join(", ")} are missing SUMMARY files. Run /doctor to diagnose.`, level: "error", }; } @@ -1844,7 +1844,7 @@ export async function resolveDispatch(ctx) { // (e.g. after reassessment modifies the roadmap and state needs re-derivation). const unhandled = { action: "stop", - reason: `Unhandled phase "${ctx.state.phase}" — run /sf doctor to diagnose.`, + reason: `Unhandled phase "${ctx.state.phase}" — run /doctor to diagnose.`, level: "warning", matchedRule: "", }; diff --git a/src/resources/extensions/sf/auto-model-selection.js b/src/resources/extensions/sf/auto-model-selection.js index 3ca6a3dd4..67c687dae 100644 --- a/src/resources/extensions/sf/auto-model-selection.js +++ b/src/resources/extensions/sf/auto-model-selection.js @@ -69,7 +69,7 @@ export class ModelPolicyDispatchBlockedError extends Error { // LIFECYCLE: the baseline is tied to a single auto session, NOT to the // lifetime of the `pi` instance (which can outlive many auto runs and have // the user mutate tools between them). `clearToolBaseline` MUST be called -// at auto start AND auto stop so that a second `/sf autonomous` run on the same +// at auto start AND auto stop so that a second `/autonomous` run on the same // `pi` does not silently restore a stale snapshot from the prior run and // undo any tool changes the user made between sessions. const TOOL_BASELINE = new WeakMap(); @@ -267,7 +267,7 @@ export async function selectAndApplyModel( /** When false (interactive/guided-flow), skip dynamic routing and use the session model. * Dynamic routing only applies in autonomous mode where cost optimization is expected. (#3962) */ isAutoMode = true, - /** Explicit /sf model pin captured at bootstrap for long-running auto loops. */ + /** Explicit /model pin captured at bootstrap for long-running auto loops. */ sessionModelOverride, /** Thinking level captured at autonomous mode start and re-applied after model swaps. */ autoModeStartThinkingLevel, @@ -615,7 +615,7 @@ export async function selectAndApplyModel( } // Skip models the provider has previously rejected for this account // (issue #4513). The block is persisted in .sf/runtime/blocked-models.json - // so it survives /sf autonomous restarts — without this, the same dead model + // so it survives /autonomous restarts — without this, the same dead model // gets reselected after every restart. if (isModelBlocked(basePath, model.provider, model.id)) { ctx.ui.notify( diff --git a/src/resources/extensions/sf/auto-post-unit.js b/src/resources/extensions/sf/auto-post-unit.js index abc62d6a9..e7d0fa6ad 100644 --- a/src/resources/extensions/sf/auto-post-unit.js +++ b/src/resources/extensions/sf/auto-post-unit.js @@ -268,15 +268,15 @@ export function detectRogueFileWrites(unitType, unitId, basePath) { return rogues; } export const STEP_COMPLETE_FALLBACK_MESSAGE = - "Step complete. Run /clear, then /sf to continue (or /sf autonomous to run continuously)."; + "Step complete. Run /clear, then /to continue (or /autonomous to run continuously)."; export function buildStepCompleteMessage(nextState) { if (nextState.phase === "complete") { - return "Step complete — milestone finished. Run /sf status to review, or start the next milestone."; + return "Step complete — milestone finished. Run /status to review, or start the next milestone."; } const next = describeNextUnit(nextState); return ( `Step complete. Next: ${next.label}\n` + - `Run /clear, then /sf to continue (or /sf autonomous to run continuously).` + `Run /clear, then /to continue (or /autonomous to run continuously).` ); } export const USER_DRIVEN_DEEP_UNITS = new Set([ @@ -826,7 +826,7 @@ export async function postUnitPreVerification(pctx, opts) { if (err instanceof MergeConflictError) { ctx.ui.notify( `slice-cadence merge conflict in ${sid}: ${err.conflictedFiles.join(", ")}. ` + - `Resolve manually on main and run \`/sf autonomous\` to resume.`, + `Resolve manually on main and run \`/autonomous\` to resume.`, "error", ); // Stop auto AND signal the outer postUnit flow to exit early. @@ -1265,7 +1265,7 @@ export async function postUnitPreVerification(pctx, opts) { s.verificationRetryCount.delete(retryKey); s.pendingVerificationRetry = null; ctx.ui.notify( - `Milestone ${s.currentUnit.id} verification failed after ${MAX_VERIFICATION_RETRIES} retries — worktree branch preserved. Re-run /sf autonomous once blockers are resolved.`, + `Milestone ${s.currentUnit.id} verification failed after ${MAX_VERIFICATION_RETRIES} retries — worktree branch preserved. Re-run /autonomous once blockers are resolved.`, "error", ); await pauseAuto(ctx, pi); @@ -1914,8 +1914,8 @@ export async function postUnitPostVerification(pctx) { } } // Assisted mode → show wizard instead of dispatch. - // Without this notify(), /sf in assisted mode finishes a unit and silently - // exits the loop, leaving the user with no hint to /clear and /sf again. + // Without this notify(), /in assisted mode finishes a unit and silently + // exits the loop, leaving the user with no hint to /clear and /again. if (s.stepMode) { try { const nextState = await deriveState(s.basePath); diff --git a/src/resources/extensions/sf/auto-prompts.js b/src/resources/extensions/sf/auto-prompts.js index 5575c6345..c6e346c65 100644 --- a/src/resources/extensions/sf/auto-prompts.js +++ b/src/resources/extensions/sf/auto-prompts.js @@ -1774,7 +1774,7 @@ export async function buildExecuteTaskPrompt( "}", "```", "", - "Provide 2–4 options with concrete tradeoffs. The recommendation must reference one of the option ids. Autonomous mode accepts your recommendation, persists the choice + rationale as a memory, and carries it forward as a hard constraint for downstream tasks. The operator can review the audit trail later via `/sf escalate list --all`; the executed work itself can't be retroactively undone, so document your reasoning thoroughly. Set `continueWithDefault: false` only when the choice is severe enough that the loop should pause for human review even in autonomous mode (rare).", + "Provide 2–4 options with concrete tradeoffs. The recommendation must reference one of the option ids. Autonomous mode accepts your recommendation, persists the choice + rationale as a memory, and carries it forward as a hard constraint for downstream tasks. The operator can review the audit trail later via `/escalate list --all`; the executed work itself can't be retroactively undone, so document your reasoning thoroughly. Set `continueWithDefault: false` only when the choice is severe enough that the loop should pause for human review even in autonomous mode (rare).", ].join("\n") : ""; // Apply knowledge injection for this task context diff --git a/src/resources/extensions/sf/auto-start.js b/src/resources/extensions/sf/auto-start.js index e0a342381..ad69d9a18 100644 --- a/src/resources/extensions/sf/auto-start.js +++ b/src/resources/extensions/sf/auto-start.js @@ -225,7 +225,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) { warnings.push( `Branch ${branch} has ${commitsAhead} commit(s) ahead of ${mainBranch} for in-progress milestone ${milestoneId}.` + wtSuffix + - ` Run \`/sf autonomous\` to resume, or merge manually if abandoning.`, + ` Run \`/autonomous\` to resume, or merge manually if abandoning.`, ); // #4764 telemetry try { @@ -302,7 +302,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) { // Branch is NOT merged — preserve for safety, warn the user warnings.push( `Branch ${branch} exists for completed milestone ${milestoneId} but is NOT merged into ${mainBranch}. ` + - `This may contain unmerged work. Merge manually or run \`/sf health --fix\` to resolve.`, + `This may contain unmerged work. Merge manually or run \`/doctor fix\` to resolve.`, ); // #4764 telemetry try { @@ -354,12 +354,12 @@ export async function bootstrapAutoSession( // phase-specific planning model for a discuss turn (#2829). // // Precedence: - // 1) Explicit session override via /sf model (this session) + // 1) Explicit session override via /model (this session) // 2) SF model preferences from PREFERENCES.md (validated against live auth) // 3) Current session model from settings/session restore (if provider ready) // // This preserves #3517 defaults while honoring explicit runtime model - // selection for subsequent /sf runs in the same session. + // selection for subsequent /runs in the same session. // // Exception (#4122): when the session provider is a custom provider declared // in ~/.sf/agent/models.json (Ollama, vLLM, OpenAI-compatible proxy, etc.), @@ -618,7 +618,7 @@ export async function bootstrapAutoSession( hasSurvivorBranch = false; } else { ctx.ui.notify( - "Discussion completed but milestone draft was not promoted. Run /sf to try again.", + "Discussion completed but milestone draft was not promoted. Run /next to try again.", "warning", ); return releaseLockAndReturn(); @@ -656,7 +656,7 @@ export async function bootstrapAutoSession( s.consecutiveCompleteBootstraps = 0; ctx.ui.notify( "All milestones are complete and the discussion didn't produce a new one. " + - "Run /sf to start a new milestone manually.", + "Run /next to start a new milestone manually.", "warning", ); return releaseLockAndReturn(); @@ -849,14 +849,14 @@ export async function bootstrapAutoSession( state = postState; } else { ctx.ui.notify( - "Discussion completed but milestone context is still missing. Run /sf to try again.", + "Discussion completed but milestone context is still missing. Run /next to try again.", "warning", ); return releaseLockAndReturn(); } } else { ctx.ui.notify( - "Discussion completed but milestone context is still missing. Run /sf to try again.", + "Discussion completed but milestone context is still missing. Run /next to try again.", "warning", ); return releaseLockAndReturn(); @@ -876,7 +876,7 @@ export async function bootstrapAutoSession( state = postState; } else { ctx.ui.notify( - "Discussion completed but milestone draft was not promoted. Run /sf to try again.", + "Discussion completed but milestone draft was not promoted. Run /next to try again.", "warning", ); return releaseLockAndReturn(); diff --git a/src/resources/extensions/sf/auto-timeout-recovery.js b/src/resources/extensions/sf/auto-timeout-recovery.js index 398432ad2..eed57b928 100644 --- a/src/resources/extensions/sf/auto-timeout-recovery.js +++ b/src/resources/extensions/sf/auto-timeout-recovery.js @@ -297,7 +297,7 @@ export async function recoverTimedOutUnit( lastRecoveryReason: reason, }); ctx.ui.notify( - `Milestone ${unitId} ${reason}-recovery exhausted ${maxRecoveryAttempts} attempt(s): ${diagnostic}. Worktree branch preserved. Re-run /sf autonomous once blockers are resolved.`, + `Milestone ${unitId} ${reason}-recovery exhausted ${maxRecoveryAttempts} attempt(s): ${diagnostic}. Worktree branch preserved. Re-run /autonomous once blockers are resolved.`, "error", ); return "paused"; diff --git a/src/resources/extensions/sf/auto-tool-tracking.js b/src/resources/extensions/sf/auto-tool-tracking.js index 43eeef811..c58a6bddb 100644 --- a/src/resources/extensions/sf/auto-tool-tracking.js +++ b/src/resources/extensions/sf/auto-tool-tracking.js @@ -112,7 +112,7 @@ export function getToolCallCountSnapshot() { const TOOL_INVOCATION_ERROR_RE = /Validation failed for tool|Expected ',' or '\}'(?: after property value)?(?: in JSON)?|Unexpected end of JSON|Unexpected token.*in JSON/i; const DETERMINISTIC_POLICY_ERROR_RE = - /(?:^|\b)(?:HARD BLOCK:|Blocked: \/sf queue is a planning tool|Direct writes to \.sf\/STATE\.md and \.sf\/sf\.db are blocked|This is a mechanical gate)/i; + /(?:^|\b)(?:HARD BLOCK:|Blocked: \/queue is a planning tool|Direct writes to \.sf\/STATE\.md and \.sf\/next\.db are blocked|This is a mechanical gate)/i; /** * Known deterministic policy error substrings. Each entry is a stable string * that will appear in the tool error text content when the corresponding diff --git a/src/resources/extensions/sf/auto.js b/src/resources/extensions/sf/auto.js index 2d6689d36..0c58af409 100644 --- a/src/resources/extensions/sf/auto.js +++ b/src/resources/extensions/sf/auto.js @@ -589,8 +589,8 @@ export function stopAutoRemote(projectRoot) { /** * Check if a remote autonomous mode session is running (from a different process). * Reads the crash lock, checks PID liveness, and returns session details. - * Used by the guard in commands.ts to prevent bare /sf, /sf next, and - * /sf autonomous from stealing the session lock. + * Used by the guard in commands.ts to prevent bare /next, /next, and + * /autonomous from stealing the session lock. */ export function checkRemoteAutoSession(projectRoot) { const lock = readCrashLock(projectRoot); @@ -705,7 +705,7 @@ function cleanupAfterLoopExit(ctx) { clearUnitTimeout(); restoreProjectRootEnv(); restoreMilestoneLockEnv(); - // Clear crash lock and release session lock so the next `/sf next` does + // Clear crash lock and release session lock so the next `/next` does // not see a stale lock with the current PID and treat it as a "remote" // session (which would cause it to SIGTERM itself). (#2730) try { @@ -1109,7 +1109,7 @@ export async function stopAuto(ctx, pi, reason) { `auto-exit telemetry failed: ${err instanceof Error ? err.message : String(err)}`, ); } - // Drop the active-tool baseline so a subsequent /sf autonomous run on the + // Drop the active-tool baseline so a subsequent /autonomous run on the // same `pi` instance recaptures from the live tool set rather than // restoring this session's snapshot and silently undoing any tool // changes the user made between sessions (#4959 / CodeRabbit). @@ -1120,7 +1120,7 @@ export async function stopAuto(ctx, pi, reason) { } /** * Pause autonomous mode without destroying state. Context is preserved. - * The user can interact with the agent, then `/sf autonomous` resumes + * The user can interact with the agent, then `/autonomous` resumes * from disk state. Called when the user presses Escape during autonomous mode. */ export async function pauseAuto(ctx, _pi, _errorContext) { @@ -1220,7 +1220,7 @@ export async function pauseAuto(ctx, _pi, _errorContext) { ctx?.ui?.setWidget?.("sf-progress", undefined); ctx?.ui.setFooter(undefined); if (ctx) initHealthWidget(ctx); - const resumeCmd = s.stepMode ? "/sf next" : "/sf autonomous"; + const resumeCmd = s.stepMode ? "/next" : "/autonomous"; ctx?.ui.notify( `${s.stepMode ? "Step" : "Autonomous"} mode paused (Escape). Type to interact, or ${resumeCmd} to resume.`, "info", diff --git a/src/resources/extensions/sf/auto/loop.js b/src/resources/extensions/sf/auto/loop.js index 5d9580429..b2cd7a328 100644 --- a/src/resources/extensions/sf/auto/loop.js +++ b/src/resources/extensions/sf/auto/loop.js @@ -433,7 +433,7 @@ export async function autoLoop(ctx, pi, s, deps) { pi, `Memory pressure: heap at ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%). ` + `Stopping gracefully to prevent OOM kill after ${iteration} iterations. ` + - `Resume with /sf autonomous to continue from where you left off.`, + `Resume with /autonomous to continue from where you left off.`, ); finishTurn("stopped", "timeout", "memory-pressure"); break; diff --git a/src/resources/extensions/sf/auto/phases.js b/src/resources/extensions/sf/auto/phases.js index 90f2be552..a260ac81f 100644 --- a/src/resources/extensions/sf/auto/phases.js +++ b/src/resources/extensions/sf/auto/phases.js @@ -118,7 +118,7 @@ import { MAX_RECOVERY_CHARS, } from "./types.js"; -// ─── Session timeout auto-resume state ──────────────────────────────────────── +// ─── Session timeout scheduled resume state ──────────────────────────────────────── let consecutiveSessionTimeouts = 0; const MAX_SESSION_TIMEOUT_AUTO_RESUMES = 3; function resetConsecutiveSessionTimeouts() { @@ -174,7 +174,7 @@ export function _resolveReportBasePath(s) { * milestone at different transition points). * * The audit is fired with a "no-gaps" placeholder verdict. Re-run - * `/sf product-audit` manually for full LLM-powered gap analysis. + * `/product-audit` manually for full LLM-powered gap analysis. */ async function maybeFireProductAudit(s, ctx) { const mid = s.currentMilestoneId; @@ -186,7 +186,7 @@ async function maybeFireProductAudit(s, ctx) { milestoneId: mid, verdict: "no-gaps", summary: - "Auto-fired placeholder audit at milestone merge. Re-run `/sf product-audit` for full LLM-powered gap analysis.", + "Auto-fired placeholder audit at milestone merge. Re-run `/product-audit` for full LLM-powered gap analysis.", gaps: [], }; const result = await handleProductAudit(params, s.basePath); @@ -478,7 +478,7 @@ export async function runPreDispatch(ic, loopState) { }); ctx.ui.notify( healthGate.reason || - "Pre-dispatch health check failed — run /sf doctor for details.", + "Pre-dispatch health check failed — run /doctor for details.", "error", ); await deps.pauseAuto(ctx, pi); @@ -565,7 +565,7 @@ export async function runPreDispatch(ic, loopState) { milestoneId: state.activeMilestone?.id ?? undefined, }); ctx.ui.notify( - `Plan gate failed-closed: ${reason}\n\nIf this keeps happening, try: /sf doctor heal`, + `Plan gate failed-closed: ${reason}\n\nIf this keeps happening, try: /doctor heal`, "error", ); await deps.pauseAuto(ctx, pi); @@ -680,7 +680,7 @@ export async function runPreDispatch(ic, loopState) { ); const vizPrefs = prefs; if (vizPrefs?.auto_visualize) { - ctx.ui.notify("Run /sf visualize to see progress overview.", "info"); + ctx.ui.notify("Run /visualize to see progress overview.", "info"); } if (vizPrefs?.auto_report !== false) { try { @@ -705,7 +705,7 @@ export async function runPreDispatch(ic, loopState) { if (mergeErr instanceof MergeConflictError) { // Real code conflicts — stop the loop instead of retrying forever (#2330) ctx.ui.notify( - `Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /sf autonomous to resume.`, + `Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /autonomous to resume.`, "error", ); await deps.stopAuto( @@ -721,7 +721,7 @@ export async function runPreDispatch(ic, loopState) { error: String(mergeErr), }); ctx.ui.notify( - `Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /sf autonomous to resume.`, + `Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /autonomous to resume.`, "error", ); await deps.stopAuto( @@ -816,7 +816,7 @@ export async function runPreDispatch(ic, loopState) { } catch (mergeErr) { if (mergeErr instanceof MergeConflictError) { ctx.ui.notify( - `Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /sf autonomous to resume.`, + `Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /autonomous to resume.`, "error", ); await deps.stopAuto( @@ -831,7 +831,7 @@ export async function runPreDispatch(ic, loopState) { error: String(mergeErr), }); ctx.ui.notify( - `Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /sf autonomous to resume.`, + `Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /autonomous to resume.`, "error", ); await deps.stopAuto( @@ -866,12 +866,12 @@ export async function runPreDispatch(ic, loopState) { ); } else if (state.phase === "blocked") { const blockerMsg = `Blocked: ${state.blockers.join(", ")}`; - // Pause instead of hard-stop so the session is resumable with `/sf autonomous`. + // Pause instead of hard-stop so the session is resumable with `/autonomous`. // Hard-stop here was causing premature termination when slice dependencies // were temporarily unresolvable (e.g. after reassessment added new slices). await deps.pauseAuto(ctx, pi); ctx.ui.notify( - `${blockerMsg}. Fix and run /sf autonomous to resume.`, + `${blockerMsg}. Fix and run /autonomous to resume.`, "warning", ); deps.sendDesktopNotification( @@ -952,7 +952,7 @@ export async function runPreDispatch(ic, loopState) { } catch (mergeErr) { if (mergeErr instanceof MergeConflictError) { ctx.ui.notify( - `Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /sf autonomous to resume.`, + `Merge conflict: ${mergeErr.conflictedFiles.join(", ")}. Resolve conflicts manually and run /autonomous to resume.`, "error", ); await deps.stopAuto( @@ -967,7 +967,7 @@ export async function runPreDispatch(ic, loopState) { error: String(mergeErr), }); ctx.ui.notify( - `Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /sf autonomous to resume.`, + `Merge failed: ${mergeErr instanceof Error ? mergeErr.message : String(mergeErr)}. Resolve and run /autonomous to resume.`, "error", ); await deps.stopAuto( @@ -1013,7 +1013,7 @@ export async function runPreDispatch(ic, loopState) { } await deps.pauseAuto(ctx, pi); ctx.ui.notify( - `${blockerMsg}. Fix and run /sf autonomous to resume.`, + `${blockerMsg}. Fix and run /autonomous to resume.`, "warning", ); deps.sendDesktopNotification( @@ -1067,7 +1067,7 @@ export async function runDispatch(ic, preData, loopState) { }); // Warning-level stops are recoverable human checkpoints (e.g. UAT verdict // gate) — pause instead of hard-stopping so the session is resumable with - // `/sf autonomous`. Error/info-level stops remain hard stops for infrastructure + // `/autonomous`. Error/info-level stops remain hard stops for infrastructure // failures and terminal conditions respectively. // See: https://github.com/singularity-forge/sf-run/issues/2474 if (dispatchResult.level === "warning") { @@ -1533,7 +1533,7 @@ export async function runGuards(ic, mid, unitType, unitId, sliceId) { } if (budgetEnforcementAction === "pause") { ctx.ui.notify( - `${msg} Pausing autonomous mode — /sf autonomous to override and continue.`, + `${msg} Pausing autonomous mode — /autonomous to override and continue.`, "warning", ); deps.sendDesktopNotification( @@ -1647,7 +1647,7 @@ export async function runGuards(ic, mid, unitType, unitId, sliceId) { ) { const msg = `Context window at ${contextUsage.percent}% (threshold: ${contextThreshold}%). Pausing to prevent truncated output.`; ctx.ui.notify( - `${msg} Run /sf autonomous to continue (will start fresh session).`, + `${msg} Run /autonomous to continue (will start fresh session).`, "warning", ); deps.sendDesktopNotification( @@ -2392,7 +2392,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) { return { action: "break", reason: "provider-pause" }; } // Timeout category covers two distinct scenarios: - // 1. Session creation timeout (120s) — transient, auto-resume with backoff + // 1. Session creation timeout (120s) — transient, scheduled resume with backoff // 2. Unit hard timeout (30min+) — stuck agent, pause for manual review // Structural errors (TypeError, is not a function) are NOT transient // and must hard-stop to avoid infinite retry loops. @@ -2462,7 +2462,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) { ); return { action: "break", reason: "session-timeout" }; } - // Unit hard timeout (30min+): pause without auto-resume — stuck agent + // Unit hard timeout (30min+): pause without scheduled resume — stuck agent ctx.ui.notify( `Unit timed out for ${unitType} ${unitId} (supervision may have failed). Pausing autonomous mode.`, "warning", diff --git a/src/resources/extensions/sf/auto/session.js b/src/resources/extensions/sf/auto/session.js index c124c0827..c56600920 100644 --- a/src/resources/extensions/sf/auto/session.js +++ b/src/resources/extensions/sf/auto/session.js @@ -41,7 +41,7 @@ export class AutoSession { stepMode = false; /** * When false, the agent is forbidden from calling ask_user_questions. - * Assisted mode sets this true; `/sf autonomous` sets it false. + * Assisted mode sets this true; `/autonomous` sets it false. */ canAskUser = true; verbose = false; @@ -78,7 +78,7 @@ export class AutoSession { autoModeStartModel = null; autoModeStartThinkingLevel = null; originalThinkingLevel = null; - /** Explicit /sf model pin captured at bootstrap (session-scoped policy override). */ + /** Explicit /model pin captured at bootstrap (session-scoped policy override). */ manualSessionModelOverride = null; currentUnitModel = null; /** Fully-qualified model ID (provider/id) set after selectAndApplyModel + hook overrides (#2899). */ diff --git a/src/resources/extensions/sf/autonomous-command-args.js b/src/resources/extensions/sf/autonomous-command-args.js index eb8b11ac1..ffb317feb 100644 --- a/src/resources/extensions/sf/autonomous-command-args.js +++ b/src/resources/extensions/sf/autonomous-command-args.js @@ -1,5 +1,5 @@ /** - * autonomous-command-args.js — validates `/sf autonomous` command arguments. + * autonomous-command-args.js — validates `/autonomous` command arguments. * * Purpose: keep autonomous run control strict and explainable by accepting only * the small documented argument set; invented knobs fail as unsupported input. @@ -18,7 +18,7 @@ const MILESTONE_TARGET_RE = /^M\d+(?:-[a-z0-9]{6})?$/i; * Purpose: reject stale or invented knobs before they can be confused with run * control, permission profiles, or output formats. * - * Consumer: headless machine-surface validation and `/sf autonomous` routing. + * Consumer: headless machine-surface validation and `/autonomous` routing. */ export function findUnsupportedAutonomousArgs(args) { const unsupported = []; @@ -49,5 +49,5 @@ export function findUnsupportedAutonomousArgs(args) { * Consumer: headless.ts and the autonomous command handler. */ export function formatUnsupportedAutonomousArgs(args) { - return `Unsupported /sf autonomous argument(s): ${args.join(", ")}. Supported arguments: --verbose, --debug, --yolo , and optional M### milestone target.`; + return `Unsupported /autonomous argument(s): ${args.join(", ")}. Supported arguments: --verbose, --debug, --yolo , and optional M### milestone target.`; } diff --git a/src/resources/extensions/sf/autonomous-solver-eval.js b/src/resources/extensions/sf/autonomous-solver-eval.js index a5f9456dd..2166a0f33 100644 --- a/src/resources/extensions/sf/autonomous-solver-eval.js +++ b/src/resources/extensions/sf/autonomous-solver-eval.js @@ -5,7 +5,7 @@ * plane using the same task fixtures, deterministic assertions, and solver * observability signals. * - * Consumer: `/sf solver-eval` and focused regression tests. + * Consumer: `/solver-eval` and focused regression tests. */ import { spawnSync } from "node:child_process"; import { @@ -111,7 +111,7 @@ function normalizeCase(raw, index, source) { * Purpose: make solver claims reproducible from versioned or shared fixtures * instead of ad hoc manual demos. * - * Consumer: `/sf solver-eval --cases `. + * Consumer: `/solver-eval --cases `. */ export function loadAutonomousSolverEvalCases(casesPath) { const abs = resolve(casesPath); @@ -131,7 +131,7 @@ export function loadAutonomousSolverEvalCases(casesPath) { * Purpose: let operators verify the eval harness itself without spending model * quota or configuring external benchmark datasets. * - * Consumer: `/sf solver-eval --sample` and tests. + * Consumer: `/solver-eval --sample` and tests. */ export function sampleAutonomousSolverEvalCases() { return [ @@ -368,7 +368,7 @@ function resolveOutputDir(basePath, runId) { * Purpose: produce local evidence for whether SF's solver loop improves * completion quality over a raw loop under identical task fixtures. * - * Consumer: `/sf solver-eval run` and regression tests. + * Consumer: `/solver-eval run` and regression tests. */ export function runAutonomousSolverEval(options) { const basePath = resolve(options.basePath ?? process.cwd()); @@ -433,7 +433,7 @@ export function runAutonomousSolverEval(options) { /** * Run and record the built-in autonomous solver eval as a best-effort lifecycle hook. * - * Purpose: make solver quality evidence automatic for `/sf autonomous` sessions + * Purpose: make solver quality evidence automatic for `/autonomous` sessions * so regressions are captured without requiring a separate manual command. * * Consumer: auto/loop.js when an autonomous session exits. @@ -491,12 +491,12 @@ export async function runAutomaticAutonomousSolverEval(options) { } /** - * Parse `/sf solver-eval` arguments. + * Parse `/solver-eval` arguments. * * Purpose: keep command behavior explicit and reproducible while avoiding * shell parsing or hidden defaults. * - * Consumer: `/sf solver-eval` handler. + * Consumer: `/solver-eval` handler. */ export function parseAutonomousSolverEvalArgs(raw) { const tokens = String(raw ?? "") @@ -588,14 +588,14 @@ async function recordEvalRunBestEffort(basePath, report) { async function notifySolverEvalHistory(ctx, basePath, limit) { if (!(await ensureDbOpen(basePath))) { - ctx.ui.notify("No SF database available. Run /sf init first.", "warning"); + ctx.ui.notify("No SF database available. Run /init first.", "warning"); return; } const { listSolverEvalRuns } = await import("./sf-db.js"); const runs = listSolverEvalRuns(limit); if (runs.length === 0) { ctx.ui.notify( - "No solver eval runs recorded. Run /sf solver-eval --sample.", + "No solver eval runs recorded. Run /solver-eval --sample.", "info", ); return; @@ -618,7 +618,7 @@ async function notifySolverEvalHistory(ctx, basePath, limit) { async function notifySolverEvalShow(ctx, basePath, runId) { if (!(await ensureDbOpen(basePath))) { - ctx.ui.notify("No SF database available. Run /sf init first.", "warning"); + ctx.ui.notify("No SF database available. Run /init first.", "warning"); return; } const { getSolverEvalCaseResults, getSolverEvalRun } = await import( @@ -645,7 +645,7 @@ async function notifySolverEvalShow(ctx, basePath, runId) { } /** - * Handle `/sf solver-eval`. + * Handle `/solver-eval`. * * Purpose: expose solver-loop benchmarking as a first-class SF operation with * evidence stored under `.sf`, not as an external script. @@ -662,7 +662,7 @@ export async function handleAutonomousSolverEval( args = parseAutonomousSolverEvalArgs(rawArgs); } catch (err) { ctx.ui.notify( - `Usage: /sf solver-eval [run|history|show ] [--sample | --cases ] [--run-id ] [--limit ]\n${err instanceof Error ? err.message : String(err)}`, + `Usage: /solver-eval [run|history|show ] [--sample | --cases ] [--run-id ] [--limit ]\n${err instanceof Error ? err.message : String(err)}`, "warning", ); return; diff --git a/src/resources/extensions/sf/autonomous-solver.js b/src/resources/extensions/sf/autonomous-solver.js index 40361a9cc..052ce55d8 100644 --- a/src/resources/extensions/sf/autonomous-solver.js +++ b/src/resources/extensions/sf/autonomous-solver.js @@ -220,7 +220,7 @@ export function buildAutonomousSolverPromptBlock(state) { return [ "## Autonomous Solver Loop Contract", "", - `You are inside /sf autonomous iteration ${state.iteration} of ${state.maxIterations} for ${state.unitType} ${state.unitId}.`, + `You are inside /autonomous iteration ${state.iteration} of ${state.maxIterations} for ${state.unitType} ${state.unitId}.`, "", "This is SF's built-in solver loop. It is not a separate Ralph workflow. Work one bounded, useful chunk; preserve enough state for the next autonomous iteration to continue without guessing.", "", @@ -317,7 +317,7 @@ export function appendAutonomousSolverCheckpoint(basePath, params) { * Purpose: status surfaces and loop enforcement need one structured source for * the active solver unit instead of scraping markdown projections. * - * Consumer: /sf status, sf-progress, and runUnitPhase. + * Consumer: /status, sf-progress, and runUnitPhase. */ export function readAutonomousSolverState(basePath) { return readJson(statePath(basePath)); @@ -526,10 +526,10 @@ export function assessAutonomousSolverTurn(basePath, unitType, unitId) { /** * Append user steering for the next autonomous solver iteration. * - * Purpose: active /sf steer must redirect the next bounded iteration without + * Purpose: active /steer must redirect the next bounded iteration without * interrupting the current tool batch or forcing an immediate agent turn. * - * Consumer: /sf steer while autonomous mode is active. + * Consumer: /steer while autonomous mode is active. */ export function appendAutonomousSolverSteering(basePath, text, metadata = {}) { const trimmed = String(text ?? "").trim(); diff --git a/src/resources/extensions/sf/blocked-models.js b/src/resources/extensions/sf/blocked-models.js index 4ff70b058..906a8d615 100644 --- a/src/resources/extensions/sf/blocked-models.js +++ b/src/resources/extensions/sf/blocked-models.js @@ -2,7 +2,7 @@ // provider has rejected at request time for account entitlement or temporary // capacity reasons. // -// Lives at `.sf/runtime/blocked-models.json` so the block survives /sf autonomous +// Lives at `.sf/runtime/blocked-models.json` so the block survives /autonomous // restarts. Autonomous mode model selection skips blocked entries; agent-end // recovery adds entries when a runtime rejection is classified as // `unsupported-model`. See issue #4513. diff --git a/src/resources/extensions/sf/bootstrap/agent-end-recovery.js b/src/resources/extensions/sf/bootstrap/agent-end-recovery.js index 85f0d8231..88f8e2cdc 100644 --- a/src/resources/extensions/sf/bootstrap/agent-end-recovery.js +++ b/src/resources/extensions/sf/bootstrap/agent-end-recovery.js @@ -208,7 +208,7 @@ export async function handleAgentEnd(pi, event, ctx) { } // ── 1c. Unsupported-model: provider rejected this model for the current // account/plan at request time (#4513). Persist a block so the - // same dead model isn't reselected on the next /sf autonomous restart, + // same dead model isn't reselected on the next /autonomous restart, // then try a fallback before pausing. if (cls.kind === "unsupported-model") { const rejectedProvider = currentRoute?.provider; @@ -294,7 +294,7 @@ export async function handleAgentEnd(pi, event, ctx) { }); if (switched) return; } - // --- Transient fallback exhausted: pause without same-route auto-resume --- + // --- Transient fallback exhausted: pause without same-route scheduled resume --- if (isTransient(cls)) { const message = isModelRouteFailure(cls) && dash.currentUnit diff --git a/src/resources/extensions/sf/bootstrap/db-tools.js b/src/resources/extensions/sf/bootstrap/db-tools.js index 694b2e0eb..f6e51e3d2 100644 --- a/src/resources/extensions/sf/bootstrap/db-tools.js +++ b/src/resources/extensions/sf/bootstrap/db-tools.js @@ -931,7 +931,7 @@ export function registerDbTools(pi) { label: "Autonomous Checkpoint", description: "Record a PDD-shaped autonomous solver checkpoint for the current unit. " + - "Use this before ending every /sf autonomous unit turn to make progress, blockers, decisions, and remaining work explicit.", + "Use this before ending every /autonomous unit turn to make progress, blockers, decisions, and remaining work explicit.", promptSnippet: "Checkpoint autonomous solver progress with PDD fields and semantic outcome", promptGuidelines: [ @@ -1720,7 +1720,7 @@ export function registerDbTools(pi) { Type.Object({ id: Type.String({ description: - "Short id (e.g. 'A', 'B') used by /sf escalate resolve.", + "Short id (e.g. 'A', 'B') used by /escalate resolve.", }), label: Type.String({ description: "One-line label." }), tradeoffs: Type.String({ @@ -1741,7 +1741,7 @@ export function registerDbTools(pi) { }), continueWithDefault: Type.Boolean({ description: - "When true, loop continues (artifact logged for later review). When false, autonomous mode pauses until the user resolves via /sf escalate resolve.", + "When true, loop continues (artifact logged for later review). When false, autonomous mode pauses until the user resolves via /escalate resolve.", }), }, { @@ -2006,7 +2006,7 @@ export function registerDbTools(pi) { updateSliceStatus(params.milestoneId, params.sliceId, "skipped"); invalidateStateCache(); // Rebuild STATE.md so it reflects the skip immediately (#3477). - // Without this, /sf autonomous reads stale STATE.md and resumes the skipped slice. + // Without this, /autonomous reads stale STATE.md and resumes the skipped slice. try { const basePath = process.cwd(); const { rebuildState } = await import("../doctor.js"); diff --git a/src/resources/extensions/sf/bootstrap/provider-error-resume.js b/src/resources/extensions/sf/bootstrap/provider-error-resume.js index 5fe66e80c..9e148af64 100644 --- a/src/resources/extensions/sf/bootstrap/provider-error-resume.js +++ b/src/resources/extensions/sf/bootstrap/provider-error-resume.js @@ -39,7 +39,7 @@ export async function resumeAutoAfterProviderDelay( } // Reset the transient retry counter before restarting — without this, // consecutiveTransientCount accumulates across pause/resume cycles and - // permanently locks out auto-resume after MAX_TRANSIENT_AUTO_RESUMES errors. + // permanently locks out scheduled resume after MAX_TRANSIENT_AUTO_RESUMES errors. deps.resetTransientRetryState(); await deps.startAuto(commandCtx, pi, snapshot.basePath, false, { step: snapshot.stepMode, diff --git a/src/resources/extensions/sf/bootstrap/register-hooks.js b/src/resources/extensions/sf/bootstrap/register-hooks.js index 9d11dba84..4cc492131 100644 --- a/src/resources/extensions/sf/bootstrap/register-hooks.js +++ b/src/resources/extensions/sf/bootstrap/register-hooks.js @@ -119,7 +119,7 @@ async function runSessionStartupDoctorFix(ctx) { const summary = summarizeDoctorIssues(report.issues); if (summary.errors > 0) { ctx.ui?.notify?.( - `Startup doctor found ${summary.errors} blocking issue(s). Run /sf doctor audit for details.`, + `Startup doctor found ${summary.errors} blocking issue(s). Run /doctor audit for details.`, "warning", ); } @@ -276,7 +276,7 @@ export function registerHooks(pi, ecosystemHandlers = []) { ); } // Forge-only: high/critical entries are queued as hidden follow-up repair - // work on startup, even outside /sf autonomous. The drain helper owns claim TTL + // work on startup, even outside /autonomous. The drain helper owns claim TTL // and delivery failure retry, so this is safe to call opportunistically. const highBlocked = triage.stillBlocked.filter( (e) => e.severity === "high" || e.severity === "critical", @@ -471,7 +471,7 @@ export function registerHooks(pi, ecosystemHandlers = []) { completedWork: `Task ${state.activeTask.id} (${state.activeTask.title}) was in progress when compaction occurred.`, remainingWork: "Check the task plan for remaining steps.", decisions: "Check task summary files for prior decisions.", - context: "Session was auto-compacted by Pi. Resume with /sf.", + context: "Session was auto-compacted by Pi. Resume with /next.", nextAction: `Resume task ${state.activeTask.id}: ${state.activeTask.title}.`, }), ); @@ -573,7 +573,7 @@ export function registerHooks(pi, ecosystemHandlers = []) { } } // ── Queue-mode execution guard (#2545): block source-code mutations ── - // When /sf queue is active, the agent should only create milestones, + // When /queue is active, the agent should only create milestones, // not execute work. Block write/edit to non-.sf/ paths and bash commands // that would modify files. if (isQueuePhaseActive()) { diff --git a/src/resources/extensions/sf/bootstrap/register-shortcuts.js b/src/resources/extensions/sf/bootstrap/register-shortcuts.js index 79d47a6d1..90f45c673 100644 --- a/src/resources/extensions/sf/bootstrap/register-shortcuts.js +++ b/src/resources/extensions/sf/bootstrap/register-shortcuts.js @@ -17,7 +17,7 @@ export function registerShortcuts(pi) { const openDashboardOverlay = async (ctx) => { const basePath = projectRoot(); if (!existsSync(join(basePath, ".sf"))) { - ctx.ui.notify("No .sf/ directory found. Run /sf to start.", "info"); + ctx.ui.notify("No .sf/ directory found. Run /next to start.", "info"); return; } await ctx.ui.custom( @@ -50,7 +50,7 @@ export function registerShortcuts(pi) { const parallelDir = join(basePath, ".sf", "parallel"); if (!existsSync(parallelDir)) { ctx.ui.notify( - "No parallel workers found. Run /sf parallel start first.", + "No parallel workers found. Run /parallel start first.", "info", ); return; @@ -102,5 +102,5 @@ export function registerShortcuts(pi) { handler: openParallelOverlay, }); // No Ctrl+Shift+P fallback — conflicts with cycleModelBackward (shift+ctrl+p). - // Use Ctrl+Alt+P or /sf parallel watch instead. + // Use Ctrl+Alt+P or /parallel watch instead. } diff --git a/src/resources/extensions/sf/bootstrap/system-context.js b/src/resources/extensions/sf/bootstrap/system-context.js index 2aea2d17c..e3f622401 100644 --- a/src/resources/extensions/sf/bootstrap/system-context.js +++ b/src/resources/extensions/sf/bootstrap/system-context.js @@ -183,7 +183,7 @@ export async function buildBeforeAgentStartResult(event, ctx) { if (autoEnableCmuxPreferences()) { loadedPreferences = loadEffectiveSFPreferences(); ctx.ui.notify( - "cmux detected — auto-enabled. Run /sf cmux off to disable.", + "cmux detected — auto-enabled. Run /cmux off to disable.", "info", ); } @@ -278,7 +278,7 @@ export async function buildBeforeAgentStartResult(event, ctx) { ? rawContent.slice(0, MAX_CODEBASE_CHARS) + "\n\n*(truncated — see .sf/CODEBASE.md for full map)*" : rawContent; - codebaseBlock = `\n\n[PROJECT CODEBASE — File structure and descriptions (generated ${generatedAt}, auto-refreshed when SF detects tracked file changes; use /sf codebase stats for status)]\n\n${content}`; + codebaseBlock = `\n\n[PROJECT CODEBASE — File structure and descriptions (generated ${generatedAt}, auto-refreshed when SF detects tracked file changes; use /codebase stats for status)]\n\n${content}`; } } catch (e) { logWarning("bootstrap", `CODEBASE file read failed: ${e.message}`); diff --git a/src/resources/extensions/sf/bootstrap/write-gate.js b/src/resources/extensions/sf/bootstrap/write-gate.js index 49edafe42..2324b13cb 100644 --- a/src/resources/extensions/sf/bootstrap/write-gate.js +++ b/src/resources/extensions/sf/bootstrap/write-gate.js @@ -492,7 +492,7 @@ export function shouldBlockQueueExecutionInSnapshot( return { block: true, reason: - `Blocked: /sf queue is a planning tool — it creates milestones, not executes work. ` + + `Blocked: /queue is a planning tool — it creates milestones, not executes work. ` + `Cannot ${toolName} to "${input}" during queue mode. ` + `Write CONTEXT.md files and update PROJECT.md/QUEUE.md instead.`, }; @@ -503,7 +503,7 @@ export function shouldBlockQueueExecutionInSnapshot( return { block: true, reason: - `Blocked: /sf queue is a planning tool — it creates milestones, not executes work. ` + + `Blocked: /queue is a planning tool — it creates milestones, not executes work. ` + `Cannot run "${input.slice(0, 80)}${input.length > 80 ? "…" : ""}" during queue mode. ` + `Use read-only commands (cat, grep, git log, etc.) to investigate, then write planning artifacts.`, }; @@ -512,6 +512,6 @@ export function shouldBlockQueueExecutionInSnapshot( // bypass execution restrictions. return { block: true, - reason: `Blocked: /sf queue is a planning tool — it creates milestones, not executes work. Unknown tools are not permitted during queue mode.`, + reason: `Blocked: /queue is a planning tool — it creates milestones, not executes work. Unknown tools are not permitted during queue mode.`, }; } diff --git a/src/resources/extensions/sf/canonical-milestone-plan.js b/src/resources/extensions/sf/canonical-milestone-plan.js index 55c2ba0ab..37d892c46 100644 --- a/src/resources/extensions/sf/canonical-milestone-plan.js +++ b/src/resources/extensions/sf/canonical-milestone-plan.js @@ -236,7 +236,7 @@ export function getCanonicalMilestonePlan(basePath, milestoneId, options = {}) { return blockedResult( `db-missing-${dbPlan.missing}`, milestoneId, - `.sf/sf.db is available, so ${milestoneId} must be read from DB rows. Missing ${dbPlan.missing}; projection files are export/recovery only. Run /sf doctor or sf recover to reconcile.`, + `.sf/sf.db is available, so ${milestoneId} must be read from DB rows. Missing ${dbPlan.missing}; projection files are export/recovery only. Run /doctor or sf recover to reconcile.`, paths, ); } diff --git a/src/resources/extensions/sf/commands-add-tests.js b/src/resources/extensions/sf/commands-add-tests.js index 838a7f5e0..4efc09c44 100644 --- a/src/resources/extensions/sf/commands-add-tests.js +++ b/src/resources/extensions/sf/commands-add-tests.js @@ -1,5 +1,5 @@ /** - * SF Command — /sf add-tests + * SF Command — /add-tests * * Generates tests for a completed slice by dispatching an LLM prompt * with implementation context (summaries, changed files, test patterns). @@ -101,7 +101,7 @@ export async function handleAddTests(args, ctx, pi) { const targetId = args.trim() || findLastCompletedSlice(basePath, milestoneId); if (!targetId) { ctx.ui.notify( - "No completed slices found. Specify a slice ID: /sf add-tests S03", + "No completed slices found. Specify a slice ID: /add-tests S03", "warning", ); return; diff --git a/src/resources/extensions/sf/commands-backlog.js b/src/resources/extensions/sf/commands-backlog.js index 99cb7c247..be807ae90 100644 --- a/src/resources/extensions/sf/commands-backlog.js +++ b/src/resources/extensions/sf/commands-backlog.js @@ -1,5 +1,5 @@ /** - * SF Command — /sf backlog + * SF Command — /backlog * * Structured backlog management with 999.x numbering. * Items live in `.sf/sf.db`. @@ -31,7 +31,7 @@ async function listBacklog(basePath, ctx) { const items = currentItems(basePath); if (items.length === 0) { ctx.ui.notify( - "Backlog is empty. Add items with /sf backlog add ", + "Backlog is empty. Add items with /backlog add <title>", "info", ); return; @@ -50,7 +50,7 @@ async function listBacklog(basePath, ctx) { async function addBacklogItem(basePath, title, ctx) { if (!title) { - ctx.ui.notify("Usage: /sf backlog add <title>", "warning"); + ctx.ui.notify("Usage: /backlog add <title>", "warning"); return; } if (!ensureBacklogDb(basePath)) { @@ -71,7 +71,7 @@ async function addBacklogItem(basePath, title, ctx) { async function promoteBacklogItem(basePath, itemId, ctx, _pi) { if (!itemId) { ctx.ui.notify( - "Usage: /sf backlog promote <id>\nExample: /sf backlog promote 999.1", + "Usage: /backlog promote <id>\nExample: /backlog promote 999.1", "warning", ); return; @@ -102,7 +102,7 @@ async function promoteBacklogItem(basePath, itemId, ctx, _pi) { async function removeBacklogItem(basePath, itemId, ctx) { if (!itemId) { - ctx.ui.notify("Usage: /sf backlog remove <id>", "warning"); + ctx.ui.notify("Usage: /backlog remove <id>", "warning"); return; } if (!ensureBacklogDb(basePath)) { diff --git a/src/resources/extensions/sf/commands-bootstrap.js b/src/resources/extensions/sf/commands-bootstrap.js index 2ff046c2c..d6a1322d6 100644 --- a/src/resources/extensions/sf/commands-bootstrap.js +++ b/src/resources/extensions/sf/commands-bootstrap.js @@ -1,4 +1,4 @@ -import { importExtensionModule } from "@singularity-forge/pi-coding-agent"; +import { registerSFCommands } from "./commands/index.js"; import { workflowTemplateCommandDefinitions } from "./workflow-templates.js"; const TOP_LEVEL_SUBCOMMANDS = [ @@ -11,7 +11,7 @@ const TOP_LEVEL_SUBCOMMANDS = [ { cmd: "stop", desc: "Stop autonomous mode gracefully" }, { cmd: "pause", - desc: "Pause autonomous mode (preserves state, /sf autonomous to resume)", + desc: "Pause autonomous mode (preserves state, /autonomous to resume)", }, { cmd: "status", desc: "Progress dashboard" }, { cmd: "visualize", desc: "Open workflow visualizer" }, @@ -79,7 +79,7 @@ function filterStartsWith(partial, options, prefix = "") { description: option.desc, })); } -function getSfArgumentCompletions(prefix) { +function _getSfArgumentCompletions(prefix) { const parts = prefix.trim().split(/\s+/); if (parts.length <= 1) { return filterStartsWith(parts[0] ?? "", TOP_LEVEL_SUBCOMMANDS); @@ -348,15 +348,5 @@ function getSfArgumentCompletions(prefix) { return null; } export function registerLazySFCommand(pi) { - pi.registerCommand("sf", { - description: "SF — Singularity Forge", - getArgumentCompletions: getSfArgumentCompletions, - handler: async (args, ctx) => { - const { handleSFCommand } = await importExtensionModule( - import.meta.url, - "./commands.js", - ); - await handleSFCommand(args, ctx, pi); - }, - }); + registerSFCommands(pi); } diff --git a/src/resources/extensions/sf/commands-cmux.js b/src/resources/extensions/sf/commands-cmux.js index b003296a5..6d6121441 100644 --- a/src/resources/extensions/sf/commands-cmux.js +++ b/src/resources/extensions/sf/commands-cmux.js @@ -178,7 +178,7 @@ export async function handleCmux(args, ctx) { return; } ctx.ui.notify( - "Usage: /sf cmux <status|on|off|notifications on|notifications off|sidebar on|sidebar off|splits on|splits off|browser on|browser off>", + "Usage: /cmux <status|on|off|notifications on|notifications off|sidebar on|sidebar off|splits on|splits off|browser on|browser off>", "info", ); } diff --git a/src/resources/extensions/sf/commands-codebase.js b/src/resources/extensions/sf/commands-codebase.js index ca58f9108..21d6f449d 100644 --- a/src/resources/extensions/sf/commands-codebase.js +++ b/src/resources/extensions/sf/commands-codebase.js @@ -1,5 +1,5 @@ /** - * SF Command — /sf codebase + * SF Command — /codebase * * Generate and manage the codebase map (.sf/CODEBASE.md). * Subcommands: generate, update, stats, indexer, help @@ -15,7 +15,7 @@ import { import { loadEffectiveSFPreferences } from "./preferences.js"; const USAGE = - "Usage: /sf codebase [generate|update|stats|indexer]\n\n" + + "Usage: /codebase [generate|update|stats|indexer]\n\n" + " generate [--max-files N] [--collapse-threshold N] — Generate or regenerate CODEBASE.md\n" + " update [--max-files N] [--collapse-threshold N] — Refresh the CODEBASE.md cache immediately\n" + " stats — Show file count, coverage, and generation time\n" + @@ -69,7 +69,7 @@ export async function handleCodebase(args, ctx, _pi) { const existing = readCodebaseMap(basePath); if (!existing) { ctx.ui.notify( - "No codebase map found. Run /sf codebase generate to create one.", + "No codebase map found. Run /codebase generate to create one.", "warning", ); return; @@ -100,7 +100,7 @@ export async function handleCodebase(args, ctx, _pi) { return; } ctx.ui.notify( - `Unknown /sf codebase indexer action "${action}". Use status.`, + `Unknown /codebase indexer action "${action}". Use status.`, "warning", ); return; @@ -126,7 +126,7 @@ function showStats(basePath, ctx) { const stats = getCodebaseMapStats(basePath); if (!stats.exists) { ctx.ui.notify( - "No codebase map found. Run /sf codebase generate to create one.", + "No codebase map found. Run /codebase generate to create one.", "info", ); return; @@ -142,7 +142,7 @@ function showStats(basePath, ctx) { ` Undescribed: ${stats.undescribedCount}\n` + ` Generated: ${stats.generatedAt ?? "unknown"}\n\n` + (stats.undescribedCount > 0 - ? `Tip: Auto-refresh keeps the cache current, but /sf codebase update forces an immediate refresh.` + ? `Tip: Auto-refresh keeps the cache current, but /codebase update forces an immediate refresh.` : `Coverage is complete.`), "info", ); diff --git a/src/resources/extensions/sf/commands-debug.js b/src/resources/extensions/sf/commands-debug.js index 8dbbb6133..1507322fc 100644 --- a/src/resources/extensions/sf/commands-debug.js +++ b/src/resources/extensions/sf/commands-debug.js @@ -21,11 +21,11 @@ function formatSessionLine(prefix, session) { } function usageText() { return [ - "Usage: /sf debug <issue-text>", - " /sf debug list", - " /sf debug status <slug>", - " /sf debug continue <slug>", - " /sf debug --diagnose [<slug> | <issue text>]", + "Usage: /debug <issue-text>", + " /debug list", + " /debug status <slug>", + " /debug continue <slug>", + " /debug --diagnose [<slug> | <issue text>]", ].join("\n"); } export function parseDebugCommand(args) { @@ -42,7 +42,7 @@ export function parseDebugCommand(args) { if (parts.length === 1) return { type: "error", - message: "Missing slug. Usage: /sf debug status <slug>", + message: "Missing slug. Usage: /debug status <slug>", }; if (parts.length === 2 && isValidSlugCandidate(parts[1])) return { type: "status", slug: parts[1] }; @@ -52,7 +52,7 @@ export function parseDebugCommand(args) { if (parts.length === 1) return { type: "error", - message: "Missing slug. Usage: /sf debug continue <slug>", + message: "Missing slug. Usage: /debug continue <slug>", }; if (parts.length === 2 && isValidSlugCandidate(parts[1])) return { type: "continue", slug: parts[1] }; @@ -67,7 +67,7 @@ export function parseDebugCommand(args) { return { type: "error", message: - "Invalid diagnose target. Usage: /sf debug --diagnose [<slug> | <issue text>]", + "Invalid diagnose target. Usage: /debug --diagnose [<slug> | <issue text>]", }; } if (head.startsWith("-") && !SUBCOMMANDS.has(head)) { @@ -106,7 +106,7 @@ export async function handleDebug(args, ctx, pi) { formatSessionLine("Session:", s), `Artifact: ${created.artifactPath}`, `Log: ${s.logPath}`, - `Next: /sf debug status ${s.slug} or /sf debug continue ${s.slug}`, + `Next: /debug status ${s.slug} or /debug continue ${s.slug}`, ].join("\n") + dispatchNote, "info", ); @@ -129,7 +129,7 @@ export async function handleDebug(args, ctx, pi) { } catch (err) { const msg = err instanceof Error ? err.message : String(err); ctx.ui.notify( - `Debug dispatch failed: ${msg}\nSession '${s.slug}' is persisted; retry with /sf debug continue ${s.slug}`, + `Debug dispatch failed: ${msg}\nSession '${s.slug}' is persisted; retry with /debug continue ${s.slug}`, "warning", ); } @@ -137,7 +137,7 @@ export async function handleDebug(args, ctx, pi) { } catch (error) { const message = error instanceof Error ? error.message : String(error); ctx.ui.notify( - `Unable to create debug session: ${message}\nTry /sf debug --diagnose for artifact health details.`, + `Unable to create debug session: ${message}\nTry /debug --diagnose for artifact health details.`, "error", ); } @@ -148,7 +148,7 @@ export async function handleDebug(args, ctx, pi) { const listed = listDebugSessions(basePath); if (listed.sessions.length === 0 && listed.malformed.length === 0) { ctx.ui.notify( - "No debug sessions found. Start one with: /sf debug <issue-text>", + "No debug sessions found. Start one with: /debug <issue-text>", "info", ); return; @@ -169,13 +169,13 @@ export async function handleDebug(args, ctx, pi) { if (listed.malformed.length > 5) { lines.push(` ... and ${listed.malformed.length - 5} more`); } - lines.push("Run /sf debug --diagnose for remediation guidance."); + lines.push("Run /debug --diagnose for remediation guidance."); } ctx.ui.notify(lines.join("\n"), "info"); } catch (error) { const message = error instanceof Error ? error.message : String(error); ctx.ui.notify( - `Unable to list debug sessions: ${message}\nRun /sf debug --diagnose for details.`, + `Unable to list debug sessions: ${message}\nRun /debug --diagnose for details.`, "warning", ); } @@ -186,7 +186,7 @@ export async function handleDebug(args, ctx, pi) { const loaded = loadDebugSession(basePath, parsed.slug); if (!loaded) { ctx.ui.notify( - `Unknown debug session slug '${parsed.slug}'. Run /sf debug list to see available sessions.`, + `Unknown debug session slug '${parsed.slug}'. Run /debug list to see available sessions.`, "warning", ); return; @@ -209,7 +209,7 @@ export async function handleDebug(args, ctx, pi) { } catch (error) { const message = error instanceof Error ? error.message : String(error); ctx.ui.notify( - `Unable to load debug session '${parsed.slug}': ${message}\nTry /sf debug --diagnose ${parsed.slug}`, + `Unable to load debug session '${parsed.slug}': ${message}\nTry /debug --diagnose ${parsed.slug}`, "warning", ); } @@ -220,14 +220,14 @@ export async function handleDebug(args, ctx, pi) { const loaded = loadDebugSession(basePath, parsed.slug); if (!loaded) { ctx.ui.notify( - `Unknown debug session slug '${parsed.slug}'. Run /sf debug list to see available sessions.`, + `Unknown debug session slug '${parsed.slug}'. Run /debug list to see available sessions.`, "warning", ); return; } if (loaded.session.status === "resolved") { ctx.ui.notify( - `Session '${parsed.slug}' is resolved. Open a new session with /sf debug <issue-text> for follow-up work.`, + `Session '${parsed.slug}' is resolved. Open a new session with /debug <issue-text> for follow-up work.`, "warning", ); return; @@ -336,7 +336,7 @@ export async function handleDebug(args, ctx, pi) { `Resumed debug session: ${resumed.session.slug}`, formatSessionLine("Session:", resumed.session), `Log: ${resumed.session.logPath}`, - `Next: /sf debug status ${resumed.session.slug}`, + `Next: /debug status ${resumed.session.slug}`, ].join("\n") + dispatchNote, "info", ); @@ -366,7 +366,7 @@ export async function handleDebug(args, ctx, pi) { } catch (err) { const msg = err instanceof Error ? err.message : String(err); ctx.ui.notify( - `Continue dispatch failed: ${msg}\nSession '${resumed.session.slug}' is persisted; retry with /sf debug continue ${resumed.session.slug}`, + `Continue dispatch failed: ${msg}\nSession '${resumed.session.slug}' is persisted; retry with /debug continue ${resumed.session.slug}`, "warning", ); } @@ -374,7 +374,7 @@ export async function handleDebug(args, ctx, pi) { } catch (error) { const message = error instanceof Error ? error.message : String(error); ctx.ui.notify( - `Unable to continue debug session '${parsed.slug}': ${message}\nTry /sf debug --diagnose ${parsed.slug}`, + `Unable to continue debug session '${parsed.slug}': ${message}\nTry /debug --diagnose ${parsed.slug}`, "warning", ); } @@ -396,7 +396,7 @@ export async function handleDebug(args, ctx, pi) { `Artifact: ${created.artifactPath}`, `Log: ${s.logPath}`, `dispatchMode=find_root_cause_only`, - `Next: /sf debug status ${s.slug} or /sf debug --diagnose ${s.slug}`, + `Next: /debug status ${s.slug} or /debug --diagnose ${s.slug}`, ].join("\n"), "info", ); @@ -420,7 +420,7 @@ export async function handleDebug(args, ctx, pi) { } catch (err) { const msg = err instanceof Error ? err.message : String(err); ctx.ui.notify( - `Diagnose dispatch failed: ${msg}\nSession '${s.slug}' is persisted; continue manually with /sf debug continue ${s.slug}`, + `Diagnose dispatch failed: ${msg}\nSession '${s.slug}' is persisted; continue manually with /debug continue ${s.slug}`, "warning", ); } @@ -428,7 +428,7 @@ export async function handleDebug(args, ctx, pi) { } catch (error) { const message = error instanceof Error ? error.message : String(error); ctx.ui.notify( - `Unable to create diagnose session: ${message}\nTry /sf debug --diagnose for artifact health details.`, + `Unable to create diagnose session: ${message}\nTry /debug --diagnose for artifact health details.`, "error", ); } @@ -441,7 +441,7 @@ export async function handleDebug(args, ctx, pi) { const loaded = loadDebugSession(basePath, parsed.slug); if (!loaded) { ctx.ui.notify( - `Diagnose: session '${parsed.slug}' not found.\nRun /sf debug list to discover valid slugs.`, + `Diagnose: session '${parsed.slug}' not found.\nRun /debug list to discover valid slugs.`, "warning", ); return; diff --git a/src/resources/extensions/sf/commands-do.js b/src/resources/extensions/sf/commands-do.js index 83f18ad8d..1677d0ca8 100644 --- a/src/resources/extensions/sf/commands-do.js +++ b/src/resources/extensions/sf/commands-do.js @@ -1,8 +1,8 @@ /** - * SF Command — /sf do + * SF Command — /do * - * Routes freeform natural language to the correct /sf subcommand - * using keyword matching. Falls back to /sf quick for task-like input. + * Routes freeform natural language to the correct /subcommand + * using keyword matching. Falls back to /quick for task-like input. */ import { importExtensionModule } from "@singularity-forge/pi-coding-agent"; @@ -116,12 +116,12 @@ function matchRoute(input) { export async function handleDo(args, ctx, pi) { if (!args.trim()) { ctx.ui.notify( - "Usage: /sf do <what you want to do>\n\n" + + "Usage: /do <what you want to do>\n\n" + "Examples:\n" + - " /sf do show me progress\n" + - " /sf do run autonomously\n" + - " /sf do clean up old branches\n" + - " /sf do fix the login bug", + " /do show me progress\n" + + " /do run autonomously\n" + + " /do clean up old branches\n" + + " /do fix the login bug", "warning", ); return; @@ -131,7 +131,7 @@ export async function handleDo(args, ctx, pi) { const fullCommand = match.remainingArgs ? `${match.command} ${match.remainingArgs}` : match.command; - ctx.ui.notify(`→ /sf ${fullCommand}`, "info"); + ctx.ui.notify(`→ /${fullCommand}`, "info"); // Re-dispatch through the main dispatcher const { handleSFCommand } = await importExtensionModule( import.meta.url, @@ -141,7 +141,7 @@ export async function handleDo(args, ctx, pi) { return; } // No keyword match → treat as quick task - ctx.ui.notify(`→ /sf quick ${args}`, "info"); + ctx.ui.notify(`→ /quick ${args}`, "info"); const { handleQuick } = await importExtensionModule( import.meta.url, "./quick.js", diff --git a/src/resources/extensions/sf/commands-escalate.js b/src/resources/extensions/sf/commands-escalate.js index c87cc3a0c..25c185626 100644 --- a/src/resources/extensions/sf/commands-escalate.js +++ b/src/resources/extensions/sf/commands-escalate.js @@ -1,4 +1,4 @@ -// SF Command — `/sf escalate` (SF ADR-011 P2) +// SF Command — `/escalate` (SF ADR-011 P2) // // Subcommands: // list [--all] — show active escalations; --all also includes resolved @@ -18,7 +18,7 @@ import { function usage() { return [ - "Usage: /sf escalate <subcommand>", + "Usage: /escalate <subcommand>", "", "Subcommands:", " list [--all] List active escalations (--all also shows resolved)", @@ -35,7 +35,7 @@ function parseSliceTask(spec) { export async function handleEscalate(args, ctx) { await ensureDbOpen(process.cwd()); if (!isDbAvailable()) { - ctx.ui.notify("SF database is not available. Run /sf doctor.", "error"); + ctx.ui.notify("SF database is not available. Run /doctor.", "error"); return; } const trimmed = args.trim(); @@ -85,7 +85,7 @@ export async function handleEscalate(args, ctx) { ctx.ui.notify( includeResolved ? "No escalations recorded." - : "No active escalations. Use /sf escalate list --all to include resolved.", + : "No active escalations. Use /escalate list --all to include resolved.", "info", ); return; @@ -98,7 +98,7 @@ export async function handleEscalate(args, ctx) { const parsed = spec ? parseSliceTask(spec) : null; if (!parsed) { ctx.ui.notify( - "Usage: /sf escalate show <sliceId>/<taskId> (e.g. S01/T01)", + "Usage: /escalate show <sliceId>/<taskId> (e.g. S01/T01)", "warning", ); return; @@ -145,7 +145,7 @@ export async function handleEscalate(args, ctx) { ); } else { out.push( - `\nUnresolved. Run /sf escalate resolve ${parsed.sliceId}/${parsed.taskId} <option-id|accept>`, + `\nUnresolved. Run /escalate resolve ${parsed.sliceId}/${parsed.taskId} <option-id|accept>`, ); } ctx.ui.notify(out.join("\n"), "info"); @@ -156,7 +156,7 @@ export async function handleEscalate(args, ctx) { const parsed = spec ? parseSliceTask(spec) : null; if (!parsed) { ctx.ui.notify( - "Usage: /sf escalate resolve <sliceId>/<taskId> <option> [-- <rationale>]", + "Usage: /escalate resolve <sliceId>/<taskId> <option> [-- <rationale>]", "warning", ); return; diff --git a/src/resources/extensions/sf/commands-eval-review.js b/src/resources/extensions/sf/commands-eval-review.js index b66c64cd0..879afbda7 100644 --- a/src/resources/extensions/sf/commands-eval-review.js +++ b/src/resources/extensions/sf/commands-eval-review.js @@ -1,5 +1,5 @@ /** - * SF Command — /sf eval-review + * SF Command — /eval-review * * Audits the implemented evaluation strategy of a slice against the planned * `AI-SPEC.md` and observed `SUMMARY.md`. Dispatches an LLM turn that scores @@ -63,7 +63,7 @@ const READ_MARKER_RESERVE_BYTES = 128; const SPEC_MARKER_RESERVE_BYTES = 128; /** Below this many bytes left for spec we skip reading and emit only a marker. */ const MIN_USEFUL_SPEC_BYTES = 256; -const USAGE = "Usage: /sf eval-review <sliceId> [--force] [--show] (e.g. S07)"; +const USAGE = "Usage: /eval-review <sliceId> [--force] [--show] (e.g. S07)"; // ─── Argument parsing ───────────────────────────────────────────────────────── /** * Typed error thrown by {@link parseEvalReviewArgs} on argument validation @@ -134,7 +134,7 @@ export function parseEvalReviewArgs(raw) { * - `no-slice-dir` → likely a typo in the slice ID, milestone exists but * slice does not. * - `no-summary` → slice exists but `SUMMARY.md` is missing; the user - * probably skipped `/sf execute-phase`. + * probably skipped `/execute-phase`. * - `ready` → audit can run. * * AI-SPEC.md is optional in every state where the slice directory exists — @@ -483,7 +483,7 @@ export function planEvalReviewAction(args, detected, existingPath) { } // ─── Handler entry ──────────────────────────────────────────────────────────── /** - * Handle `/sf eval-review <sliceId> [--force] [--show]`. + * Handle `/eval-review <sliceId> [--force] [--show]`. * * Workflow: * 1. Parse and validate args (path-traversal-safe). @@ -519,7 +519,7 @@ export async function handleEvalReview(args, ctx, pi) { const state = await deriveState(basePath); if (!state.activeMilestone) { ctx.ui.notify( - "No active milestone — start or resume one before running /sf eval-review.", + "No active milestone — start or resume one before running /eval-review.", "warning", ); return; @@ -541,7 +541,7 @@ export async function handleEvalReview(args, ctx, pi) { if (action.kind === "show") { if (!action.path) { ctx.ui.notify( - `No EVAL-REVIEW.md present for ${parsed.sliceId}. Run /sf eval-review ${parsed.sliceId} to generate one.`, + `No EVAL-REVIEW.md present for ${parsed.sliceId}. Run /eval-review ${parsed.sliceId} to generate one.`, "warning", ); return; @@ -560,7 +560,7 @@ export async function handleEvalReview(args, ctx, pi) { } if (action.kind === "no-summary") { ctx.ui.notify( - `Slice ${parsed.sliceId} exists but has no SUMMARY.md — run /sf execute-phase first to generate one.`, + `Slice ${parsed.sliceId} exists but has no SUMMARY.md — run /execute-phase first to generate one.`, "warning", ); return; diff --git a/src/resources/extensions/sf/commands-extensions.js b/src/resources/extensions/sf/commands-extensions.js index 2ed890617..1a6992485 100644 --- a/src/resources/extensions/sf/commands-extensions.js +++ b/src/resources/extensions/sf/commands-extensions.js @@ -1,5 +1,5 @@ /** - * SF Extensions Command — /sf extensions + * SF Extensions Command — /extensions * * Manage the extension registry: list, enable, disable, info. * Self-contained — no imports outside the extensions tree (extensions are loaded @@ -105,7 +105,7 @@ function discoverManifests() { } // ─── Command Handler ──────────────────────────────────────────────────────── /** - * Handler for /sf extensions subcommands (list, enable, disable, info). + * Handler for /extensions subcommands (list, enable, disable, info). */ export async function handleExtensions(args, ctx) { const parts = args.split(/\s+/).filter(Boolean); @@ -127,7 +127,7 @@ export async function handleExtensions(args, ctx) { return; } ctx.ui.notify( - `Unknown: /sf extensions ${subCmd}. Usage: /sf extensions [list|enable|disable|info]`, + `Unknown: /extensions ${subCmd}. Usage: /extensions [list|enable|disable|info]`, "warning", ); } @@ -180,13 +180,13 @@ function handleList(ctx) { */ function handleEnable(id, ctx) { if (!id) { - ctx.ui.notify("Usage: /sf extensions enable <id>", "warning"); + ctx.ui.notify("Usage: /extensions enable <id>", "warning"); return; } const manifests = discoverManifests(); if (!manifests.has(id)) { ctx.ui.notify( - `Extension "${id}" not found. Run /sf extensions list to see available extensions.`, + `Extension "${id}" not found. Run /extensions list to see available extensions.`, "warning", ); return; @@ -209,14 +209,14 @@ function handleEnable(id, ctx) { } function handleDisable(id, reason, ctx) { if (!id) { - ctx.ui.notify("Usage: /sf extensions disable <id>", "warning"); + ctx.ui.notify("Usage: /extensions disable <id>", "warning"); return; } const manifests = discoverManifests(); const manifest = manifests.get(id) ?? null; if (!manifests.has(id)) { ctx.ui.notify( - `Extension "${id}" not found. Run /sf extensions list to see available extensions.`, + `Extension "${id}" not found. Run /extensions list to see available extensions.`, "warning", ); return; @@ -252,7 +252,7 @@ function handleDisable(id, reason, ctx) { } function handleInfo(id, ctx) { if (!id) { - ctx.ui.notify("Usage: /sf extensions info <id>", "warning"); + ctx.ui.notify("Usage: /extensions info <id>", "warning"); return; } const manifests = discoverManifests(); diff --git a/src/resources/extensions/sf/commands-extract-learnings.js b/src/resources/extensions/sf/commands-extract-learnings.js index 14cfad3cc..2e572633d 100644 --- a/src/resources/extensions/sf/commands-extract-learnings.js +++ b/src/resources/extensions/sf/commands-extract-learnings.js @@ -1,5 +1,5 @@ /** - * SF Command — /sf extract-learnings + * SF Command — /extract-learnings * * Analyses completed milestone artefacts and dispatches an LLM turn that * extracts structured knowledge into 4 categories: @@ -171,7 +171,7 @@ export async function handleExtractLearnings(args, ctx, pi) { const { milestoneId } = parseExtractLearningsArgs(args); if (!milestoneId) { ctx.ui.notify( - "Usage: /sf extract-learnings <milestoneId> (e.g. M001)", + "Usage: /extract-learnings <milestoneId> (e.g. M001)", "warning", ); return; @@ -235,14 +235,14 @@ export async function handleExtractLearnings(args, ctx, pi) { } /** * Canonical structured-extraction instructions, shared by the manual - * `/sf extract-learnings` path and the autonomous mode complete-milestone turn. + * `/extract-learnings` path and the autonomous mode complete-milestone turn. */ export function buildExtractionStepsBlock(ctx) { return `## Structured Learnings Extraction Perform the following steps IN ORDER. Each step is mandatory unless explicitly marked optional. These instructions are the single source of truth shared by -\`/sf extract-learnings\` and the autonomous mode milestone-completion turn. +\`/extract-learnings\` and the autonomous mode milestone-completion turn. ### Step 1 — Classify findings into four categories diff --git a/src/resources/extensions/sf/commands-handlers.js b/src/resources/extensions/sf/commands-handlers.js index 77b0f2be4..8c54713dd 100644 --- a/src/resources/extensions/sf/commands-handlers.js +++ b/src/resources/extensions/sf/commands-handlers.js @@ -259,7 +259,7 @@ export async function handleSkillHealth(args, ctx) { formatSkillDetail, } = await import("./skill-health.js"); const basePath = projectRoot(); - // /sf skill-health <skill-name> — detail view + // /skill-health <skill-name> — detail view if (args && !args.startsWith("--")) { const detail = formatSkillDetail(basePath, args); ctx.ui.notify(detail, "info"); @@ -288,7 +288,7 @@ export async function handleCapture(args, ctx) { // Strip surrounding quotes from the argument let text = args.trim(); if (!text) { - ctx.ui.notify('Usage: /sf capture "your thought here"', "warning"); + ctx.ui.notify('Usage: /capture "your thought here"', "warning"); return; } // Remove wrapping quotes (single or double) @@ -299,7 +299,7 @@ export async function handleCapture(args, ctx) { text = text.slice(1, -1); } if (!text) { - ctx.ui.notify('Usage: /sf capture "your thought here"', "warning"); + ctx.ui.notify('Usage: /capture "your thought here"', "warning"); return; } const basePath = process.cwd(); @@ -474,14 +474,14 @@ export async function handleKnowledge(args, ctx) { const typeArg = parts[0]?.toLowerCase(); if (!typeArg || !["rule", "pattern", "lesson"].includes(typeArg)) { ctx.ui.notify( - "Usage: /sf knowledge <rule|pattern|lesson> <description>\nExample: /sf knowledge rule Use real DB for integration tests", + "Usage: /knowledge <rule|pattern|lesson> <description>\nExample: /knowledge rule Use real DB for integration tests", "warning", ); return; } const entryText = parts.slice(1).join(" ").trim(); if (!entryText) { - ctx.ui.notify(`Usage: /sf knowledge ${typeArg} <description>`, "warning"); + ctx.ui.notify(`Usage: /knowledge ${typeArg} <description>`, "warning"); return; } const type = typeArg; @@ -497,7 +497,7 @@ export async function handleRunHook(args, ctx, pi) { const parts = args.trim().split(/\s+/); if (parts.length < 3) { ctx.ui.notify( - `Usage: /sf run-hook <hook-name> <unit-type> <unit-id> + `Usage: /run-hook <hook-name> <unit-type> <unit-id> Unit types: execute-task - Task execution (unit-id: M001/S01/T01) @@ -507,8 +507,8 @@ Unit types: complete-milestone - Milestone completion (unit-id: M001) Examples: - /sf run-hook code-review execute-task M001/S01/T01 - /sf run-hook lint-check plan-slice M001/S01`, + /run-hook code-review execute-task M001/S01/T01 + /run-hook lint-check plan-slice M001/S01`, "warning", ); return; @@ -627,7 +627,7 @@ export async function handleUpdate(ctx, deps = {}) { ? reloadError.message : String(reloadError); ctx.ui.notify( - `Updated to v${latest}, but automatic reload failed: ${message}. Use /sf reload to resume with the new version.`, + `Updated to v${latest}, but automatic reload failed: ${message}. Use /reload to resume with the new version.`, "warning", ); } diff --git a/src/resources/extensions/sf/commands-harness.js b/src/resources/extensions/sf/commands-harness.js index 5398b7767..4b97fd34c 100644 --- a/src/resources/extensions/sf/commands-harness.js +++ b/src/resources/extensions/sf/commands-harness.js @@ -46,7 +46,7 @@ function formatProfileSummary(profile) { "Runtime observation boundary:", "- Profile state was stored only in .sf runtime state.", "- No repo-committable artifact was written by profiling.", - "- Use /sf harness promote <finding-id> after review to create a tracked docs artifact.", + "- Use /harness promote <finding-id> after review to create a tracked docs artifact.", "- Untracked files remain observed_only; SF did not stage or adopt them.", ].join("\n"); } @@ -56,7 +56,7 @@ function formatProfileSummary(profile) { * Purpose: keep promotion artifacts deterministic while preventing path * traversal through user-provided finding IDs. * - * Consumer: `/sf harness promote <finding-id>`. + * Consumer: `/harness promote <finding-id>`. */ function findingIdSlug(findingId) { const slug = findingId @@ -73,7 +73,7 @@ function findingIdSlug(findingId) { * Purpose: promotion must be a writeback from recorded observations, not a new * profiler run that can observe its own artifact or introduce timestamps. * - * Consumer: `/sf harness promote <finding-id>`. + * Consumer: `/harness promote <finding-id>`. */ function parseRecordedProfile(profileJson) { try { @@ -97,7 +97,7 @@ function parseRecordedProfile(profileJson) { * Purpose: document the recorded observation facts without leaking absolute * runtime paths or adding promotion-time fields. * - * Consumer: `/sf harness promote <finding-id>`. + * Consumer: `/harness promote <finding-id>`. */ function profilePromotionPayload(profile, fallback) { return { @@ -124,18 +124,18 @@ function profilePromotionPayload(profile, fallback) { * Purpose: satisfy AC1 of sf-moocr4rv-au7r3l — harness findings must be * promotable into tracked docs with deterministic path and content. * - * Consumer: `/sf harness promote <finding-id>` command. + * Consumer: `/harness promote <finding-id>` command. */ export async function handleHarnessPromote(findingId, ctx) { const basePath = projectRoot(); const opened = await ensureDbOpen(basePath); if (!opened) { - ctx.ui.notify("No SF database available. Run /sf init first.", "warning"); + ctx.ui.notify("No SF database available. Run /init first.", "warning"); return; } if (!findingId || findingId.trim().length === 0) { ctx.ui.notify( - "Usage: /sf harness promote <finding-id>\nPromotes a harness observation to a tracked docs artifact.", + "Usage: /harness promote <finding-id>\nPromotes a harness observation to a tracked docs artifact.", "warning", ); return; @@ -144,7 +144,7 @@ export async function handleHarnessPromote(findingId, ctx) { const latestProfile = getLatestRepoProfile(); if (!latestProfile) { ctx.ui.notify( - "No recorded harness profile found. Run /sf harness profile first; promotion writes tracked docs only from .sf runtime observations.", + "No recorded harness profile found. Run /harness profile first; promotion writes tracked docs only from .sf runtime observations.", "warning", ); return; @@ -215,7 +215,7 @@ export async function handleHarnessPromote(findingId, ctx) { * Purpose: give users and future auto-flow slices an explicit entry point for * harness evolution's read-only observation phase. * - * Consumer: `/sf harness profile` command. + * Consumer: `/harness profile` command. */ export async function handleHarness(args, ctx) { const subcommand = args.trim() || "profile"; @@ -226,7 +226,7 @@ export async function handleHarness(args, ctx) { } if (!["profile", "snapshot", "status"].includes(subcommand)) { ctx.ui.notify( - "Usage: /sf harness profile | /sf harness promote <finding-id>\nRecords a read-only .sf runtime profile or promotes a reviewed finding to tracked docs.", + "Usage: /harness profile | /harness promote <finding-id>\nRecords a read-only .sf runtime profile or promotes a reviewed finding to tracked docs.", "warning", ); return; @@ -234,7 +234,7 @@ export async function handleHarness(args, ctx) { const basePath = projectRoot(); const opened = await ensureDbOpen(basePath); if (!opened) { - ctx.ui.notify("No SF database available. Run /sf init first.", "warning"); + ctx.ui.notify("No SF database available. Run /init first.", "warning"); return; } const profile = profileRepository(basePath); diff --git a/src/resources/extensions/sf/commands-inspect.js b/src/resources/extensions/sf/commands-inspect.js index 81a37dd8b..e3dfaebc7 100644 --- a/src/resources/extensions/sf/commands-inspect.js +++ b/src/resources/extensions/sf/commands-inspect.js @@ -35,7 +35,7 @@ export async function handleInspect(ctx) { const { isDbAvailable, _getAdapter } = await import("./sf-db.js"); if (!(await ensureDbOpen(process.cwd())) || !isDbAvailable()) { ctx.ui.notify( - "No SF database available. Run /sf autonomous to create one.", + "No SF database available. Run /autonomous to create one.", "info", ); return; @@ -43,7 +43,7 @@ export async function handleInspect(ctx) { const adapter = _getAdapter(); if (!adapter) { ctx.ui.notify( - "No SF database available. Run /sf autonomous to create one.", + "No SF database available. Run /autonomous to create one.", "info", ); return; @@ -83,7 +83,7 @@ export async function handleInspect(ctx) { }; ctx.ui.notify(formatInspectOutput(data), "info"); } catch (err) { - logWarning("command", `/sf inspect failed: ${getErrorMessage(err)}`); + logWarning("command", `/inspect failed: ${getErrorMessage(err)}`); ctx.ui.notify( "Failed to inspect SF database. Check stderr for details.", "error", diff --git a/src/resources/extensions/sf/commands-logs.js b/src/resources/extensions/sf/commands-logs.js index bbb6f0abd..388a5552a 100644 --- a/src/resources/extensions/sf/commands-logs.js +++ b/src/resources/extensions/sf/commands-logs.js @@ -1,13 +1,13 @@ /** - * /sf logs — Browse activity logs, debug logs, and metrics. + * /logs — Browse activity logs, debug logs, and metrics. * * Subcommands: - * /sf logs — List recent activity + debug logs - * /sf logs <N> — Show summary of activity log #N - * /sf logs debug — List debug log files - * /sf logs debug <N> — Show debug log summary #N - * /sf logs tail [N] — Show last N activity log entries (default 5) - * /sf logs clear — Remove old activity and debug logs + * /logs — List recent activity + debug logs + * /logs <N> — Show summary of activity log #N + * /logs debug — List debug log files + * /logs debug <N> — Show debug log summary #N + * /logs tail [N] — Show last N activity log entries (default 5) + * /logs clear — Remove old activity and debug logs */ import { existsSync, @@ -244,35 +244,35 @@ export async function handleLogs(args, ctx) { const basePath = process.cwd(); const parts = args.trim().split(/\s+/).filter(Boolean); const subCmd = parts[0] ?? ""; - // /sf logs clear + // /logs clear if (subCmd === "clear") { await handleLogsClear(basePath, ctx); return; } - // /sf logs debug [N] + // /logs debug [N] if (subCmd === "debug") { const idx = parts[1] ? parseInt(parts[1], 10) : undefined; await handleLogsDebug(basePath, ctx, idx); return; } - // /sf logs tail [N] + // /logs tail [N] if (subCmd === "tail") { const count = parts[1] ? parseInt(parts[1], 10) : 5; await handleLogsTail(basePath, ctx, count); return; } - // /sf logs current — show active unit from auto.lock + // /logs current — show active unit from auto.lock if (subCmd === "current") { await handleLogsCurrent(basePath, ctx); return; } - // /sf logs <N> — show specific activity log + // /logs <N> — show specific activity log if (subCmd && /^\d+$/.test(subCmd)) { const seq = parseInt(subCmd, 10); await handleLogsShow(basePath, ctx, seq); return; } - // /sf logs — list overview + // /logs — list overview await handleLogsList(basePath, ctx); } // ─── Subcommand Handlers ──────────────────────────────────────────────────── @@ -305,8 +305,8 @@ async function handleLogsList(basePath, ctx) { lines.push(` ... and ${activities.length - 15} older entries`); } lines.push(""); - lines.push(" View details: /sf logs <#>"); - lines.push(" Active unit: /sf logs current"); + lines.push(" View details: /logs <#>"); + lines.push(" Active unit: /logs current"); } if (debugLogs.length > 0) { lines.push(""); @@ -318,7 +318,7 @@ async function handleLogsList(basePath, ctx) { lines.push(` ${i + 1}. ${d.filename} ${size} ${age}`); } lines.push(""); - lines.push(" View details: /sf logs debug <#>"); + lines.push(" View details: /logs debug <#>"); } // Metrics summary const metricsPath = join(sfRoot(basePath), "metrics.json"); @@ -341,7 +341,7 @@ async function handleLogsList(basePath, ctx) { ); } lines.push(""); - lines.push("Tip: Enable debug logging with SF_DEBUG=1 before /sf autonomous"); + lines.push("Tip: Enable debug logging with SF_DEBUG=1 before /autonomous"); ctx.ui.notify(lines.join("\n"), "info"); } async function handleLogsShow(basePath, ctx, seq) { @@ -349,7 +349,7 @@ async function handleLogsShow(basePath, ctx, seq) { const entry = activities.find((e) => e.seq === seq); if (!entry) { ctx.ui.notify( - `Activity log #${seq} not found. Run /sf logs to see available logs.`, + `Activity log #${seq} not found. Run /logs to see available logs.`, "warning", ); return; @@ -421,7 +421,7 @@ async function handleLogsDebug(basePath, ctx, idx) { ); } lines.push(""); - lines.push("View details: /sf logs debug <#>"); + lines.push("View details: /logs debug <#>"); ctx.ui.notify(lines.join("\n"), "info"); return; } @@ -571,7 +571,7 @@ async function handleLogsCurrent(basePath, ctx) { ); if (!sessionExists) { lines.push( - "Recommendation: Check .sf/runtime/ for error markers or run /sf doctor.", + "Recommendation: Check .sf/runtime/ for error markers or run /doctor.", ); } } diff --git a/src/resources/extensions/sf/commands-maintenance.js b/src/resources/extensions/sf/commands-maintenance.js index 84bc7f711..ed7c36687 100644 --- a/src/resources/extensions/sf/commands-maintenance.js +++ b/src/resources/extensions/sf/commands-maintenance.js @@ -147,7 +147,7 @@ export async function handleCleanupBranches(ctx, basePath) { export async function handleCleanupSnapshots(ctx, basePath) { let refs; try { - refs = nativeForEachRef(basePath, "refs/sf/snapshots/"); + refs = nativeForEachRef(basePath, "refs/next/snapshots/"); } catch (e) { logWarning("command", `snapshot ref list failed: ${e.message}`); ctx.ui.notify("No snapshot refs to clean up.", "info"); @@ -270,7 +270,7 @@ export async function handleCleanupWorktrees(ctx, basePath) { export async function handleSkip(unitArg, ctx, basePath) { if (!unitArg) { ctx.ui.notify( - "Usage: /sf skip <unit-id> (e.g., /sf skip execute-task/M001/S01/T03 or /sf skip T03)", + "Usage: /skip <unit-id> (e.g., /skip execute-task/M001/S01/T03 or /skip T03)", "info", ); return; @@ -509,7 +509,7 @@ export async function handleCleanupProjects(args, ctx) { } if (!fix && orphaned.length > 0) { lines.push( - `Run /sf cleanup projects --fix to permanently delete ${pl(orphaned.length, "orphaned director")}${orphaned.length === 1 ? "y" : "ies"}.`, + `Run /cleanup projects --fix to permanently delete ${pl(orphaned.length, "orphaned director")}${orphaned.length === 1 ? "y" : "ies"}.`, ); ctx.ui.notify(lines.join("\n"), "warning"); return; diff --git a/src/resources/extensions/sf/commands-mcp-status.js b/src/resources/extensions/sf/commands-mcp-status.js index 4f90f68f5..e533a62a7 100644 --- a/src/resources/extensions/sf/commands-mcp-status.js +++ b/src/resources/extensions/sf/commands-mcp-status.js @@ -1,12 +1,12 @@ /** - * MCP Status — `/sf mcp` command handler. + * MCP Status — `/mcp` command handler. * * Shows configured MCP servers, their connection status, and available tools. * * Subcommands: - * /sf mcp — Overview of all servers (alias: /sf mcp status) - * /sf mcp status — Same as bare /sf mcp - * /sf mcp check <srv> — Detailed status for a specific server + * /mcp — Overview of all servers (alias: /mcp status) + * /mcp status — Same as bare /mcp + * /mcp check <srv> — Detailed status for a specific server */ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; @@ -69,7 +69,7 @@ export function formatMcpStatusReport(servers) { lines.push(` ${icon} ${s.name} (${s.transport}) — ${status}`); } lines.push(""); - lines.push("Use /sf mcp check <server> for details on a specific server."); + lines.push("Use /mcp check <server> for details on a specific server."); lines.push("Use mcp_discover to connect and list tools for a server."); return lines.join("\n"); } @@ -100,7 +100,7 @@ export function formatMcpServerDetail(server) { } // ─── Command handler ──────────────────────────────────────────────────────── /** - * Handle `/sf mcp [status|check <server>]`. + * Handle `/mcp [status|check <server>]`. */ export async function handleMcpStatus(args, ctx) { const trimmed = args.trim(); @@ -115,7 +115,7 @@ export async function handleMcpStatus(args, ctx) { ); return; } - // /sf mcp check <server> + // /mcp check <server> if (lowered.startsWith("check ")) { const serverName = trimmed.slice("check ".length).trim(); const config = configs.find((c) => c.name === serverName); @@ -157,7 +157,7 @@ export async function handleMcpStatus(args, ctx) { ); return; } - // /sf mcp or /sf mcp status + // /mcp or /mcp status if (!lowered || lowered === "status") { // Build status for each server const statuses = []; @@ -190,7 +190,7 @@ export async function handleMcpStatus(args, ctx) { } // Unknown subcommand ctx.ui.notify( - "Usage: /sf mcp [status|check <server>]\n\n" + + "Usage: /mcp [status|check <server>]\n\n" + " status Show all MCP server statuses (default)\n" + " check <server> Detailed status for a specific server", "warning", diff --git a/src/resources/extensions/sf/commands-memory.js b/src/resources/extensions/sf/commands-memory.js index b36f57cda..ad27d52fe 100644 --- a/src/resources/extensions/sf/commands-memory.js +++ b/src/resources/extensions/sf/commands-memory.js @@ -1,5 +1,5 @@ /** - * SF Command — `/sf memory` + * SF Command — `/memory` * * Subcommands: * list — show recent active memories @@ -97,7 +97,7 @@ function truncate(text, max) { // ─── Handler ──────────────────────────────────────────────────────────────── export async function handleMemory(args, ctx, pi) { const parsed = parseArgs(args); - // `/sf memory` or `/sf memory help` + // `/memory` or `/memory help` if (parsed.sub === "" || parsed.sub === "help") { ctx.ui.notify(usage(), "info"); return; @@ -160,7 +160,7 @@ export async function handleMemory(args, ctx, pi) { } function usage() { return [ - "Usage: /sf memory <subcommand>", + "Usage: /memory <subcommand>", " list list recent active memories", ' search "<query>" embedding-ranked search (gateway-aware; static fallback)', " show <MEM###> print one memory", @@ -208,7 +208,7 @@ async function handleSearch(ctx, parsed) { const query = parsed.positional.join(" ").trim(); if (!query) { ctx.ui.notify( - 'Usage: /sf memory search "<query>" (uses embeddings when SF_LLM_GATEWAY_KEY is set; static fallback otherwise)', + 'Usage: /memory search "<query>" (uses embeddings when SF_LLM_GATEWAY_KEY is set; static fallback otherwise)', "warning", ); return; @@ -240,7 +240,7 @@ async function handleSearch(ctx, parsed) { } function handleShow(ctx, id) { if (!id) { - ctx.ui.notify("Usage: /sf memory show <MEM###>", "warning"); + ctx.ui.notify("Usage: /memory show <MEM###>", "warning"); return; } const adapter = _getAdapter(); @@ -277,7 +277,7 @@ function handleShow(ctx, id) { } function handleForget(ctx, id) { if (!id) { - ctx.ui.notify("Usage: /sf memory forget <MEM###>", "warning"); + ctx.ui.notify("Usage: /memory forget <MEM###>", "warning"); return; } const ok = supersedeMemory(id, "CAP_EXCEEDED"); @@ -542,7 +542,7 @@ function sanitizeProbeError(error) { } function handleExport(ctx, target) { if (!target) { - ctx.ui.notify("Usage: /sf memory export <path.json>", "warning"); + ctx.ui.notify("Usage: /memory export <path.json>", "warning"); return; } try { @@ -585,7 +585,7 @@ function handleExport(ctx, target) { } function handleImport(ctx, target) { if (!target) { - ctx.ui.notify("Usage: /sf memory import <path.json>", "warning"); + ctx.ui.notify("Usage: /memory import <path.json>", "warning"); return; } try { @@ -624,7 +624,7 @@ function handleDecay(ctx) { function handleCap(ctx, arg) { const max = arg ? Number.parseInt(arg, 10) : 50; if (!Number.isFinite(max) || max < 1) { - ctx.ui.notify("Usage: /sf memory cap <max> (default 50)", "warning"); + ctx.ui.notify("Usage: /memory cap <max> (default 50)", "warning"); return; } enforceMemoryCap(max); @@ -634,7 +634,7 @@ function handleSources(ctx) { const sources = listMemorySources(30); if (sources.length === 0) { ctx.ui.notify( - "No memory sources yet. Use `/sf memory ingest <path|url>` to add one.", + "No memory sources yet. Use `/memory ingest <path|url>` to add one.", "info", ); return; @@ -648,7 +648,7 @@ function handleSources(ctx) { async function handleNote(ctx, args) { const text = args.positional.join(" ").trim(); if (!text) { - ctx.ui.notify('Usage: /sf memory note "your note"', "warning"); + ctx.ui.notify('Usage: /memory note "your note"', "warning"); return; } try { @@ -666,7 +666,7 @@ async function handleIngest(ctx, args) { const target = args.positional[0]; if (!target) { ctx.ui.notify( - "Usage: /sf memory ingest <path|url> [--tag a,b] [--scope project|global]", + "Usage: /memory ingest <path|url> [--tag a,b] [--scope project|global]", "warning", ); return; @@ -687,7 +687,7 @@ async function handleIngest(ctx, args) { ctx.ui.notify(summarizeIngest(result), "info"); if (args.extract && result.sourceId) { ctx.ui.notify( - `(Use \`/sf memory extract ${result.sourceId}\` to trigger extraction manually.)`, + `(Use \`/memory extract ${result.sourceId}\` to trigger extraction manually.)`, "info", ); } @@ -697,7 +697,7 @@ async function handleIngest(ctx, args) { } function handleExtractSource(ctx, pi, id) { if (!id) { - ctx.ui.notify("Usage: /sf memory extract <SRC-xxx>", "warning"); + ctx.ui.notify("Usage: /memory extract <SRC-xxx>", "warning"); return; } const source = getMemorySource(id); diff --git a/src/resources/extensions/sf/commands-plan.js b/src/resources/extensions/sf/commands-plan.js index 42da0ee82..e487933cf 100644 --- a/src/resources/extensions/sf/commands-plan.js +++ b/src/resources/extensions/sf/commands-plan.js @@ -5,7 +5,7 @@ * ~/.sf/projects/<hash>/ into the repo (promote), plus visibility (list) * and comparison (diff) companions. * - * Consumer: SF ops handler (commands/handlers/ops.js) via `/sf plan <subcmd>`. + * Consumer: SF ops handler (commands/handlers/ops.js) via `/plan <subcmd>`. */ import { spawnSync } from "node:child_process"; @@ -202,7 +202,7 @@ export async function handlePlanPromote(args, ctx) { if (!source) { ctx.ui.notify( - "Usage: /sf plan promote <source> [--to <dir>] [--rename <name>] [--edit]", + "Usage: /plan promote <source> [--to <dir>] [--rename <name>] [--edit]", "warning", ); return; @@ -321,7 +321,7 @@ export async function handlePlanList(_args, ctx) { export async function handlePlanDiff(args, ctx) { const source = args.trim(); if (!source) { - ctx.ui.notify("Usage: /sf plan diff <source>", "warning"); + ctx.ui.notify("Usage: /plan diff <source>", "warning"); return; } @@ -392,7 +392,7 @@ export async function handlePlanDiff(args, ctx) { * Purpose: make docs/specs reproducible human exports from SF's DB-first * working model instead of unmanaged snapshots. * - * Consumer: handlePlan router for `/sf plan specs ...`. + * Consumer: handlePlan router for `/plan specs ...`. */ export async function handlePlanSpecs(args, ctx) { const subcmd = args.trim() || "diff"; @@ -448,7 +448,7 @@ export async function handlePlanSpecs(args, ctx) { return { ok: true, changed }; } ctx.ui.notify( - `Spec exports are stale:\n${changed.map((p) => ` ${p}`).join("\n")}\nRun /sf plan specs generate.`, + `Spec exports are stale:\n${changed.map((p) => ` ${p}`).join("\n")}\nRun /plan specs generate.`, "error", ); return { ok: false, changed }; @@ -464,7 +464,7 @@ export async function handlePlanSpecs(args, ctx) { return { ok: changed.length === 0, changed }; } - ctx.ui.notify("Usage: /sf plan specs generate|diff|check", "warning"); + ctx.ui.notify("Usage: /plan specs generate|diff|check", "warning"); return { ok: false, changed: [] }; } @@ -489,7 +489,7 @@ export async function handlePlan(args, ctx) { return true; } if (trimmed === "") { - ctx.ui.notify("Usage: /sf plan promote|list|diff|specs ...", "info"); + ctx.ui.notify("Usage: /plan promote|list|diff|specs ...", "info"); return true; } return false; diff --git a/src/resources/extensions/sf/commands-pr-branch.js b/src/resources/extensions/sf/commands-pr-branch.js index 08f32aaf7..edfb7be74 100644 --- a/src/resources/extensions/sf/commands-pr-branch.js +++ b/src/resources/extensions/sf/commands-pr-branch.js @@ -1,5 +1,5 @@ /** - * SF Command — /sf pr-branch + * SF Command — /pr-branch * * Creates a clean PR branch by cherry-picking commits while stripping * any changes to .sf/, .planning/, and PLAN.md paths. Useful for diff --git a/src/resources/extensions/sf/commands-prefs-wizard.js b/src/resources/extensions/sf/commands-prefs-wizard.js index 183831ad9..73ac1764a 100644 --- a/src/resources/extensions/sf/commands-prefs-wizard.js +++ b/src/resources/extensions/sf/commands-prefs-wizard.js @@ -115,7 +115,7 @@ export async function handlePrefs(args, ctx) { return; } ctx.ui.notify( - "Usage: /sf prefs [global|project|status|wizard|setup|import-claude [global|project]]", + "Usage: /prefs [global|project|status|wizard|setup|import-claude [global|project]]", "info", ); } diff --git a/src/resources/extensions/sf/commands-rate.js b/src/resources/extensions/sf/commands-rate.js index b511fcdb8..9b3c279c5 100644 --- a/src/resources/extensions/sf/commands-rate.js +++ b/src/resources/extensions/sf/commands-rate.js @@ -1,5 +1,5 @@ /** - * /sf rate — Submit feedback on the last unit's model tier assignment. + * /rate — Submit feedback on the last unit's model tier assignment. * Feeds into the adaptive routing history so future dispatches improve. */ import { loadLedgerFromDisk } from "./metrics.js"; @@ -10,7 +10,7 @@ export async function handleRate(args, ctx, basePath) { const rating = args.trim().toLowerCase(); if (!rating || !VALID_RATINGS.has(rating)) { ctx.ui.notify( - "Usage: /sf rate <over|ok|under>\n" + + "Usage: /rate <over|ok|under>\n" + " over — model was overpowered for that task (encourage cheaper)\n" + " ok — model was appropriate\n" + " under — model was too weak (encourage stronger)", diff --git a/src/resources/extensions/sf/commands-scaffold-sync.js b/src/resources/extensions/sf/commands-scaffold-sync.js index 90f1a2b4d..5042c548d 100644 --- a/src/resources/extensions/sf/commands-scaffold-sync.js +++ b/src/resources/extensions/sf/commands-scaffold-sync.js @@ -1,5 +1,5 @@ /** - * commands-scaffold-sync.ts — `/sf scaffold sync` (ADR-021 Phase E). + * commands-scaffold-sync.ts — `/scaffold sync` (ADR-021 Phase E). * * Manual escape hatch over the Phase C automatic scaffold sync. Lets the user: * - Inspect drift without modifying anything (`--dry-run`). @@ -15,7 +15,7 @@ import { ensureAgenticDocsScaffold } from "./agentic-docs-scaffold.js"; import { projectRoot } from "./commands/context.js"; import { detectScaffoldDrift } from "./scaffold-drift.js"; -/** Parse the args string for `/sf scaffold sync`. Tolerates extra whitespace. */ +/** Parse the args string for `/scaffold sync`. Tolerates extra whitespace. */ export function parseScaffoldSyncArgs(args) { const trimmed = (args || "").trim(); const tokens = trimmed.length > 0 ? trimmed.split(/\s+/) : []; @@ -144,7 +144,7 @@ async function tryLoadScaffoldKeeper() { return null; } /** - * Top-level handler for `/sf scaffold sync [args]`. + * Top-level handler for `/scaffold sync [args]`. * * Always notifies via `ctx.ui.notify` — never throws on the sync paths * themselves; underlying calls (`ensureAgenticDocsScaffold`, diff --git a/src/resources/extensions/sf/commands-scan.js b/src/resources/extensions/sf/commands-scan.js index 181faf5ad..608eeee4f 100644 --- a/src/resources/extensions/sf/commands-scan.js +++ b/src/resources/extensions/sf/commands-scan.js @@ -1,17 +1,17 @@ /** - * SF Command — /sf scan + * SF Command — /scan * - * Rapid codebase assessment — lightweight alternative to /sf map-codebase. + * Rapid codebase assessment — lightweight alternative to /map-codebase. * Spawns one focused AI analysis pass and writes structured documents to * .sf/codebase/ for use by planning and execution phases. * * Usage: - * /sf scan — tech+arch focus (default) - * /sf scan --focus tech — technology stack + integrations only - * /sf scan --focus arch — architecture + structure only - * /sf scan --focus quality — conventions + testing patterns only - * /sf scan --focus concerns — technical debt + concerns only - * /sf scan --focus tech+arch — explicit default (same as no flag) + * /scan — tech+arch focus (default) + * /scan --focus tech — technology stack + integrations only + * /scan --focus arch — architecture + structure only + * /scan --focus quality — conventions + testing patterns only + * /scan --focus concerns — technical debt + concerns only + * /scan --focus tech+arch — explicit default (same as no flag) */ import { existsSync, mkdirSync } from "node:fs"; import { join, relative } from "node:path"; diff --git a/src/resources/extensions/sf/commands-schedule.js b/src/resources/extensions/sf/commands-schedule.js index 8aef54bae..6ca60beca 100644 --- a/src/resources/extensions/sf/commands-schedule.js +++ b/src/resources/extensions/sf/commands-schedule.js @@ -1,5 +1,5 @@ /** - * SF Command — /sf schedule + * SF Command — /schedule * * Schedule management: add, list, done, cancel, snooze, run. * Entries are stored in SQLite (`schedule_entries`). Legacy schedule JSONL is @@ -240,7 +240,7 @@ async function addItem(args, ctx) { } if (!dueAt) { ctx.ui.notify( - "Usage: /sf schedule add --in <duration> <title>\n /sf schedule add --at <ISO-date> <title>", + "Usage: /schedule add --in <duration> <title>\n /schedule add --at <ISO-date> <title>", "warning", ); return; @@ -250,7 +250,7 @@ async function addItem(args, ctx) { kind === "command" ? _commandFromParts(titleParts) : _joinPlain(titleParts); if (!title) { ctx.ui.notify( - "Missing title. Example: /sf schedule add --in 2w 'Review adoption metrics'", + "Missing title. Example: /schedule add --in 2w 'Review adoption metrics'", "warning", ); return; @@ -358,7 +358,7 @@ async function listItems(args, ctx) { async function markDone(args, ctx) { const idPrefix = _joinPlain(_splitArgs(args)); if (!idPrefix) { - ctx.ui.notify("Usage: /sf schedule done \u003cid\u003e", "warning"); + ctx.ui.notify("Usage: /schedule done \u003cid\u003e", "warning"); return; } const store = createScheduleStore(_basePath()); @@ -379,7 +379,7 @@ async function markDone(args, ctx) { async function markCancel(args, ctx) { const idPrefix = _joinPlain(_splitArgs(args)); if (!idPrefix) { - ctx.ui.notify("Usage: /sf schedule cancel \u003cid\u003e", "warning"); + ctx.ui.notify("Usage: /schedule cancel \u003cid\u003e", "warning"); return; } const store = createScheduleStore(_basePath()); @@ -412,7 +412,7 @@ async function snoozeItem(args, ctx) { if (!idPrefix || !by) { ctx.ui.notify( - "Usage: /sf schedule snooze \u003cid\u003e --by \u003cduration\u003e", + "Usage: /schedule snooze \u003cid\u003e --by \u003cduration\u003e", "warning", ); return; @@ -460,10 +460,7 @@ async function runItem(args, ctx) { if (!idPrefix) idPrefix = part; } if (!idPrefix) { - ctx.ui.notify( - "Usage: /sf schedule run [--dry-run] \u003cid\u003e", - "warning", - ); + ctx.ui.notify("Usage: /schedule run [--dry-run] \u003cid\u003e", "warning"); return; } const store = createScheduleStore(_basePath()); @@ -537,7 +534,7 @@ async function runItem(args, ctx) { // ─── Public handler ───────────────────────────────────────────────────────── /** - * Handle /sf schedule subcommands. + * Handle /schedule subcommands. * * Purpose: route schedule CLI input to the appropriate subcommand. * @@ -566,7 +563,7 @@ export async function handleSchedule(args, ctx) { return runItem(rest, ctx); case "": ctx.ui.notify( - "Usage: /sf schedule add|list|done|cancel|snooze|run\n" + + "Usage: /schedule add|list|done|cancel|snooze|run\n" + " add --in \u003cduration\u003e [--kind \u003ckind\u003e] [--scope \u003cscope\u003e] [--autonomous-dispatch] \u003ctitle-or-command\u003e\n" + " list [--due] [--all] [--json] [--scope \u003cscope\u003e]\n" + " done \u003cid\u003e\n" + @@ -578,7 +575,7 @@ export async function handleSchedule(args, ctx) { return; default: ctx.ui.notify( - `Unknown schedule subcommand: ${sub}. Use /sf schedule for usage.`, + `Unknown schedule subcommand: ${sub}. Use /schedule for usage.`, "warning", ); } diff --git a/src/resources/extensions/sf/commands-session-report.js b/src/resources/extensions/sf/commands-session-report.js index 89d2bfcc4..a3e69c9ed 100644 --- a/src/resources/extensions/sf/commands-session-report.js +++ b/src/resources/extensions/sf/commands-session-report.js @@ -1,5 +1,5 @@ /** - * SF Command — /sf session-report + * SF Command — /session-report * * Summarizes the current session: tasks completed, cost, tokens, * duration, model usage breakdown. diff --git a/src/resources/extensions/sf/commands-ship.js b/src/resources/extensions/sf/commands-ship.js index c2c395f0e..a43cf64b7 100644 --- a/src/resources/extensions/sf/commands-ship.js +++ b/src/resources/extensions/sf/commands-ship.js @@ -1,5 +1,5 @@ /** - * SF Command — /sf ship + * SF Command — /ship * * Creates a PR from milestone artifacts: generates title + body from * roadmap, slice summaries, and metrics, then opens via `gh pr create`. diff --git a/src/resources/extensions/sf/commands-todo.js b/src/resources/extensions/sf/commands-todo.js index ec0a5009c..a2fbd7872 100644 --- a/src/resources/extensions/sf/commands-todo.js +++ b/src/resources/extensions/sf/commands-todo.js @@ -5,7 +5,7 @@ * docs, test, and implementation artifacts without treating raw notes as * approved runtime behavior. * - * Consumer: `/sf todo triage` command. + * Consumer: `/todo triage` command. */ import { createHash } from "node:crypto"; @@ -603,7 +603,7 @@ export async function handleTodo(args, ctx, _pi) { const ci = parts.includes("--ci"); if (subcommand !== "triage") { ctx.ui.notify( - "Usage: /sf todo triage [--no-clear] [--backlog] [--ci]\nReads root TODO.md, writes .sf/triage artifacts, and clears processed dump notes by default.", + "Usage: /todo triage [--no-clear] [--backlog] [--ci]\nReads root TODO.md, writes .sf/triage artifacts, and clears processed dump notes by default.", "warning", ); return; diff --git a/src/resources/extensions/sf/commands-uok.js b/src/resources/extensions/sf/commands-uok.js index eb4e39234..63b35717f 100644 --- a/src/resources/extensions/sf/commands-uok.js +++ b/src/resources/extensions/sf/commands-uok.js @@ -207,7 +207,7 @@ export async function handleUok(args, ctx) { const trimmed = args.trim(); if (trimmed === "help" || trimmed === "--help") { ctx.ui.notify( - "Usage: /sf uok [status|metrics|circuit-breakers|gates|messages|--json]\n\n status — UOK ledger health, last run, last error, historical drift, startup gate, and gate health\n metrics — Render Prometheus-format metrics to .sf/runtime/uok-metrics.prom and display\n circuit-breakers — List all circuit breaker states and failure streaks\n gates — List observed gate runs and circuit breaker state\n messages — Show message bus status\n --json — Same as status but outputs JSON", + "Usage: /uok [status|metrics|circuit-breakers|gates|messages|--json]\n\n status — UOK ledger health, last run, last error, historical drift, startup gate, and gate health\n metrics — Render Prometheus-format metrics to .sf/runtime/uok-metrics.prom and display\n circuit-breakers — List all circuit breaker states and failure streaks\n gates — List observed gate runs and circuit breaker state\n messages — Show message bus status\n --json — Same as status but outputs JSON", "info", ); return; @@ -297,7 +297,7 @@ export async function handleUok(args, ctx) { lines.push(`Unique conversations: ${m.uniqueConversations}`); lines.push(""); lines.push( - "Tip: /sf uok messages compact — remove messages older than retention period", + "Tip: /uok messages compact — remove messages older than retention period", ); ctx.ui.notify(lines.join("\n"), "info"); return; diff --git a/src/resources/extensions/sf/commands-workflow-templates.js b/src/resources/extensions/sf/commands-workflow-templates.js index 1310b2f39..1b9f1db86 100644 --- a/src/resources/extensions/sf/commands-workflow-templates.js +++ b/src/resources/extensions/sf/commands-workflow-templates.js @@ -1,7 +1,7 @@ /** - * SF Workflow Template Commands — /sf start, /sf templates + * SF Workflow Template Commands — /start, /templates * - * Handles the `/sf start [template] [description]` and `/sf templates` commands. + * Handles the `/start [template] [description]` and `/templates` commands. * Resolves templates by name or auto-detection, then dispatches the workflow prompt. */ import { @@ -177,10 +177,10 @@ function findInProgressWorkflows(basePath) { results.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); return results; } -// ─── /sf start ────────────────────────────────────────────────────────────── +// ─── /start ────────────────────────────────────────────────────────────── export async function handleStart(args, ctx, pi) { const trimmed = args.trim(); - // /sf start --list → same as /sf templates + // /start --list → same as /templates if (trimmed === "--list" || trimmed === "list") { ctx.ui.notify(listTemplates(), "info"); return; @@ -191,7 +191,7 @@ export async function handleStart(args, ctx, pi) { if (isAutoActive()) { ctx.ui.notify( "Cannot start a workflow template while autonomous mode is running.\n" + - "Run /sf pause first, then /sf start.", + "Run /pause first, then /start.", "warning", ); return; @@ -199,12 +199,12 @@ export async function handleStart(args, ctx, pi) { if (isAutoPaused()) { ctx.ui.notify( "Autonomous mode is paused. Starting a workflow template will run independently.\n" + - "The paused autonomous session can be resumed later with /sf autonomous.", + "The paused autonomous session can be resumed later with /autonomous.", "info", ); } // ─── Resume detection ─────────────────────────────────────────────────── - // /sf start --resume or /sf start resume → resume in-progress workflow + // /start --resume or /start resume → resume in-progress workflow if (trimmed === "--resume" || trimmed === "resume") { const basePath = process.cwd(); const inProgress = findInProgressWorkflows(basePath); @@ -265,7 +265,7 @@ export async function handleStart(args, ctx, pi) { ); return; } - // Show in-progress workflows when /sf start is called with no args + // Show in-progress workflows when /start is called with no args if (!trimmed) { const basePath = process.cwd(); const inProgress = findInProgressWorkflows(basePath); @@ -279,12 +279,12 @@ export async function handleStart(args, ctx, pi) { `In-progress workflow found:\n` + ` ${wf.templateName}: "${wf.description}"\n` + ` Phase ${completedCount + 1}/${wf.phases.length}: ${activePhase?.name ?? "unknown"}\n\n` + - `Run /sf start resume to continue it.\n`, + `Run /start resume to continue it.\n`, "info", ); } } - // /sf start --dry-run <template> → preview without executing + // /start --dry-run <template> → preview without executing const dryRun = trimmed.includes("--dry-run"); const cleanedArgs = trimmed.replace(/--dry-run\s*/, "").trim(); // Parse: first word might be a template name, rest is description @@ -323,9 +323,9 @@ export async function handleStart(args, ctx, pi) { } else if (detected.length > 1) { const choices = detected .slice(0, 4) - .map((m) => ` /sf start ${m.id} ${cleanedArgs}`); + .map((m) => ` /start ${m.id} ${cleanedArgs}`); ctx.ui.notify( - `Multiple templates could match. Pick one:\n\n${choices.join("\n")}\n\nOr specify explicitly: /sf start <template> <description>`, + `Multiple templates could match. Pick one:\n\n${choices.join("\n")}\n\nOr specify explicitly: /start <template> <description>`, "info", ); return; @@ -337,7 +337,7 @@ export async function handleStart(args, ctx, pi) { ctx.ui.notify(formatStartUsage(), "info"); } else { ctx.ui.notify( - `No template matched "${firstWord}". Run /sf start to see available templates.`, + `No template matched "${firstWord}". Run /start to see available templates.`, "warning", ); } @@ -386,20 +386,20 @@ export async function handleStart(args, ctx, pi) { if (templateId === "full-project") { const root = sfRoot(basePath); if (!existsSync(root)) { - ctx.ui.notify("Routing to /sf init for full project setup...", "info"); - // Trigger /sf init by dispatching to the handler + ctx.ui.notify("Routing to /init for full project setup...", "info"); + // Trigger /init by dispatching to the handler pi.sendMessage( { customType: "sf-workflow-template", content: - "The user wants to start a full SF project. Run `/sf init` to bootstrap the project, then `/sf autonomous` to begin execution.", + "The user wants to start a full SF project. Run `/init` to bootstrap the project, then `/autonomous` to begin execution.", display: false, }, { triggerTurn: true }, ); } else { ctx.ui.notify( - "Project already initialized. Use `/sf autonomous` to continue or `/sf discuss` to start a new milestone.", + "Project already initialized. Use `/autonomous` to continue or `/discuss` to start a new milestone.", "info", ); } @@ -487,10 +487,10 @@ export async function handleStart(args, ctx, pi) { setActiveRunDir(runDir); startAutoDetached(ctx, pi, basePath, false); } -// ─── /sf templates ────────────────────────────────────────────────────────── +// ─── /templates ────────────────────────────────────────────────────────── export async function handleTemplates(args, ctx) { const trimmed = args.trim(); - // /sf templates info <name> + // /templates info <name> if (trimmed.startsWith("info ")) { const name = trimmed.replace(/^info\s+/, "").trim(); const info = getTemplateInfo(name); @@ -498,17 +498,17 @@ export async function handleTemplates(args, ctx) { ctx.ui.notify(info, "info"); } else { ctx.ui.notify( - `Unknown template "${name}". Run /sf templates to see available templates.`, + `Unknown template "${name}". Run /templates to see available templates.`, "warning", ); } return; } - // /sf templates — list all + // /templates — list all ctx.ui.notify(listTemplates(), "info"); } /** - * Return template IDs for autocomplete in /sf templates info <name>. + * Return template IDs for autocomplete in /templates info <name>. */ export function getTemplateCompletions(prefix) { try { diff --git a/src/resources/extensions/sf/commands-worktree.js b/src/resources/extensions/sf/commands-worktree.js index 447c1014c..a31bd7497 100644 --- a/src/resources/extensions/sf/commands-worktree.js +++ b/src/resources/extensions/sf/commands-worktree.js @@ -1,4 +1,4 @@ -// SF — In-TUI handler for /sf worktree commands (list, merge, clean, remove). +// SF — In-TUI handler for /worktree commands (list, merge, clean, remove). // // Mirrors the CLI subcommands but emits results via ctx.ui.notify() instead // of writing colored output to stderr. Reuses the same extension modules @@ -87,11 +87,11 @@ export function formatWorktreeList(statuses) { lines.push(""); } lines.push("Commands:"); - lines.push(" /sf worktree merge <name> Merge into main and clean up"); + lines.push(" /worktree merge <name> Merge into main and clean up"); lines.push( - " /sf worktree remove <name> Remove a worktree (--force to skip safety checks)", + " /worktree remove <name> Remove a worktree (--force to skip safety checks)", ); - lines.push(" /sf worktree clean Remove all merged/empty worktrees"); + lines.push(" /worktree clean Remove all merged/empty worktrees"); return lines.join("\n"); } export function formatCleanKeepReason(status) { @@ -125,7 +125,7 @@ async function handleMerge(args, ctx) { } else { const names = worktrees.map((w) => w.name).join(", "); ctx.ui.notify( - `Usage: /sf worktree merge <name>\n\nWorktrees: ${names}`, + `Usage: /worktree merge <name>\n\nWorktrees: ${names}`, "warning", ); return; @@ -163,7 +163,7 @@ async function handleMerge(args, ctx) { [ `Auto-commit before merge failed: ${msg}`, "", - `Commit or stash changes in ${wt.path}, then re-run /sf worktree merge ${target}.`, + `Commit or stash changes in ${wt.path}, then re-run /worktree merge ${target}.`, ].join("\n"), "error", ); @@ -179,12 +179,12 @@ async function handleMerge(args, ctx) { const msg = err instanceof Error ? err.message : String(err); if (err instanceof SFError && err.code === SF_GIT_ERROR) { ctx.ui.notify( - `Merge requires the main branch to be checked out: ${msg}\n\nSwitch to ${mainBranch} (e.g. 'git checkout ${mainBranch}'), then re-run /sf worktree merge ${target}.`, + `Merge requires the main branch to be checked out: ${msg}\n\nSwitch to ${mainBranch} (e.g. 'git checkout ${mainBranch}'), then re-run /worktree merge ${target}.`, "error", ); } else { ctx.ui.notify( - `Merge failed: ${msg}\n\nResolve conflicts manually, then run /sf worktree merge ${target} again.`, + `Merge failed: ${msg}\n\nResolve conflicts manually, then run /worktree merge ${target} again.`, "error", ); } @@ -205,8 +205,8 @@ async function handleMerge(args, ctx) { "", `Cleanup failed after the merge succeeded: ${msg}`, err instanceof SFError && err.code === SF_GIT_ERROR - ? `Switch to ${mainBranch} (e.g. 'git checkout ${mainBranch}'), then remove the worktree manually with /sf worktree remove ${target} --force.` - : `Remove the worktree manually with /sf worktree remove ${target} --force, or run 'git worktree prune' to clean up dangling registrations.`, + ? `Switch to ${mainBranch} (e.g. 'git checkout ${mainBranch}'), then remove the worktree manually with /worktree remove ${target} --force.` + : `Remove the worktree manually with /worktree remove ${target} --force, or run 'git worktree prune' to clean up dangling registrations.`, ]; ctx.ui.notify(cleanupLines.join("\n"), "warning"); } @@ -256,7 +256,7 @@ async function handleRemove(args, ctx) { const force = tokens.includes("--force"); const name = tokens.find((t) => t !== "--force"); if (!name) { - ctx.ui.notify("Usage: /sf worktree remove <name> [--force]", "warning"); + ctx.ui.notify("Usage: /worktree remove <name> [--force]", "warning"); return; } const worktrees = listWorktrees(basePath); @@ -275,8 +275,8 @@ async function handleRemove(args, ctx) { [ `Worktree "${name}" has pending changes (${formatCleanKeepReason(status)}).`, "", - ` Merge first: /sf worktree merge ${name}`, - ` Or force-remove: /sf worktree remove ${name} --force`, + ` Merge first: /worktree merge ${name}`, + ` Or force-remove: /worktree remove ${name} --force`, ].join("\n"), "warning", ); @@ -295,7 +295,7 @@ async function handleRemove(args, ctx) { } // ─── Help text ────────────────────────────────────────────────────────────── const HELP_TEXT = [ - "Usage: /sf worktree <command> [args]", + "Usage: /worktree <command> [args]", "", "Commands:", " list Show all worktrees with status", diff --git a/src/resources/extensions/sf/commands.js b/src/resources/extensions/sf/commands.js index d08cdb5da..ac9af4357 100644 --- a/src/resources/extensions/sf/commands.js +++ b/src/resources/extensions/sf/commands.js @@ -1,6 +1,6 @@ import { importExtensionModule } from "@singularity-forge/pi-coding-agent"; -export { registerSFCommand } from "./commands/index.js"; +export { registerSFCommand, registerSFCommands } from "./commands/index.js"; export async function handleSFCommand(...args) { const { handleSFCommand: dispatch } = await importExtensionModule( import.meta.url, diff --git a/src/resources/extensions/sf/commands/catalog.js b/src/resources/extensions/sf/commands/catalog.js index 513d84fa1..960478bea 100644 --- a/src/resources/extensions/sf/commands/catalog.js +++ b/src/resources/extensions/sf/commands/catalog.js @@ -12,7 +12,35 @@ const sfHome = process.env.SF_HOME || join(homedir(), ".sf"); * Comprehensive description of all available SF commands for help text. */ export const SF_COMMAND_DESCRIPTION = - "SF — Singularity Forge: /sf help|start|templates|next|autonomous|stop|pause|reload|status|widget|visualize|queue|quick|discuss|capture|triage|todo|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|show-config|prefs|config|keys|hooks|run-hook|skill-health|doctor|uok|logs|forensics|changelog|migrate|remote|steer|knowledge|harness|solver-eval|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|scaffold|extract-learnings|eval-review|plan"; + "SF — Singularity Forge: /help|start|templates|next|autonomous|pause|status|widget|visualize|queue|quick|discuss|capture|triage|todo|dispatch|history|undo|undo-task|reset-slice|rate|skip|cleanup|mode|show-config|prefs|config|keys|hooks|run-hook|skill-health|doctor|uok|logs|forensics|migrate|remote|steer|knowledge|harness|solver-eval|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp|rethink|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|scaffold|extract-learnings|eval-review|plan"; + +export const BASE_RUNTIME_COMMANDS = new Set([ + "settings", + "model", + "scoped-models", + "export", + "share", + "copy", + "name", + "session", + "changelog", + "hotkeys", + "fork", + "tree", + "provider", + "login", + "logout", + "new", + "compact", + "resume", + "reload", + "thinking", + "edit-mode", + "terminal", + "stop", + "exit", + "quit", +]); /** * Top-level SF subcommands with descriptions. */ @@ -26,7 +54,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [ { cmd: "stop", desc: "Stop autonomous mode gracefully" }, { cmd: "pause", - desc: "Pause autonomous mode (preserves state, /sf autonomous to resume)", + desc: "Pause autonomous mode (preserves state, /autonomous to resume)", }, { cmd: "reload", @@ -42,7 +70,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [ { cmd: "quick", desc: "Execute a quick task without full planning overhead" }, { cmd: "discuss", desc: "Discuss architecture and decisions" }, { cmd: "capture", desc: "Fire-and-forget thought capture" }, - { cmd: "debug", desc: "Create and inspect persistent /sf debug sessions" }, + { cmd: "debug", desc: "Create and inspect persistent /debug sessions" }, { cmd: "scan", desc: "Run source and project scans" }, { cmd: "escalate", @@ -194,6 +222,14 @@ export const TOP_LEVEL_SUBCOMMANDS = [ desc: "Promote planning artifacts from ~/.sf/ to docs/ (promote, list, diff)", }, ]; + +export const DIRECT_SF_COMMANDS = TOP_LEVEL_SUBCOMMANDS.filter( + (command) => !BASE_RUNTIME_COMMANDS.has(command.cmd), +); + +export const DIRECT_SF_COMMAND_NAMES = DIRECT_SF_COMMANDS.map( + (command) => command.cmd, +); /** * Nested subcommand definitions for multi-level completion. */ @@ -605,3 +641,18 @@ export function getSfArgumentCompletions(prefix) { } return []; } + +export function getSfTopLevelCommandCompletions(command, prefix) { + const suffix = typeof prefix === "string" ? prefix : ""; + const fullPrefix = suffix.length > 0 ? `${command} ${suffix}` : `${command} `; + const completions = getSfArgumentCompletions(fullPrefix) ?? []; + const commandPrefix = `${command} `; + return completions.map((completion) => ({ + ...completion, + value: + typeof completion.value === "string" && + completion.value.startsWith(commandPrefix) + ? completion.value.slice(commandPrefix.length) + : completion.value, + })); +} diff --git a/src/resources/extensions/sf/commands/context.js b/src/resources/extensions/sf/commands/context.js index 439b0de6d..554254200 100644 --- a/src/resources/extensions/sf/commands/context.js +++ b/src/resources/extensions/sf/commands/context.js @@ -49,7 +49,7 @@ export async function guardRemoteSession(ctx, _pi) { if (process.env.SF_WEB_BRIDGE_TUI === "1") { ctx.ui.notify( `Another autonomous mode session (PID ${remote.pid}) is running on this project (${unitLabel}). ` + - `Stop it first with /sf stop, or use /sf steer to redirect it.`, + `Stop it first with /autonomous stop, or use /steer to redirect it.`, "warning", ); return false; @@ -71,7 +71,7 @@ export async function guardRemoteSession(ctx, _pi) { id: "steer", label: "Steer the session", description: - "Use /sf steer <instruction> to redirect the running session.", + "Use /steer <instruction> to redirect the running session.", }, { id: "stop", @@ -84,7 +84,7 @@ export async function guardRemoteSession(ctx, _pi) { description: "Start a new session, terminating the existing one.", }, ], - notYetMessage: "Run /sf when ready.", + notYetMessage: "Run /next when ready.", }); if (choice === "status") { await handleStatus(ctx); @@ -92,8 +92,8 @@ export async function guardRemoteSession(ctx, _pi) { } if (choice === "steer") { ctx.ui.notify( - "Use /sf steer <instruction> to redirect the running autonomous mode session.\n" + - "Example: /sf steer Use Postgres instead of SQLite", + "Use /steer <instruction> to redirect the running autonomous mode session.\n" + + "Example: /steer Use Postgres instead of SQLite", "info", ); return false; diff --git a/src/resources/extensions/sf/commands/dispatcher.js b/src/resources/extensions/sf/commands/dispatcher.js index aec6b94f2..d027ccee2 100644 --- a/src/resources/extensions/sf/commands/dispatcher.js +++ b/src/resources/extensions/sf/commands/dispatcher.js @@ -30,7 +30,7 @@ export async function handleSFCommand(args, ctx, pi) { throw err; } ctx.ui.notify( - `Unknown: /sf ${trimmed}. Run /sf help for available commands.`, + `Unknown: /${trimmed}. Run /help for available commands.`, "warning", ); } diff --git a/src/resources/extensions/sf/commands/handlers/autonomous.js b/src/resources/extensions/sf/commands/handlers/autonomous.js index 80383d54a..ea7dec612 100644 --- a/src/resources/extensions/sf/commands/handlers/autonomous.js +++ b/src/resources/extensions/sf/commands/handlers/autonomous.js @@ -20,8 +20,8 @@ import { guardRemoteSession, projectRoot } from "../context.js"; /** * Parse --yolo flag and optional file path from the autonomous command string. - * Supports: `/sf autonomous --yolo path/to/file.md` or - * `/sf autonomous -y path/to/file.md`. + * Supports: `/autonomous --yolo path/to/file.md` or + * `/autonomous -y path/to/file.md`. */ function parseYoloFlag(trimmed) { const yoloRe = /(?:--yolo|-y)\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+)/; @@ -53,7 +53,7 @@ export function parseMilestoneTarget(input) { /** * Dispatch entry point for the autonomous command family. * - * Handles `/sf autonomous`, `/sf next`, `/sf stop`, `/sf pause`, and their flag + * Handles `/autonomous`, `/next`, `/autonomous stop`, `/pause`, and their flag * variants. * Returns `true` when the command was recognised and routed (caller stops * searching), `false` when the command isn't autonomous-related. @@ -118,6 +118,14 @@ export async function handleAutonomousCommand(trimmed, ctx, pi) { } if (isAutonomousVerb) { const autonomousArgsText = trimmed.replace(/^autonomous\b/, "").trim(); + if (autonomousArgsText === "stop") { + await stopAutonomousRun(ctx, pi); + return true; + } + if (autonomousArgsText === "pause") { + await pauseAutonomousRun(ctx, pi); + return true; + } const { yoloSeedFile, rest: afterYolo } = parseYoloFlag(autonomousArgsText); const { milestoneId, rest: afterMilestone } = parseMilestoneTarget(afterYolo); @@ -173,39 +181,11 @@ export async function handleAutonomousCommand(trimmed, ctx, pi) { return true; } if (trimmed === "stop") { - if (!isAutoActive() && !isAutoPaused()) { - const result = stopAutoRemote(projectRoot()); - if (result.found) { - ctx.ui.notify( - `Sent stop signal to autonomous mode session (PID ${result.pid}). It will shut down gracefully.`, - "info", - ); - } else if (result.error) { - ctx.ui.notify( - `Failed to stop remote autonomous run: ${result.error}`, - "error", - ); - } else { - ctx.ui.notify("Autonomous mode is not running.", "info"); - } - return true; - } - await stopAuto(ctx, pi, "User requested stop"); + await stopAutonomousRun(ctx, pi); return true; } if (trimmed === "pause") { - if (!isAutoActive()) { - if (isAutoPaused()) { - ctx.ui.notify( - "Autonomous mode is already paused. /sf autonomous to resume.", - "info", - ); - } else { - ctx.ui.notify("Autonomous mode is not running.", "info"); - } - return true; - } - await pauseAuto(ctx, pi); + await pauseAutonomousRun(ctx, pi); return true; } if (trimmed === "rate" || trimmed.startsWith("rate ")) { @@ -223,3 +203,39 @@ export async function handleAutonomousCommand(trimmed, ctx, pi) { } return false; } + +async function stopAutonomousRun(ctx, pi) { + if (!isAutoActive() && !isAutoPaused()) { + const result = stopAutoRemote(projectRoot()); + if (result.found) { + ctx.ui.notify( + `Sent stop signal to autonomous mode session (PID ${result.pid}). It will shut down gracefully.`, + "info", + ); + } else if (result.error) { + ctx.ui.notify( + `Failed to stop remote autonomous run: ${result.error}`, + "error", + ); + } else { + ctx.ui.notify("Autonomous mode is not running.", "info"); + } + return; + } + await stopAuto(ctx, pi, "User requested stop"); +} + +async function pauseAutonomousRun(ctx, pi) { + if (!isAutoActive()) { + if (isAutoPaused()) { + ctx.ui.notify( + "Autonomous mode is already paused. /autonomous to resume.", + "info", + ); + } else { + ctx.ui.notify("Autonomous mode is not running.", "info"); + } + return; + } + await pauseAuto(ctx, pi); +} diff --git a/src/resources/extensions/sf/commands/handlers/core.js b/src/resources/extensions/sf/commands/handlers/core.js index 5f4ebe7ed..6b0bb7953 100644 --- a/src/resources/extensions/sf/commands/handlers/core.js +++ b/src/resources/extensions/sf/commands/handlers/core.js @@ -24,102 +24,102 @@ export function showHelp(ctx, args = "") { const summaryLines = [ "SF — Singularity Forge\n", "QUICK START", - " /sf start <tpl> Start a workflow template", - " /sf Run one assisted unit (same as /sf next)", - " /sf autonomous Run all queued product units continuously", - " /sf pause Pause autonomous mode", - " /sf stop Stop autonomous mode gracefully", + " /start <tpl> Start a workflow template", + " /next Run one assisted unit", + " /autonomous Run all queued product units continuously", + " /pause Pause autonomous mode", + " /autonomous stop Stop autonomous mode gracefully", "", "VISIBILITY", - ` /sf status Dashboard (${formattedShortcutPair("dashboard")})`, - ` /sf parallel watch Parallel monitor (${formattedShortcutPair("parallel")})`, - ` /sf notifications Notification history (${formattedShortcutPair("notifications")})`, - " /sf visualize Interactive 10-tab TUI", - " /sf queue Show queued/dispatched units", + ` /status Dashboard (${formattedShortcutPair("dashboard")})`, + ` /parallel watch Parallel monitor (${formattedShortcutPair("parallel")})`, + ` /notifications Notification history (${formattedShortcutPair("notifications")})`, + " /visualize Interactive 10-tab TUI", + " /queue Show queued/dispatched units", "", "COURSE CORRECTION", - " /sf steer <desc> Apply user override to active work", - " /sf capture <text> Quick-capture a thought to CAPTURES.md", - " /sf triage Classify and route pending captures", - " /sf undo Revert last completed unit [--force]", - " /sf rethink Conversational project reorganization", + " /steer <desc> Apply user override to active work", + " /capture <text> Quick-capture a thought to CAPTURES.md", + " /triage Classify and route pending captures", + " /undo Revert last completed unit [--force]", + " /rethink Conversational project reorganization", "", "SETUP", - " /sf init Project init wizard", - " /sf setup Global setup status [llm|search|remote|keys|prefs]", - " /sf reload Snapshot and reload agent with fresh extension code", - " /sf model Switch active session model", - " /sf prefs Manage preferences", - " /sf doctor Diagnose and repair .sf/ state", + " /init Project init wizard", + " /setup Global setup status [llm|search|remote|keys|prefs]", + " /reload Snapshot and reload agent with fresh extension code", + " /model Switch active session model", + " /prefs Manage preferences", + " /doctor Diagnose and repair .sf/ state", "", - "Use /sf help all for the complete command reference.", + "Use /help all for the complete command reference.", ]; const allLines = [ "SF — Singularity Forge\n", "WORKFLOW", - " /sf start <tpl> Start a workflow template (bugfix, spike, feature, hotfix, etc.)", - " /sf templates List available workflow templates [info <name>]", - " /sf Run one assisted unit (same as /sf next)", - " /sf next Assisted mode: execute next task, then pause [--dry-run] [--verbose]", - " /sf autonomous Run all queued product units continuously [--verbose]", - " /sf stop Stop autonomous mode gracefully", - " /sf pause Pause autonomous mode (preserves state, /sf autonomous to resume)", - " /sf discuss Start guided milestone/slice discussion", - " /sf new-milestone Create milestone from headless context (used by sf headless)", + " /start <tpl> Start a workflow template (bugfix, spike, feature, hotfix, etc.)", + " /templates List available workflow templates [info <name>]", + " /next Run one assisted unit", + " /next Assisted mode: execute next task, then pause [--dry-run] [--verbose]", + " /autonomous Run all queued product units continuously [--verbose]", + " /autonomous stop Stop autonomous mode gracefully", + " /pause Pause autonomous mode (preserves state, /autonomous to resume)", + " /discuss Start guided milestone/slice discussion", + " /new-milestone Create milestone from headless context (used by sf headless)", "", "VISIBILITY", - ` /sf status Show progress dashboard (${formattedShortcutPair("dashboard")})`, - ` /sf parallel watch Open parallel worker monitor (${formattedShortcutPair("parallel")})`, - " /sf visualize Interactive 10-tab TUI (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)", - " /sf queue Show queued/dispatched units and execution order", - " /sf history View execution history [--cost] [--phase] [--model] [N]", - " /sf changelog Show categorized release notes [version]", - ` /sf notifications View persistent notification history [clear|tail|filter] (${formattedShortcutPair("notifications")})`, + ` /status Show progress dashboard (${formattedShortcutPair("dashboard")})`, + ` /parallel watch Open parallel worker monitor (${formattedShortcutPair("parallel")})`, + " /visualize Interactive 10-tab TUI (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)", + " /queue Show queued/dispatched units and execution order", + " /history View execution history [--cost] [--phase] [--model] [N]", + " /changelog Show categorized release notes [version]", + ` /notifications View persistent notification history [clear|tail|filter] (${formattedShortcutPair("notifications")})`, "", "COURSE CORRECTION", - " /sf steer <desc> Apply user override to active work", - " /sf capture <text> Quick-capture a thought to CAPTURES.md", - " /sf triage Classify and route pending captures", - " /sf skip <unit> Prevent a unit from autonomous mode dispatch", - " /sf undo Revert last completed unit [--force]", - " /sf rethink Conversational project reorganization — reorder, park, discard, add milestones", - " /sf park [id] Park a milestone — skip without deleting [reason]", - " /sf unpark [id] Reactivate a parked milestone", + " /steer <desc> Apply user override to active work", + " /capture <text> Quick-capture a thought to CAPTURES.md", + " /triage Classify and route pending captures", + " /skip <unit> Prevent a unit from autonomous mode dispatch", + " /undo Revert last completed unit [--force]", + " /rethink Conversational project reorganization — reorder, park, discard, add milestones", + " /park [id] Park a milestone — skip without deleting [reason]", + " /unpark [id] Reactivate a parked milestone", "", "PROJECT KNOWLEDGE", - " /sf knowledge <type> <text> Add rule, pattern, or lesson to KNOWLEDGE.md", - " /sf codebase [generate|update|stats|indexer] Manage CODEBASE.md and Sift code search", + " /knowledge <type> <text> Add rule, pattern, or lesson to KNOWLEDGE.md", + " /codebase [generate|update|stats|indexer] Manage CODEBASE.md and Sift code search", "", "SCHEDULE", - " /sf schedule add --in <dur> <title> Schedule a follow-up item", - " /sf schedule list Show pending scheduled items", - " /sf schedule done <id> Mark an item complete", + " /schedule add --in <dur> <title> Schedule a follow-up item", + " /schedule list Show pending scheduled items", + " /schedule done <id> Mark an item complete", "", "SETUP & CONFIGURATION", - " /sf init Project init wizard — detect, configure, bootstrap .sf/", - " /sf setup Global setup status [llm|search|remote|keys|prefs]", - " /sf model Switch active session model [provider/model|model-id]", - " /sf mode Set workflow mode (solo/team) [global|project]", - " /sf prefs Manage preferences [global|project|status|wizard|setup|import-claude]", - " /sf cmux Manage cmux integration [status|on|off|notifications|sidebar|splits|browser]", - " /sf config Set API keys for external tools", - " /sf keys API key manager [list|add|remove|test|rotate|doctor]", - " /sf show-config Show effective configuration (models, routing, toggles)", - " /sf hooks Show post-unit hook configuration", - " /sf extensions Manage extensions [list|enable|disable|info]", - " /sf fast Toggle OpenAI service tier [on|off|flex|status]", - " /sf mcp External MCP server status [status|check <server>]", + " /init Project init wizard — detect, configure, bootstrap .sf/", + " /setup Global setup status [llm|search|remote|keys|prefs]", + " /model Switch active session model [provider/model|model-id]", + " /mode Set workflow mode (solo/team) [global|project]", + " /prefs Manage preferences [global|project|status|wizard|setup|import-claude]", + " /cmux Manage cmux integration [status|on|off|notifications|sidebar|splits|browser]", + " /config Set API keys for external tools", + " /keys API key manager [list|add|remove|test|rotate|doctor]", + " /show-config Show effective configuration (models, routing, toggles)", + " /hooks Show post-unit hook configuration", + " /extensions Manage extensions [list|enable|disable|info]", + " /fast Toggle OpenAI service tier [on|off|flex|status]", + " /mcp External MCP server status [status|check <server>]", "", "MAINTENANCE", - " /sf doctor Diagnose and repair .sf/ state [audit|fix|heal] [scope]", - " /sf reload Snapshot & reload agent, resume same session", - " /sf export Export milestone/slice results [--json|--markdown|--html] [--all]", - " /sf cleanup Remove merged branches or snapshots [branches|snapshots]", - " /sf worktree Manage worktrees from the TUI [list|merge|clean|remove]", - " /sf migrate Migrate .planning/ (v1) to .sf/ (v2) format", - " /sf remote Configure remote question delivery [slack|discord|status|disconnect]", - " /sf inspect Show SQLite DB diagnostics (schema, row counts, recent entries)", - " /sf update Update SF to the latest version via npm", + " /doctor Diagnose and repair .sf/ state [audit|fix|heal] [scope]", + " /reload Snapshot & reload agent, resume same session", + " /export Export milestone/slice results [--json|--markdown|--html] [--all]", + " /cleanup Remove merged branches or snapshots [branches|snapshots]", + " /worktree Manage worktrees from the TUI [list|merge|clean|remove]", + " /migrate Migrate .planning/ (v1) to .sf/ (v2) format", + " /remote Configure remote question delivery [slack|discord|status|disconnect]", + " /inspect Show SQLite DB diagnostics (schema, row counts, recent entries)", + " /update Update SF to the latest version via npm", ]; const showAll = args.trim().toLowerCase() === "all"; ctx.ui.notify((showAll ? allLines : summaryLines).join("\n"), "info"); @@ -131,7 +131,7 @@ export async function handleStatus(ctx) { await ensureDbOpen(); const state = await deriveState(basePath); if (state.registry.length === 0) { - ctx.ui.notify("No SF milestones found. Run /sf to start.", "info"); + ctx.ui.notify("No SF milestones found. Run /next to start.", "info"); return; } const { SFDashboardOverlay } = await import("../../dashboard-overlay.js"); @@ -176,7 +176,7 @@ export async function handleVisualize(ctx) { ); if (result === undefined) { ctx.ui.notify( - "Visualizer requires an interactive terminal. Use /sf status for a text-based overview.", + "Visualizer requires an interactive terminal. Use /status for a text-based overview.", "warning", ); } @@ -204,7 +204,7 @@ export async function handleSetup(args, ctx) { return; } if (args === "remote") { - ctx.ui.notify("Use /sf remote to configure remote questions.", "info"); + ctx.ui.notify("Use /remote to configure remote questions.", "info"); return; } if (args === "keys") { @@ -220,11 +220,11 @@ export async function handleSetup(args, ctx) { ctx.ui.notify(statusLines.join("\n"), "info"); ctx.ui.notify( "Available setup commands:\n" + - " /sf setup llm — LLM authentication\n" + - " /sf setup search — Web search provider\n" + - " /sf setup remote — Remote questions (Discord/Slack/Telegram)\n" + - " /sf setup keys — Tool API keys\n" + - " /sf setup prefs — Global preferences wizard", + " /setup llm — LLM authentication\n" + + " /setup search — Web search provider\n" + + " /setup remote — Remote questions (Discord/Slack/Telegram)\n" + + " /setup keys — Tool API keys\n" + + " /setup prefs — Global preferences wizard", "info", ); } @@ -341,7 +341,7 @@ async function handleModel(trimmedArgs, ctx, pi) { ? `${ctx.model.provider}/${ctx.model.id}` : "(none)"; ctx.ui.notify( - `Current model: ${current}\nUsage: /sf model <provider/model|model-id>`, + `Current model: ${current}\nUsage: /model <provider/model|model-id>`, "info", ); return; @@ -357,7 +357,7 @@ async function handleModel(trimmedArgs, ctx, pi) { } if (!targetModel) { ctx.ui.notify( - `Model "${trimmed}" not found. Use /sf model with an exact provider/model or a unique model ID.`, + `Model "${trimmed}" not found. Use /model with an exact provider/model or a unique model ID.`, "warning", ); return; @@ -370,9 +370,9 @@ async function handleModel(trimmedArgs, ctx, pi) { ); return; } - // /sf model is an explicit per-session pin for SF dispatches. + // /model is an explicit per-session pin for SF dispatches. // This is captured at auto bootstrap so it survives internal session - // switches during /sf autonomous and /sf next runs. + // switches during /autonomous and /next runs. const sessionId = ctx.sessionManager?.getSessionId?.(); if (sessionId) { setSessionModelOverride(sessionId, { diff --git a/src/resources/extensions/sf/commands/handlers/notifications-handler.js b/src/resources/extensions/sf/commands/handlers/notifications-handler.js index 3830b041b..ce2095331 100644 --- a/src/resources/extensions/sf/commands/handlers/notifications-handler.js +++ b/src/resources/extensions/sf/commands/handlers/notifications-handler.js @@ -1,4 +1,4 @@ -// SF Extension — /sf notifications Command Handler +// SF Extension — /notifications Command Handler // View, filter, and clear the persistent notification history. import { SFNotificationOverlay } from "../../notification-overlay.js"; import { @@ -37,7 +37,7 @@ function formatTimestamp(ts) { } } export async function handleNotificationsCommand(args, ctx, _pi) { - // /sf notifications clear + // /notifications clear if (args === "clear") { clearNotifications(); // Suppress persistence so the confirmation toast doesn't re-populate the store @@ -49,7 +49,7 @@ export async function handleNotificationsCommand(args, ctx, _pi) { } return true; } - // /sf notifications tail [N] + // /notifications tail [N] if (args === "tail" || args.startsWith("tail ")) { const countStr = args.replace(/^tail\s*/, "").trim(); const count = countStr ? parseInt(countStr, 10) : 20; @@ -69,7 +69,7 @@ export async function handleNotificationsCommand(args, ctx, _pi) { ); const suffix = all.length > entries.length - ? `\n... and ${all.length - entries.length} more (open /sf notifications to browse all)` + ? `\n... and ${all.length - entries.length} more (open /notifications to browse all)` : ""; ctx.ui.notify( `Last ${entries.length} notification(s):\n${lines.join("\n")}${suffix}`, @@ -77,7 +77,7 @@ export async function handleNotificationsCommand(args, ctx, _pi) { ); return true; } - // /sf notifications filter <severity> + // /notifications filter <severity> if (args.startsWith("filter ")) { const severity = args .replace(/^filter\s+/, "") @@ -85,7 +85,7 @@ export async function handleNotificationsCommand(args, ctx, _pi) { .toLowerCase(); if (!["error", "warning", "info", "success"].includes(severity)) { ctx.ui.notify( - "Usage: /sf notifications filter <error|warning|info|success>", + "Usage: /notifications filter <error|warning|info|success>", "warning", ); return true; @@ -103,7 +103,7 @@ export async function handleNotificationsCommand(args, ctx, _pi) { ); const suffix = entries.length > 20 - ? `\n... and ${entries.length - 20} more (open /sf notifications to browse all)` + ? `\n... and ${entries.length - 20} more (open /notifications to browse all)` : ""; ctx.ui.notify( `${severity} notifications (${entries.length}):\n${lines.join("\n")}${suffix}`, @@ -111,7 +111,7 @@ export async function handleNotificationsCommand(args, ctx, _pi) { ); return true; } - // /sf notifications (no args) — open overlay in TUI, or print summary + // /notifications (no args) — open overlay in TUI, or print summary if (args === "" || args === "status") { // Try overlay first (TUI mode) if (ctx.hasUI) { @@ -157,7 +157,7 @@ export async function handleNotificationsCommand(args, ctx, _pi) { } // Unknown subcommand ctx.ui.notify( - "Usage: /sf notifications [clear|tail [N]|filter <severity>]", + "Usage: /notifications [clear|tail [N]|filter <severity>]", "warning", ); return true; diff --git a/src/resources/extensions/sf/commands/handlers/ops.js b/src/resources/extensions/sf/commands/handlers/ops.js index 28d0df56a..03c7f9713 100644 --- a/src/resources/extensions/sf/commands/handlers/ops.js +++ b/src/resources/extensions/sf/commands/handlers/ops.js @@ -126,7 +126,7 @@ export async function handleOpsCommand(trimmed, ctx, pi) { } if (trimmed === "skip") { ctx.ui.notify( - "Usage: /sf skip <unit-id> Example: /sf skip M001/S01/T03", + "Usage: /skip <unit-id> Example: /skip M001/S01/T03", "warning", ); return true; @@ -235,7 +235,7 @@ export async function handleOpsCommand(trimmed, ctx, pi) { } if (trimmed === "run-hook") { ctx.ui.notify( - `Usage: /sf run-hook <hook-name> <unit-type> <unit-id> + `Usage: /run-hook <hook-name> <unit-type> <unit-id> Unit types: execute-task - Task execution (unit-id: M001/S01/T01) @@ -245,8 +245,8 @@ Unit types: complete-milestone - Milestone completion (unit-id: M001) Examples: - /sf run-hook code-review execute-task M001/S01/T01 - /sf run-hook lint-check plan-slice M001/S01`, + /run-hook code-review execute-task M001/S01/T01 + /run-hook lint-check plan-slice M001/S01`, "warning", ); return true; @@ -257,7 +257,7 @@ Examples: } if (trimmed === "steer") { ctx.ui.notify( - "Usage: /sf steer <description of change>. Example: /sf steer Use Postgres instead of SQLite", + "Usage: /steer <description of change>. Example: /steer Use Postgres instead of SQLite", "warning", ); return true; @@ -268,7 +268,7 @@ Examples: } if (trimmed === "knowledge") { ctx.ui.notify( - "Usage: /sf knowledge <rule|pattern|lesson> <description>. Example: /sf knowledge rule Use real DB for integration tests", + "Usage: /knowledge <rule|pattern|lesson> <description>. Example: /knowledge rule Use real DB for integration tests", "warning", ); return true; @@ -302,7 +302,7 @@ Examples: const phase = trimmed.replace(/^dispatch\s*/, "").trim(); if (!phase) { ctx.ui.notify( - "Usage: /sf dispatch <phase> (research|plan|execute|complete|reassess|uat|replan)", + "Usage: /dispatch <phase> (research|plan|execute|complete|reassess|uat|replan)", "warning", ); return true; @@ -386,7 +386,7 @@ Examples: } if (trimmed === "scaffold") { ctx.ui.notify( - "Usage: /sf scaffold sync [--dry-run] [--include-editing] [--only=<glob>]", + "Usage: /scaffold sync [--dry-run] [--include-editing] [--only=<glob>]", "warning", ); return true; @@ -431,7 +431,7 @@ Examples: ctx, ); if (handled) return true; - ctx.ui.notify("Usage: /sf plan promote|list|diff|specs ...", "info"); + ctx.ui.notify("Usage: /plan promote|list|diff|specs ...", "info"); return true; } return false; diff --git a/src/resources/extensions/sf/commands/handlers/parallel.js b/src/resources/extensions/sf/commands/handlers/parallel.js index 1d1ff6953..9004defd7 100644 --- a/src/resources/extensions/sf/commands/handlers/parallel.js +++ b/src/resources/extensions/sf/commands/handlers/parallel.js @@ -170,7 +170,7 @@ export async function handleParallelCommand(trimmed, _ctx, pi) { } emitParallelMessage( pi, - `Unknown parallel subcommand "${subcommand}". Usage: /sf parallel [start [--stop-on-failure]|status|stop|pause|resume|merge|watch]`, + `Unknown parallel subcommand "${subcommand}". Usage: /parallel [start [--stop-on-failure]|status|stop|pause|resume|merge|watch]`, ); return true; } diff --git a/src/resources/extensions/sf/commands/handlers/workflow.js b/src/resources/extensions/sf/commands/handlers/workflow.js index 07edf9f3e..5cd7b9f26 100644 --- a/src/resources/extensions/sf/commands/handlers/workflow.js +++ b/src/resources/extensions/sf/commands/handlers/workflow.js @@ -35,7 +35,7 @@ import { projectRoot } from "../context.js"; // ─── Custom Workflow Subcommands ───────────────────────────────────────── const WORKFLOW_USAGE = [ - "Usage: /sf workflow <subcommand>", + "Usage: /workflow <subcommand>", "", " new — Create a new workflow definition (via skill)", " run <name> [k=v] — Create a run and start autonomous mode", @@ -97,7 +97,7 @@ export function parseWorkflowRunArgs(args) { return { defName, overrides }; } async function handleCustomWorkflow(sub, ctx, pi) { - // Bare `/sf workflow` — show usage + // Bare `/workflow` — show usage if (!sub) { ctx.ui.notify(WORKFLOW_USAGE, "info"); return true; @@ -114,10 +114,7 @@ async function handleCustomWorkflow(sub, ctx, pi) { if (sub === "run" || sub.startsWith("run ")) { const args = sub.slice("run".length).trim(); if (!args) { - ctx.ui.notify( - "Usage: /sf workflow run <name> [param=value ...]", - "warning", - ); + ctx.ui.notify("Usage: /workflow run <name> [param=value ...]", "warning"); return true; } const { defName, overrides } = parseWorkflowRunArgs(args); @@ -136,7 +133,7 @@ async function handleCustomWorkflow(sub, ctx, pi) { ); startAutoDetached(ctx, pi, base, false); } catch (err) { - // Clean up engine state so a failed workflow run doesn't pollute the next /sf autonomous + // Clean up engine state so a failed workflow run doesn't pollute the next /autonomous setActiveEngineId(null); setActiveRunDir(null); const msg = err instanceof Error ? err.message : String(err); @@ -165,7 +162,7 @@ async function handleCustomWorkflow(sub, ctx, pi) { if (sub === "validate" || sub.startsWith("validate ")) { const defName = sub.slice("validate".length).trim(); if (!defName) { - ctx.ui.notify("Usage: /sf workflow validate <name>", "warning"); + ctx.ui.notify("Usage: /workflow validate <name>", "warning"); return true; } const base = projectRoot(); @@ -197,7 +194,7 @@ async function handleCustomWorkflow(sub, ctx, pi) { const engineId = getActiveEngineId(); if (engineId === "dev" || engineId === null) { ctx.ui.notify( - "No custom workflow is running. Use /sf pause for dev workflow.", + "No custom workflow is running. Use /pause for dev workflow.", "warning", ); return true; @@ -215,7 +212,7 @@ async function handleCustomWorkflow(sub, ctx, pi) { const engineId = getActiveEngineId(); if (engineId === "dev" || engineId === null) { ctx.ui.notify( - "No custom workflow to resume. Use /sf autonomous for dev workflow.", + "No custom workflow to resume. Use /autonomous for dev workflow.", "warning", ); return true; @@ -232,7 +229,7 @@ async function handleCustomWorkflow(sub, ctx, pi) { return true; } export async function handleWorkflowCommand(trimmed, ctx, pi) { - // ── /sf do — natural language routing (must be early to route to other commands) ── + // ── /do — natural language routing (must be early to route to other commands) ── if (trimmed === "do" || trimmed.startsWith("do ")) { const { handleDo } = await import("../../commands-do.js"); await handleDo(trimmed.replace(/^do\s*/, "").trim(), ctx, pi); @@ -250,7 +247,7 @@ export async function handleWorkflowCommand(trimmed, ctx, pi) { await handleSchedule(trimmed.replace(/^schedule\s*/, "").trim(), ctx); return true; } - // ── Custom workflow commands (`/sf workflow ...`) ── + // ── Custom workflow commands (`/workflow ...`) ── if (trimmed === "workflow" || trimmed.startsWith("workflow ")) { const sub = trimmed.slice("workflow".length).trim(); return handleCustomWorkflow(sub, ctx, pi); @@ -266,8 +263,8 @@ export async function handleWorkflowCommand(trimmed, ctx, pi) { if (trimmed === "quick" || trimmed.startsWith("quick ")) { if (isAutoActive()) { ctx.ui.notify( - "/sf quick cannot run while autonomous mode is active.\n" + - "Stop autonomous mode first with /sf stop, then run /sf quick.", + "/quick cannot run while autonomous mode is active.\n" + + "Stop autonomous mode first with /stop, then run /quick.", "error", ); return true; @@ -318,7 +315,7 @@ export async function handleWorkflowCommand(trimmed, ctx, pi) { } if (isParked(basePath, targetId)) { ctx.ui.notify( - `${targetId} is already parked. Use /sf unpark ${targetId} to reactivate.`, + `${targetId} is already parked. Use /unpark ${targetId} to reactivate.`, "info", ); return true; @@ -327,11 +324,11 @@ export async function handleWorkflowCommand(trimmed, ctx, pi) { .replace(targetId, "") .trim() .replace(/^["']|["']$/g, ""); - const reason = reasonParts || "Parked via /sf park"; + const reason = reasonParts || "Parked via /park"; const success = parkMilestone(basePath, targetId, reason); ctx.ui.notify( success - ? `Parked ${targetId}. Run /sf unpark ${targetId} to reactivate.` + ? `Parked ${targetId}. Run /unpark ${targetId} to reactivate.` : `Could not park ${targetId} — milestone not found.`, success ? "info" : "warning", ); @@ -354,7 +351,7 @@ export async function handleWorkflowCommand(trimmed, ctx, pi) { targetId = parkedEntries[0].id; } else { ctx.ui.notify( - `Parked milestones: ${parkedEntries.map((entry) => entry.id).join(", ")}. Specify which to unpark: /sf unpark <id>`, + `Parked milestones: ${parkedEntries.map((entry) => entry.id).join(", ")}. Specify which to unpark: /unpark <id>`, "info", ); return true; diff --git a/src/resources/extensions/sf/commands/index.js b/src/resources/extensions/sf/commands/index.js index 6b7e5ab2a..283f1ccf4 100644 --- a/src/resources/extensions/sf/commands/index.js +++ b/src/resources/extensions/sf/commands/index.js @@ -1,24 +1,44 @@ import { importExtensionModule } from "@singularity-forge/pi-coding-agent"; -import { getSfArgumentCompletions, SF_COMMAND_DESCRIPTION } from "./catalog.js"; -export function registerSFCommand(pi) { - pi.registerCommand("sf", { - description: SF_COMMAND_DESCRIPTION, - getArgumentCompletions: getSfArgumentCompletions, - handler: async (args, ctx) => { - const { handleSFCommand } = await importExtensionModule( - import.meta.url, - "./dispatcher.js", - ); - const { setStderrLoggingEnabled } = await importExtensionModule( - import.meta.url, - "../workflow-logger.js", - ); - const previousStderrSetting = setStderrLoggingEnabled(false); - try { - await handleSFCommand(args, ctx, pi); - } finally { - setStderrLoggingEnabled(previousStderrSetting); - } - }, - }); +import { + DIRECT_SF_COMMANDS, + getSfTopLevelCommandCompletions, + SF_COMMAND_DESCRIPTION, +} from "./catalog.js"; + +async function dispatchDirectSFCommand(command, args, ctx, pi) { + const { handleSFCommand } = await importExtensionModule( + import.meta.url, + "./dispatcher.js", + ); + const { setStderrLoggingEnabled } = await importExtensionModule( + import.meta.url, + "../workflow-logger.js", + ); + const previousStderrSetting = setStderrLoggingEnabled(false); + try { + const suffix = + typeof args === "string" && args.trim().length > 0 + ? ` ${args.trim()}` + : ""; + await handleSFCommand(`${command}${suffix}`, ctx, pi); + } finally { + setStderrLoggingEnabled(previousStderrSetting); + } +} + +export function registerSFCommands(pi) { + for (const command of DIRECT_SF_COMMANDS) { + pi.registerCommand(command.cmd, { + description: command.desc || SF_COMMAND_DESCRIPTION, + getArgumentCompletions: (prefix) => + getSfTopLevelCommandCompletions(command.cmd, prefix), + handler: async (args, ctx) => { + await dispatchDirectSFCommand(command.cmd, args, ctx, pi); + }, + }); + } +} + +export function registerSFCommand(pi) { + registerSFCommands(pi); } diff --git a/src/resources/extensions/sf/config-overlay.js b/src/resources/extensions/sf/config-overlay.js index b2973f037..0cb39e5ed 100644 --- a/src/resources/extensions/sf/config-overlay.js +++ b/src/resources/extensions/sf/config-overlay.js @@ -4,7 +4,7 @@ * Read-only TUI overlay showing the effective SF configuration: * token profile, model assignments, dynamic routing, git settings, * budget, workflow toggles, and preference file sources. - * Opened via `/sf show-config` or `/sf config`. + * Opened via `/show-config` or `/config`. */ import { Key, matchesKey, truncateToWidth } from "@singularity-forge/pi-tui"; import { @@ -359,7 +359,7 @@ export class SFConfigOverlay { allLines.push( t.fg( "muted", - " esc/q close \u2502 \u2191\u2193/jk scroll \u2502 /sf prefs to edit", + " esc/q close \u2502 \u2191\u2193/jk scroll \u2502 /prefs to edit", ), ); // Apply scroll diff --git a/src/resources/extensions/sf/crash-recovery.js b/src/resources/extensions/sf/crash-recovery.js index b3fff0f51..b3175c667 100644 --- a/src/resources/extensions/sf/crash-recovery.js +++ b/src/resources/extensions/sf/crash-recovery.js @@ -90,21 +90,21 @@ export function formatCrashInfo(lock) { ]; // Add recovery guidance based on what was happening when it crashed if (lock.unitType === "starting" && lock.unitId === "bootstrap") { - lines.push(`No work was lost. Run /sf autonomous to restart.`); + lines.push(`No work was lost. Run /autonomous to restart.`); } else if ( lock.unitType.includes("research") || lock.unitType.includes("plan") ) { lines.push( - `The ${lock.unitType} unit may be incomplete. Run /sf autonomous to re-run it.`, + `The ${lock.unitType} unit may be incomplete. Run /autonomous to re-run it.`, ); } else if (lock.unitType.includes("execute")) { lines.push( - `Task execution was interrupted. Run /sf autonomous to resume — completed work is preserved.`, + `Task execution was interrupted. Run /autonomous to resume — completed work is preserved.`, ); } else if (lock.unitType.includes("complete")) { lines.push( - `Slice/milestone completion was interrupted. Run /sf autonomous to finish.`, + `Slice/milestone completion was interrupted. Run /autonomous to finish.`, ); } return lines.join("\n"); diff --git a/src/resources/extensions/sf/dashboard-overlay.js b/src/resources/extensions/sf/dashboard-overlay.js index 3a8440a6a..a2b744f17 100644 --- a/src/resources/extensions/sf/dashboard-overlay.js +++ b/src/resources/extensions/sf/dashboard-overlay.js @@ -4,7 +4,7 @@ * Full-screen overlay showing autonomous mode progress: milestone/slice/task * breakdown, current unit, completed units, timing, and activity log. * Toggled with Ctrl+Alt+G (⌃⌥G on macOS), Ctrl+Shift+G fallback, - * or opened from /sf status. + * or opened from /status. */ import { Key, @@ -417,7 +417,7 @@ export class SFDashboardOverlay { ); lines.push(blank()); } else if (this.dashData.paused) { - lines.push(row(th.fg("dim", "/sf autonomous to resume"))); + lines.push(row(th.fg("dim", "/autonomous to resume"))); lines.push(blank()); } else if (isRemote) { const rs = this.dashData.remoteSession; @@ -428,9 +428,7 @@ export class SFDashboardOverlay { lines.push(row(th.fg("text", `Remote session: ${unitDisplay}`))); lines.push(blank()); } else { - lines.push( - row(th.fg("dim", "No unit running · /sf autonomous to start")), - ); + lines.push(row(th.fg("dim", "No unit running · /autonomous to start"))); lines.push(blank()); } // Parallel workers section — shows active subagent sessions diff --git a/src/resources/extensions/sf/docs/claude-marketplace-import.md b/src/resources/extensions/sf/docs/claude-marketplace-import.md index c45aa6e23..d599bbea4 100644 --- a/src/resources/extensions/sf/docs/claude-marketplace-import.md +++ b/src/resources/extensions/sf/docs/claude-marketplace-import.md @@ -11,14 +11,14 @@ SF can read Claude Code marketplace catalogs, inspect the plugins they reference The interactive entry point is: ```text -/sf prefs import-claude +/prefs import-claude ``` You can also choose scope explicitly: ```text -/sf prefs import-claude global -/sf prefs import-claude project +/prefs import-claude global +/prefs import-claude project ``` --- @@ -194,7 +194,7 @@ Real host validation included: - clean startup of the installed `sf` binary after fixing stale bad settings - successful invocation of an imported skill (`/stinkysnake`) -- successful execution of `/sf prefs import-claude global` +- successful execution of `/prefs import-claude global` - verification that imported marketplace agent directories were **not** reintroduced into `settings.packages` --- diff --git a/src/resources/extensions/sf/docs/preferences-reference.md b/src/resources/extensions/sf/docs/preferences-reference.md index 01c50a050..276a50511 100644 --- a/src/resources/extensions/sf/docs/preferences-reference.md +++ b/src/resources/extensions/sf/docs/preferences-reference.md @@ -90,7 +90,7 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea | `git.isolation` | `"worktree"` | `"worktree"` | | `unique_milestone_ids` | `false` | `true` | - Quick setup: `/sf mode` (global) or `/sf mode project` (project-level). + Quick setup: `/mode` (global) or `/mode project` (project-level). - `always_use_skills`: skills SF should use whenever they are relevant. @@ -126,7 +126,7 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea - `idle_timeout_minutes`: minutes of inactivity before the supervisor intervenes (default: 10). - `hard_timeout_minutes`: minutes before the supervisor forces termination (default: 30). - `solver_max_iterations`: maximum autonomous solver iterations for one unit before pausing (default: `30000`, min: `1`, max: `100000`). - - `solver_eval_on_autonomous_exit`: automatically run and record the built-in solver eval when `/sf autonomous` exits (default: `true`; set `false` only to disable lifecycle eval evidence). + - `solver_eval_on_autonomous_exit`: automatically run and record the built-in solver eval when `/autonomous` exits (default: `true`; set `false` only to disable lifecycle eval evidence). - `completion_nudge_after`: tool calls in a complete-slice unit before nudging the agent to call `sf_slice_complete` (default: 10; set `0` to disable). - `runaway_guard_enabled`: enable active-loop diagnosis for long-running units (default: `true`). - `runaway_tool_call_warning`: unit tool calls before a runaway warning (default: `60`; set `0` to disable this signal). @@ -177,8 +177,8 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea - `exclude_patterns`: string[] — extra file or directory patterns to omit from CODEBASE.md. - `max_files`: number — maximum files to include in CODEBASE.md. Default: `500`. - `collapse_threshold`: number — files-per-directory threshold before collapsing a directory summary. Default: `20`. - - `indexer_backend`: `"sift"` or `"none"` — codebase-indexer backend used for prompt guidance and `/sf codebase indexer status`. Default: `"sift"`. - - `/sf codebase indexer status` reports Sift status. Install `rupurt/sift` on `PATH` or set `SIFT_PATH`. + - `indexer_backend`: `"sift"` or `"none"` — codebase-indexer backend used for prompt guidance and `/codebase indexer status`. Default: `"sift"`. + - `/codebase indexer status` reports Sift status. Install `rupurt/sift` on `PATH` or set `SIFT_PATH`. - `remote_questions`: route interactive questions to Slack/Discord for machine-surface autonomous runs. Keys: - `channel`: `"slack"` or `"discord"` — channel type. @@ -834,7 +834,7 @@ This team-mode configuration: ### Doctor Checks -Run `/sf doctor` to validate your config: +Run `/doctor` to validate your config: - **Error:** `context_compact_at` > `context_hard_limit` (illogical; compact must happen before hitting hard limit). - **Error:** Invalid `worktree_mode` value. @@ -844,7 +844,7 @@ Run `/sf doctor` to validate your config: - **Warning:** Unrecognized phase name in `unit_timeout_by_phase` or `max_agents_by_phase`. - **Warning:** Phase timeout < 60 seconds or agent count out of range [1, 16]. -Run `/sf doctor --fix` to auto-correct fixable errors (e.g., `context_compact_at` > `context_hard_limit`). +Run `/doctor --fix` to auto-correct fixable errors (e.g., `context_compact_at` > `context_hard_limit`). --- @@ -964,10 +964,10 @@ OPENAI_API_KEY=vault://secret/openai/prod#api_key ### Troubleshooting -Run `/sf doctor` to check Vault setup: +Run `/doctor` to check Vault setup: ```bash -/sf doctor +/doctor ``` **Common Issues:** @@ -1080,7 +1080,7 @@ I recommend reassessing the problem statement or constraints. ### Doctor Check -Run `/sf doctor` to validate turn_status marker coverage: +Run `/doctor` to validate turn_status marker coverage: - **Warning:** Executive prompts missing turn_status marker templates. Agents won't be able to signal `blocked` or `giving_up` state. diff --git a/src/resources/extensions/sf/doctor-environment.js b/src/resources/extensions/sf/doctor-environment.js index 3457df093..40e44a501 100644 --- a/src/resources/extensions/sf/doctor-environment.js +++ b/src/resources/extensions/sf/doctor-environment.js @@ -634,7 +634,7 @@ export function runEnvironmentChecks(basePath) { } /** * Run environment checks with git remote check included. - * Use this for explicit /sf doctor invocations, not pre-dispatch gates. + * Use this for explicit /doctor invocations, not pre-dispatch gates. */ export function runFullEnvironmentChecks(basePath) { const results = runEnvironmentChecks(basePath); @@ -644,7 +644,7 @@ export function runFullEnvironmentChecks(basePath) { } /** * Run slow opt-in checks (build and/or test). - * These are never run on the pre-dispatch gate — only on explicit /sf doctor --build/--test. + * These are never run on the pre-dispatch gate — only on explicit /doctor --build/--test. */ export function runSlowEnvironmentChecks(basePath, options) { const results = []; diff --git a/src/resources/extensions/sf/doctor-format.js b/src/resources/extensions/sf/doctor-format.js index 9ba4dadde..e4d1f13e1 100644 --- a/src/resources/extensions/sf/doctor-format.js +++ b/src/resources/extensions/sf/doctor-format.js @@ -99,7 +99,7 @@ export function formatDoctorIssuesForPrompt(issues) { } /** * Serialize a doctor report to JSON — suitable for CI/tooling integration. - * Usage: /sf doctor --json + * Usage: /doctor --json */ export function formatDoctorReportJson(report) { return JSON.stringify( diff --git a/src/resources/extensions/sf/doctor-global-checks.js b/src/resources/extensions/sf/doctor-global-checks.js index cfffb446b..4dd6f7c88 100644 --- a/src/resources/extensions/sf/doctor-global-checks.js +++ b/src/resources/extensions/sf/doctor-global-checks.js @@ -57,7 +57,7 @@ export async function checkGlobalHealth(issues, fixesApplied, shouldFix) { code: "orphaned_project_state", scope: "project", unitId: "global", - message: `${orphaned.length} orphaned SF project state director${orphaned.length === 1 ? "y" : "ies"} in ${projectsDir} whose git root no longer exists: ${labels}${overflow}${unknownNote}. Run /sf cleanup projects to audit or /sf cleanup projects --fix to reclaim disk space.`, + message: `${orphaned.length} orphaned SF project state director${orphaned.length === 1 ? "y" : "ies"} in ${projectsDir} whose git root no longer exists: ${labels}${overflow}${unknownNote}. Run /cleanup projects to audit or /cleanup projects --fix to reclaim disk space.`, file: projectsDir, fixable: true, }); diff --git a/src/resources/extensions/sf/doctor-proactive.js b/src/resources/extensions/sf/doctor-proactive.js index 90d41749d..a9231f01c 100644 --- a/src/resources/extensions/sf/doctor-proactive.js +++ b/src/resources/extensions/sf/doctor-proactive.js @@ -247,7 +247,7 @@ export async function preDispatchHealthGate(basePath) { ); } catch { issues.push( - `Corrupt git state: ${blockers.join(", ")}. Run /sf doctor fix.`, + `Corrupt git state: ${blockers.join(", ")}. Run /doctor fix.`, ); } } @@ -298,7 +298,7 @@ export async function preDispatchHealthGate(basePath) { resolution.status === "missing" ) { issues.push( - `${resolution.reason} Restore the branch or update the integration branch before dispatching. Run /sf doctor for details.`, + `${resolution.reason} Restore the branch or update the integration branch before dispatching. Run /doctor for details.`, ); } } @@ -367,7 +367,7 @@ export async function preDispatchHealthGate(basePath) { if (issues.length > 0) { return { proceed: false, - reason: `Pre-dispatch health check failed:\n${issues.map((i) => ` - ${i}`).join("\n")}\nRun /sf doctor fix to resolve.`, + reason: `Pre-dispatch health check failed:\n${issues.map((i) => ` - ${i}`).join("\n")}\nRun /doctor fix to resolve.`, issues, fixesApplied, }; diff --git a/src/resources/extensions/sf/doctor-providers.js b/src/resources/extensions/sf/doctor-providers.js index 0a8533fc9..2fb298f73 100644 --- a/src/resources/extensions/sf/doctor-providers.js +++ b/src/resources/extensions/sf/doctor-providers.js @@ -258,8 +258,8 @@ function checkLlmProviders() { providerId === "anthropic-vertex" ? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC" : info?.hasOAuth - ? `Run /sf keys to authenticate` - : `Set ${envVar} or run /sf keys`, + ? `Run /keys to authenticate` + : `Set ${envVar} or run /keys`, required: true, }); } else if (lookup.backedOff) { @@ -315,8 +315,8 @@ function checkRemoteQuestionsProvider() { ? `${label} — token not found (remote questions auto-resolve on timeout)` : `${label} — channel configured but token not found`, detail: info?.envVar - ? `Set ${info.envVar} or run /sf keys` - : `Run /sf keys to configure`, + ? `Set ${info.envVar} or run /keys` + : `Run /keys to configure`, required: !autoResolvable, }; } diff --git a/src/resources/extensions/sf/doctor-runtime-checks.js b/src/resources/extensions/sf/doctor-runtime-checks.js index dff0ce644..efcbd16b5 100644 --- a/src/resources/extensions/sf/doctor-runtime-checks.js +++ b/src/resources/extensions/sf/doctor-runtime-checks.js @@ -639,7 +639,7 @@ export async function checkRuntimeHealth( code: "metrics_ledger_bloat", scope: "project", unitId: "project", - message: `metrics.json has ${parsed.units.length} unit entries (${fileSizeMB}MB) — threshold is ${BLOAT_UNITS_THRESHOLD}. Run /sf doctor --fix to prune to the newest 1500 entries.`, + message: `metrics.json has ${parsed.units.length} unit entries (${fileSizeMB}MB) — threshold is ${BLOAT_UNITS_THRESHOLD}. Run /doctor --fix to prune to the newest 1500 entries.`, file: ".sf/metrics.json", fixable: true, }); @@ -709,18 +709,18 @@ export async function checkRuntimeHealth( // Non-fatal — large file scan failed } // ── Snapshot ref bloat ──────────────────────────────────────────────── - // refs/sf/snapshots/ accumulate over time. Prune to newest 5 per label + // refs/next/snapshots/ accumulate over time. Prune to newest 5 per label // when total count exceeds threshold. try { if (nativeIsRepo(basePath)) { - const refs = nativeForEachRef(basePath, "refs/sf/snapshots/"); + const refs = nativeForEachRef(basePath, "refs/next/snapshots/"); if (refs.length > 50) { issues.push({ severity: "warning", code: "snapshot_ref_bloat", scope: "project", unitId: "project", - message: `${refs.length} snapshot refs found under refs/sf/snapshots/ — pruning to newest 5 per label will reclaim git storage`, + message: `${refs.length} snapshot refs found under refs/next/snapshots/ — pruning to newest 5 per label will reclaim git storage`, fixable: true, }); if (shouldFix("snapshot_ref_bloat")) { @@ -804,7 +804,7 @@ function formatBucketCountParts(counts) { * * Returns `null` when there is nothing actionable (everything is current or * intentionally customised). Otherwise returns a single warning summarising the - * bucket counts. The phrase "Run /sf scaffold sync" is forward-looking — + * bucket counts. The phrase "Run /scaffold sync" is forward-looking — * Phase E adds the command. Phase C runs the silent path automatically on * every SF startup, so the user does not need to act on most of these. */ @@ -823,8 +823,8 @@ export function checkScaffoldFreshness(basePath) { const summary = parts.join(", "); const guidance = pendingCount > 0 - ? `Run /sf scaffold sync to refresh ${pendingCount} pending docs` - : "Run /sf scaffold sync to inspect drift"; + ? `Run /scaffold sync to refresh ${pendingCount} pending docs` + : "Run /scaffold sync to inspect drift"; return { severity: "warning", code: "scaffold_drift", diff --git a/src/resources/extensions/sf/doctor.js b/src/resources/extensions/sf/doctor.js index cd1410f53..47c383d24 100644 --- a/src/resources/extensions/sf/doctor.js +++ b/src/resources/extensions/sf/doctor.js @@ -418,7 +418,7 @@ function classifyProcess(row, rows = []) { if ( (cmd.includes("node") || cmd.includes("sf-run") || cmd.includes("codex")) && (cmd.includes(" sf") || - cmd.includes("/sf") || + cmd.includes("/next") || cmd.includes("dist/loader") || cmd.includes("tool-session") || cmd.includes("headless")) @@ -643,7 +643,7 @@ function chooseRecommendedAction(args) { * milestone/unit, progress age, session pointer, child processes, last errors, * and recommended action. * - * Consumer: `/sf doctor flow` command and session_start startup health sweep. + * Consumer: `/doctor flow` command and session_start startup health sweep. */ export async function runFlowAudit(basePath, options = {}) { const nowMs = options.nowMs ?? Date.now(); diff --git a/src/resources/extensions/sf/escalation.js b/src/resources/extensions/sf/escalation.js index 4c7816de6..02c8d7449 100644 --- a/src/resources/extensions/sf/escalation.js +++ b/src/resources/extensions/sf/escalation.js @@ -83,7 +83,7 @@ export function writeEscalationArtifact(basePath, artifact) { ); if (!path) { throw new Error( - `escalation: cannot resolve tasks dir for ${artifact.milestoneId}/${artifact.sliceId} — run /sf doctor`, + `escalation: cannot resolve tasks dir for ${artifact.milestoneId}/${artifact.sliceId} — run /doctor`, ); } mkdirSync(dirname(path), { recursive: true }); @@ -314,7 +314,7 @@ export function resolveEscalation( if (art.respondedAt) { const wasAuto = art.userRationale?.startsWith("autonomous mode:"); const detail = wasAuto - ? ` (auto-accepted in autonomous mode → choice="${art.userChoice}"; the carry-forward was already injected into the downstream task, so this can't be retroactively changed via /sf escalate resolve. Capture the corrective decision as \`/sf memory note "..."\` so future tasks pick it up.)` + ? ` (auto-accepted in autonomous mode → choice="${art.userChoice}"; the carry-forward was already injected into the downstream task, so this can't be retroactively changed via /escalate resolve. Capture the corrective decision as \`/memory note "..."\` so future tasks pick it up.)` : ` (resolved by user → choice="${art.userChoice}").`; return { status: "already-resolved", diff --git a/src/resources/extensions/sf/eval-review-schema.js b/src/resources/extensions/sf/eval-review-schema.js index 15544e0be..6bd5fde3d 100644 --- a/src/resources/extensions/sf/eval-review-schema.js +++ b/src/resources/extensions/sf/eval-review-schema.js @@ -1,7 +1,7 @@ /** * EVAL-REVIEW frontmatter schema and parser. * - * The auditor agent for `/sf eval-review` writes a markdown file whose + * The auditor agent for `/eval-review` writes a markdown file whose * machine-readable contract lives entirely in YAML frontmatter. The body * after the closing `---` is human-only prose and is never parsed by any * consumer (the design response to a prior parser that used regex over LLM-generated diff --git a/src/resources/extensions/sf/extension-manifest.json b/src/resources/extensions/sf/extension-manifest.json index 6f851b4cc..2de411cc8 100644 --- a/src/resources/extensions/sf/extension-manifest.json +++ b/src/resources/extensions/sf/extension-manifest.json @@ -44,7 +44,83 @@ "sift_search", "write" ], - "commands": ["exit", "kill", "sf", "worktree", "wt"], + "commands": [ + "add-tests", + "autonomous", + "backlog", + "capture", + "cleanup", + "cmux", + "codebase", + "config", + "debug", + "discuss", + "dispatch", + "do", + "doctor", + "escalate", + "eval-review", + "exit", + "extensions", + "extract-learnings", + "fast", + "forensics", + "harness", + "help", + "history", + "hooks", + "init", + "inspect", + "keys", + "kill", + "knowledge", + "logs", + "mcp", + "migrate", + "mode", + "new-milestone", + "next", + "notifications", + "parallel", + "park", + "pause", + "plan", + "pr-branch", + "prefs", + "queue", + "quick", + "rate", + "remote", + "reset-slice", + "rethink", + "run-hook", + "scaffold", + "scan", + "schedule", + "session-report", + "setup", + "ship", + "show-config", + "skill-health", + "skip", + "solver-eval", + "start", + "status", + "steer", + "templates", + "todo", + "triage", + "undo", + "undo-task", + "unpark", + "uok", + "update", + "visualize", + "widget", + "workflow", + "worktree", + "wt" + ], "hooks": [ "session_start", "session_switch", diff --git a/src/resources/extensions/sf/forensics.js b/src/resources/extensions/sf/forensics.js index 7bd09edf3..9dcbd5415 100644 --- a/src/resources/extensions/sf/forensics.js +++ b/src/resources/extensions/sf/forensics.js @@ -138,7 +138,7 @@ export async function handleForensics(args, ctx, pi) { const basePath = process.cwd(); const root = sfRuntimeRoot(basePath); if (!existsSync(root)) { - ctx.ui.notify("No SF state found. Run /sf autonomous first.", "warning"); + ctx.ui.notify("No SF state found. Run /autonomous first.", "warning"); return; } let problemDescription = args.trim(); @@ -837,9 +837,9 @@ function detectWorktreeOrphans(summary, anomalies) { summary: `${count} worktree orphan(s) detected (${reason})`, details: reason === "in-progress-unmerged" - ? "Autonomous mode exited without completing a milestone; live work sits on an unmerged milestone branch. Run `/sf autonomous` to resume, or merge manually." + ? "Autonomous mode exited without completing a milestone; live work sits on an unmerged milestone branch. Run `/autonomous` to resume, or merge manually." : reason === "complete-unmerged" - ? "A completed milestone's branch was never merged back to main. Run `/sf health --fix` to resolve." + ? "A completed milestone's branch was never merged back to main. Run `/doctor fix` to resolve." : `Reason: ${reason}.`, }); } diff --git a/src/resources/extensions/sf/gap-audit.js b/src/resources/extensions/sf/gap-audit.js index 8f8c1c164..8fef42c89 100644 --- a/src/resources/extensions/sf/gap-audit.js +++ b/src/resources/extensions/sf/gap-audit.js @@ -200,7 +200,7 @@ function findOrphanCommands() { kind: "orphan-command", name: cmd, path: relative(EXTENSION_SRC, catalogPath), - detail: `/sf ${cmd} advertised in catalog but no dispatch branch found in handlers`, + detail: `/${cmd} advertised in catalog but no dispatch branch found in handlers`, }); } } diff --git a/src/resources/extensions/sf/git-self-heal.js b/src/resources/extensions/sf/git-self-heal.js index cbe898c6e..6cedabc4c 100644 --- a/src/resources/extensions/sf/git-self-heal.js +++ b/src/resources/extensions/sf/git-self-heal.js @@ -7,7 +7,7 @@ * * Observability: Each function returns structured results describing * what actions were taken. `formatGitError` maps raw git errors to - * user-friendly messages suggesting `/sf doctor`. + * user-friendly messages suggesting `/doctor`. */ import { existsSync, unlinkSync } from "node:fs"; import { join } from "node:path"; @@ -81,34 +81,34 @@ const ERROR_PATTERNS = [ { pattern: /conflict|CONFLICT|merge conflict/i, message: - "A merge conflict occurred. Code changes on different branches touched the same files. Run `/sf doctor` to diagnose.", + "A merge conflict occurred. Code changes on different branches touched the same files. Run `/doctor` to diagnose.", }, { pattern: /cannot checkout|did not match any|pathspec .* did not match/i, message: - "Git could not switch branches — the target branch may not exist or the working tree is dirty. Run `/sf doctor` to diagnose.", + "Git could not switch branches — the target branch may not exist or the working tree is dirty. Run `/doctor` to diagnose.", }, { pattern: /HEAD detached|detached HEAD/i, message: - "Git is in a detached HEAD state — not on any branch. Run `/sf doctor` to diagnose and reattach.", + "Git is in a detached HEAD state — not on any branch. Run `/doctor` to diagnose and reattach.", }, { pattern: /\.lock|Unable to create .* lock|lock file/i, message: - "A git lock file is blocking operations. Another git process may be running, or a previous one crashed. Run `/sf doctor` to diagnose.", + "A git lock file is blocking operations. Another git process may be running, or a previous one crashed. Run `/doctor` to diagnose.", }, { pattern: /fatal: not a git repository/i, message: - "This directory is not a git repository. Run `/sf doctor` to check your project setup.", + "This directory is not a git repository. Run `/doctor` to check your project setup.", }, ]; /** * Translate raw git error strings into user-friendly messages. * * Pattern-matches against common git error strings and returns - * a non-technical message suggesting `/sf doctor`. Returns the + * a non-technical message suggesting `/doctor`. Returns the * original message if no pattern matches. */ export function formatGitError(error) { @@ -118,5 +118,5 @@ export function formatGitError(error) { return message; } } - return `A git error occurred: ${errorStr.slice(0, 200)}. Run \`/sf doctor\` for help.`; + return `A git error occurred: ${errorStr.slice(0, 200)}. Run \`/doctor\` for help.`; } diff --git a/src/resources/extensions/sf/git-service.js b/src/resources/extensions/sf/git-service.js index a2f05cca3..607d5f714 100644 --- a/src/resources/extensions/sf/git-service.js +++ b/src/resources/extensions/sf/git-service.js @@ -657,7 +657,7 @@ export class GitServiceImpl { /** * Create a snapshot ref for the given label (typically a slice branch name). * Enabled by default; opt out with prefs.snapshots === false. - * Ref path: refs/sf/snapshots/<label>/<timestamp> + * Ref path: refs/next/snapshots/<label>/<timestamp> * The ref points at HEAD, capturing the current commit before destructive operations. */ createSnapshot(label) { @@ -671,7 +671,7 @@ export class GitServiceImpl { String(now.getHours()).padStart(2, "0") + String(now.getMinutes()).padStart(2, "0") + String(now.getSeconds()).padStart(2, "0"); - const refPath = `refs/sf/snapshots/${label}/${ts}`; + const refPath = `refs/next/snapshots/${label}/${ts}`; nativeUpdateRef(this.basePath, refPath, "HEAD"); } /** diff --git a/src/resources/extensions/sf/guided-flow-queue.js b/src/resources/extensions/sf/guided-flow-queue.js index edf0bf90f..ff0559500 100644 --- a/src/resources/extensions/sf/guided-flow-queue.js +++ b/src/resources/extensions/sf/guided-flow-queue.js @@ -44,7 +44,7 @@ export async function showQueue(ctx, pi, basePath) { const sf = sfRoot(basePath); if (!existsSync(sf)) { ctx.ui.notify( - "No SF project found. Run /sf to start one first.", + "No SF project found. Run /next to start one first.", "warning", ); return; @@ -53,7 +53,7 @@ export async function showQueue(ctx, pi, basePath) { const milestoneIds = findMilestoneIds(basePath); if (milestoneIds.length === 0) { ctx.ui.notify( - "No milestones exist yet. Run /sf to create the first one.", + "No milestones exist yet. Run /next to create the first one.", "warning", ); return; @@ -90,7 +90,7 @@ export async function showQueue(ctx, pi, basePath) { description: "Queue new milestones via discussion.", }, ], - notYetMessage: "Run /sf queue when ready.", + notYetMessage: "Run /queue when ready.", }); if (choice === "reorder") { await handleQueueReorder(ctx, basePath, state); diff --git a/src/resources/extensions/sf/guided-flow.js b/src/resources/extensions/sf/guided-flow.js index d6afb89c1..cf921a6d7 100644 --- a/src/resources/extensions/sf/guided-flow.js +++ b/src/resources/extensions/sf/guided-flow.js @@ -924,7 +924,7 @@ async function buildDiscussSlicePrompt(mid, sid, sTitle, base, options) { }); } /** - * /sf discuss — show a picker of non-done slices and run a slice interview. + * /discuss — show a picker of non-done slices and run a slice interview. * Loops back to the picker after each discussion so the user can chain * multiple slice interviews in one session. */ @@ -932,7 +932,7 @@ export async function showDiscuss(ctx, pi, basePath) { // Guard: no .sf/ project if (!existsSync(sfRoot(basePath))) { ctx.ui.notify( - "No SF project found. Run /sf to start one first.", + "No SF project found. Run /next to start one first.", "warning", ); return; @@ -960,7 +960,7 @@ export async function showDiscuss(ctx, pi, basePath) { ); if (pendingMilestones.length === 0) { ctx.ui.notify( - "No active milestone. Run /sf to create one first.", + "No active milestone. Run /next to create one first.", "warning", ); return; @@ -1001,7 +1001,7 @@ export async function showDiscuss(ctx, pi, basePath) { description: "Leave this milestone as-is and start something new.", }, ], - notYetMessage: "Run /sf discuss when ready to discuss this milestone.", + notYetMessage: "Run /discuss when ready to discuss this milestone.", }); if (choice === "discuss_draft") { const discussMilestoneTemplates = inlineTemplate("context", "Context"); @@ -1102,7 +1102,7 @@ export async function showDiscuss(ctx, pi, basePath) { const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null; if (!roadmapContent && !isDbAvailable()) { ctx.ui.notify( - "No roadmap yet for this milestone. Run /sf to plan first.", + "No roadmap yet for this milestone. Run /next to plan first.", "warning", ); return; @@ -1166,8 +1166,8 @@ export async function showDiscuss(ctx, pi, basePath) { lockData.pid !== process.pid && isSessionLockProcessAlive(lockData); const nextStep = remoteAutoRunning - ? "Autonomous mode is already running — use /sf status to check progress." - : "Run /sf to start planning."; + ? "Autonomous mode is already running — use /status to check progress." + : "Run /next to start planning."; ctx.ui.notify( `All ${pendingSlices.length} slices discussed. ${nextStep}`, "info", @@ -1210,7 +1210,7 @@ export async function showDiscuss(ctx, pi, basePath) { "Pick a slice to interview. Context file will be written when done.", ], actions, - notYetMessage: "Run /sf discuss when ready.", + notYetMessage: "Run /discuss when ready.", }); if (choice === "not_yet") return; if (choice === "discuss_queued_milestone") { @@ -1294,7 +1294,7 @@ async function showDiscussQueuedMilestone( "Discussing will update its context file. It will not be activated.", ], actions, - notYetMessage: "Run /sf discuss when ready.", + notYetMessage: "Run /discuss when ready.", }); if (choice === "not_yet") return; const chosen = pendingMilestones.find((m) => m.id === choice); @@ -1323,7 +1323,7 @@ async function showDiscussQueuedMilestone( "Treat your first message as authoritative seed context; skip scouting", }, ], - notYetMessage: "Run /sf discuss when ready.", + notYetMessage: "Run /discuss when ready.", }); if (mode === "not_yet") return; fastPath = mode === "fast"; @@ -1390,8 +1390,8 @@ async function dispatchDiscussForMilestone( /** * Self-heal: scan runtime records and clear stale ones left behind when * autonomous mode crashed mid-unit. auto.ts has its own selfHealRuntimeRecords() - * but guided-flow (manual /sf mode) never called it — meaning stale records - * persisted until the next /sf autonomous run. This ensures the workflow entry + * but guided-flow (manual /mode) never called it — meaning stale records + * persisted until the next /autonomous run. This ensures the workflow entry * starts from a clean state regardless of how the previous session ended. */ function selfHealRuntimeRecords(basePath, ctx) { @@ -1472,7 +1472,7 @@ async function handleMilestoneActions( description: "Return to the previous menu.", }, ], - notYetMessage: "Run /sf when ready.", + notYetMessage: "Run /next when ready.", }); if (choice === "park") { const reason = await showNextAction(ctx, { @@ -1495,7 +1495,7 @@ async function handleMilestoneActions( description: "The approach needs to be reconsidered.", }, ], - notYetMessage: "Run /sf when ready.", + notYetMessage: "Run /next when ready.", }); // User pressed "Not yet" / Escape — cancel the park operation if (!reason || reason === "not_yet") return false; @@ -1510,7 +1510,7 @@ async function handleMilestoneActions( const success = parkMilestone(basePath, milestoneId, reasonText); if (success) { ctx.ui.notify( - `Parked ${milestoneId}. Run /sf unpark ${milestoneId} to reactivate.`, + `Parked ${milestoneId}. Run /unpark ${milestoneId} to reactivate.`, "info", ); } else { @@ -1569,7 +1569,7 @@ async function handleMilestoneActions( export async function showWorkflowEntry(ctx, pi, basePath, options) { const stepMode = options?.step; // ── Clear stale milestone ID reservations from previous cancelled sessions ── - // Reservations only need to survive within a single /sf interaction. + // Reservations only need to survive within a single /interaction. // Without this, each cancelled session permanently bumps the next ID. (#2488) clearReservedMilestoneIds(); // ── Directory safety check — refuse to operate in system/home dirs ─── @@ -1648,8 +1648,8 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { } else if (interrupted.classification === "recoverable") { if (interrupted.lock) clearLock(basePath); const resumeLabel = interrupted.pausedSession?.stepMode - ? "Resume with /sf next" - : "Resume with /sf autonomous"; + ? "Resume with /next" + : "Resume with /autonomous"; const resume = await showNextAction(ctx, { title: "SF — Interrupted Session Detected", summary: formatInterruptedSessionSummary(interrupted), @@ -1691,8 +1691,8 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { if (!(await runPlanningFlowGate(ctx, basePath, state))) return; if (!state.activeMilestone?.id) { // Guard: if a discuss session is already in flight, don't re-inject the prompt. - // Both /sf and /sf autonomous reach this branch when no milestone exists yet. - // Without this guard, every subsequent /sf call overwrites the pending auto-start + // Both /and /autonomous reach this branch when no milestone exists yet. + // Without this guard, every subsequent /call overwrites the pending auto-start // and fires another dispatchWorkflow, resetting the conversation mid-interview. if (pendingAutoStartMap.has(basePath)) { // #3274: If /clear interrupted the discussion, the pending entry is stale. @@ -1734,7 +1734,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { if (entries.length > 0) { ctx.ui.notify( `Milestone directory has ${entries.length} entries but none were recognized as milestones. ` + - `This may indicate a corrupted state or wrong working directory. Run \`/sf doctor\` to diagnose.`, + `This may indicate a corrupted state or wrong working directory. Run \`/doctor\` to diagnose.`, "warning", ); return; @@ -1769,7 +1769,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { recommended: true, }, ], - notYetMessage: "Run /sf when ready.", + notYetMessage: "Run /next when ready.", }); if (choice === "new_milestone") { await dispatchNewMilestoneDiscuss(ctx, pi, basePath, nextId, { @@ -1801,7 +1801,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { description: "Review what was built.", }, ], - notYetMessage: "Run /sf when ready.", + notYetMessage: "Run /next when ready.", }); if (choice === "new_milestone") { const milestoneIds = findMilestoneIds(basePath); @@ -1853,7 +1853,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { description: "Leave this milestone as-is and start something new.", }, ], - notYetMessage: "Run /sf when ready to discuss this milestone.", + notYetMessage: "Run /next when ready to discuss this milestone.", }); if (choice === "discuss_draft") { const discussMilestoneTemplates = inlineTemplate("context", "Context"); @@ -2002,7 +2002,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { : "New milestone — no roadmap yet.", ], actions, - notYetMessage: "Run /sf when ready.", + notYetMessage: "Run /next when ready.", }); if (choice === "plan") { pendingAutoStartMap.set(basePath, { @@ -2130,7 +2130,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { title: `SF — ${milestoneId}: ${milestoneTitle}`, summary: ["Roadmap exists. Ready to execute."], actions, - notYetMessage: "Run /sf status for details.", + notYetMessage: "Run /status for details.", }); if (choice === "auto") { startAutoDetached(ctx, pi, basePath, false); @@ -2216,7 +2216,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { title: `SF — ${milestoneId} / ${sliceId}: ${sliceTitle}`, summary: [summaryLine], actions, - notYetMessage: "Run /sf when ready.", + notYetMessage: "Run /next when ready.", }); if (choice === "plan") { const planSliceTemplates = [ @@ -2318,7 +2318,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { description: "Park, discard, or skip this milestone.", }, ], - notYetMessage: "Run /sf when ready.", + notYetMessage: "Run /next when ready.", }); if (choice === "complete") { const completeSliceTemplates = [ @@ -2407,7 +2407,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { description: "Park, discard, or skip this milestone.", }, ], - notYetMessage: "Run /sf when ready.", + notYetMessage: "Run /next when ready.", }); if (choice === "auto") { startAutoDetached(ctx, pi, basePath, false); diff --git a/src/resources/extensions/sf/health-widget-core.js b/src/resources/extensions/sf/health-widget-core.js index 15ffce6f2..85a9a4ac9 100644 --- a/src/resources/extensions/sf/health-widget-core.js +++ b/src/resources/extensions/sf/health-widget-core.js @@ -42,10 +42,10 @@ function truncateMessage(msg, maxLen) { */ export function buildHealthLines(data) { if (data.projectState === "none") { - return [" SF No project loaded — run /sf to start"]; + return [" SF No project loaded — run /next to start"]; } if (data.projectState === "initialized") { - return [" SF Project initialized — run /sf to continue setup"]; + return [" SF Project initialized — run /next to continue setup"]; } const parts = []; const totalIssues = @@ -92,7 +92,7 @@ export function buildHealthLines(data) { } // Suggest remote questions if not configured (helps autonomous mode users) if (data.projectState === "active" && !data.remoteQuestionsConfigured) { - parts.push("/sf remote telegram"); + parts.push("/remote telegram"); } return [` ${parts.join(" │ ")}`]; } diff --git a/src/resources/extensions/sf/index.js b/src/resources/extensions/sf/index.js index f39722bd8..a28b0cfa9 100644 --- a/src/resources/extensions/sf/index.js +++ b/src/resources/extensions/sf/index.js @@ -13,11 +13,12 @@ export { shouldBlockQueueExecution, } from "./bootstrap/write-gate.js"; export default async function registerExtension(pi) { - // Always register the core /sf command first, in isolation. - // This ensures /sf is available even if the full bootstrap (shortcuts, + // Always register the core SF commands first, in isolation. + // This ensures commands such as /autonomous and /plan are available even if + // the full bootstrap (shortcuts, // tools, hooks) fails — e.g. due to a Windows-specific import error. - const { registerSFCommand } = await import("./commands/index.js"); - registerSFCommand(pi); + const { registerSFCommands } = await import("./commands/index.js"); + registerSFCommands(pi); // Full setup (shortcuts, tools, hooks) in a separate try/catch so that // any platform-specific load failure doesn't take out the core command. try { @@ -29,7 +30,7 @@ export default async function registerExtension(pi) { const { logWarning } = await import("./workflow-logger.js"); logWarning( "bootstrap", - `Extension setup partially failed — /sf commands are available but shortcuts/tools may be missing: ${err instanceof Error ? err.message : String(err)}`, + `Extension setup partially failed — SF commands are available but shortcuts/tools may be missing: ${err instanceof Error ? err.message : String(err)}`, ); } } diff --git a/src/resources/extensions/sf/init-wizard.js b/src/resources/extensions/sf/init-wizard.js index 1ffd21423..f68d74d14 100644 --- a/src/resources/extensions/sf/init-wizard.js +++ b/src/resources/extensions/sf/init-wizard.js @@ -32,7 +32,7 @@ const DEFAULT_PREFS = { // ─── Main Wizard ──────────────────────────────────────────────────────────────── /** * Run the project init wizard. - * Called when entering a directory without .sf/ (or via /sf init). + * Called when entering a directory without .sf/ (or via /init). */ export async function showProjectInit(ctx, _pi, basePath, detection) { const signals = detection.projectSignals; @@ -62,7 +62,7 @@ export async function showProjectInit(ctx, _pi, basePath, detection) { description: "Continue without git (limited functionality)", }, ], - notYetMessage: "Run /sf init when ready.", + notYetMessage: "Run /init when ready.", }); if (gitChoice === "not_yet") return { completed: false, bootstrapped: false }; @@ -92,7 +92,7 @@ export async function showProjectInit(ctx, _pi, basePath, detection) { "Multiple contributors — branch-based, PR-friendly workflow", }, ], - notYetMessage: "Run /sf init when ready.", + notYetMessage: "Run /init when ready.", }); if (modeChoice === "not_yet") return { completed: false, bootstrapped: false }; @@ -128,7 +128,7 @@ export async function showProjectInit(ctx, _pi, basePath, detection) { description: "Don't verify after changes", }, ], - notYetMessage: "Run /sf init when ready.", + notYetMessage: "Run /init when ready.", }); if (verifyChoice === "not_yet") return { completed: false, bootstrapped: false }; @@ -154,7 +154,7 @@ export async function showProjectInit(ctx, _pi, basePath, detection) { description: "Change git settings", }, ], - notYetMessage: "Run /sf init when ready.", + notYetMessage: "Run /init when ready.", }); if (gitChoice === "not_yet") return { completed: false, bootstrapped: false }; if (gitChoice === "customize") { @@ -171,7 +171,7 @@ export async function showProjectInit(ctx, _pi, basePath, detection) { ' - "Always write tests for new code"', ' - "This is a monorepo, only touch packages/api"', "", - "You can always add more later via /sf prefs project.", + "You can always add more later via /prefs project.", ], actions: [ { @@ -186,7 +186,7 @@ export async function showProjectInit(ctx, _pi, basePath, detection) { description: "Enter project-specific rules", }, ], - notYetMessage: "Run /sf init when ready.", + notYetMessage: "Run /init when ready.", }); if (instructionChoice === "not_yet") return { completed: false, bootstrapped: false }; @@ -224,7 +224,7 @@ export async function showProjectInit(ctx, _pi, basePath, detection) { description: "Change advanced settings", }, ], - notYetMessage: "Run /sf init when ready.", + notYetMessage: "Run /init when ready.", }); if (advancedChoice === "not_yet") return { completed: false, bootstrapped: false }; @@ -264,8 +264,8 @@ export async function showProjectInit(ctx, _pi, basePath, detection) { } catch { // Non-fatal — codebase map generation failure should never block project init } - // Write initial STATE.md so it exists before the first /sf invocation. - // The explicit /sf init path (ops.ts) returns without entering showWorkflowEntry(), + // Write initial STATE.md so it exists before the first /invocation. + // The explicit /init path (ops.ts) returns without entering showWorkflowEntry(), // which would otherwise generate STATE.md at guided-flow.ts:1358. try { const { deriveState } = await import("./state.js"); @@ -278,7 +278,7 @@ export async function showProjectInit(ctx, _pi, basePath, detection) { buildStateMarkdown(state), ); } catch { - // Non-fatal — STATE.md will be regenerated on next /sf invocation + // Non-fatal — STATE.md will be regenerated on next /invocation } ctx.ui.notify("SF initialized. Starting your first milestone...", "info"); return { completed: true, bootstrapped: true }; @@ -314,14 +314,14 @@ export async function offerMigration(ctx, v1) { description: "Ignore .planning/ and create new .sf/", }, ], - notYetMessage: "Run /sf init when ready.", + notYetMessage: "Run /init when ready.", }); if (choice === "not_yet") return "cancel"; return choice; } // ─── Re-init Handler ──────────────────────────────────────────────────────────── /** - * Handle /sf init when .sf/ already exists. + * Handle /init when .sf/ already exists. * Offers preference reset without destructive milestone deletion. */ export async function handleReinit(ctx, detection) { @@ -348,13 +348,10 @@ export async function handleReinit(ctx, detection) { description: "Keep everything as-is", }, ], - notYetMessage: "Run /sf init when ready.", + notYetMessage: "Run /init when ready.", }); if (choice === "prefs") { - ctx.ui.notify( - "Use /sf prefs project to update project preferences.", - "info", - ); + ctx.ui.notify("Use /prefs project to update project preferences.", "info"); } } // ─── Git Preferences Customization ────────────────────────────────────────────── @@ -567,7 +564,7 @@ function buildPreferencesFile(prefs) { lines.push("# SF Project Preferences"); lines.push(""); lines.push( - "Generated by `/sf init`. Edit directly or use `/sf prefs project` to modify.", + "Generated by `/init`. Edit directly or use `/prefs project` to modify.", ); lines.push(""); lines.push( diff --git a/src/resources/extensions/sf/key-manager.js b/src/resources/extensions/sf/key-manager.js index 2196b0370..cab96b1d4 100644 --- a/src/resources/extensions/sf/key-manager.js +++ b/src/resources/extensions/sf/key-manager.js @@ -1,5 +1,5 @@ /** - * API Key Manager — /sf keys + * API Key Manager — /keys * * Comprehensive CLI for managing API keys: list, add, remove, test, rotate, doctor. * Works with AuthStorage from pi-coding-agent — no core package changes needed. @@ -382,7 +382,7 @@ export async function handleAddKey(providerArg, ctx, auth) { provider = findProvider(providerArg); if (!provider) { ctx.ui.notify( - `Unknown provider: "${providerArg}". Use /sf keys list to see available providers.`, + `Unknown provider: "${providerArg}". Use /keys list to see available providers.`, "error", ); return false; @@ -840,7 +840,7 @@ export function runKeyDoctor(auth) { findings.push({ severity: "warning", provider: provider.id, - message: `${provider.label}: empty key stored (from skipped setup) — run /sf keys add ${provider.id}`, + message: `${provider.label}: empty key stored (from skipped setup) — run /keys add ${provider.id}`, }); } } @@ -906,7 +906,7 @@ export function runKeyDoctor(auth) { if (!hasAnyLlm) { findings.push({ severity: "error", - message: "No LLM provider configured — run /sf keys add or /login", + message: "No LLM provider configured — run /keys add or /login", }); } // 7. Check for duplicate keys across providers @@ -966,7 +966,7 @@ export function formatDoctorFindings(findings) { } // ─── Main Handler ─────────────────────────────────────────────────────────────── /** - * Main entry point for /sf keys [subcommand]. + * Main entry point for /keys [subcommand]. */ export async function handleKeys(args, ctx) { const auth = getKeyManagerAuthStorage(); @@ -1046,14 +1046,14 @@ export async function handleKeys(args, ctx) { } default: ctx.ui.notify( - "Usage: /sf keys [list|add|remove|test|rotate|doctor]\n\n" + - " /sf keys Show key status dashboard\n" + - " /sf keys list List all configured keys\n" + - " /sf keys add [id] Add a key for a provider\n" + - " /sf keys remove [id] Remove a key\n" + - " /sf keys test [id] Validate key(s) with API call\n" + - " /sf keys rotate [id] Replace an existing key\n" + - " /sf keys doctor Health check all keys", + "Usage: /keys [list|add|remove|test|rotate|doctor]\n\n" + + " /keys Show key status dashboard\n" + + " /keys list List all configured keys\n" + + " /keys add [id] Add a key for a provider\n" + + " /keys remove [id] Remove a key\n" + + " /keys test [id] Validate key(s) with API call\n" + + " /keys rotate [id] Replace an existing key\n" + + " /keys doctor Health check all keys", "info", ); return; diff --git a/src/resources/extensions/sf/learning/fallback-chain-writer.mjs b/src/resources/extensions/sf/learning/fallback-chain-writer.mjs index d9ba7a0c5..6bc0ad948 100644 --- a/src/resources/extensions/sf/learning/fallback-chain-writer.mjs +++ b/src/resources/extensions/sf/learning/fallback-chain-writer.mjs @@ -342,7 +342,7 @@ function writeSettingsWithChains(settingsPath, chainsByName) { * a consistent performer across all categories. * * This replaces the earlier "clone the subagent chain" approach, which - * was task-blind: pinning a coding model via `/sf model` and then + * was task-blind: pinning a coding model via `/model` and then * dispatching `plan-slice` would yield fallbacks ranked by generalist * scores instead of planning-specific ones (combatant finding #3). * @@ -452,7 +452,7 @@ function detectProjectSettingsShadow(cwd, globalSettingsPath, log) { * Compute and write runtime fallback chains for every unit type in the * plugin's weight config, plus a `default` chain that fans across all * unit types (used when the current model isn't in any unit-specific - * chain — e.g. the user overrode the model via `/sf model`). + * chain — e.g. the user overrode the model via `/model`). * * Also checks for a project-level `.sf/agent/settings.json` that might * silently shadow the global chains via pi-ai's deep-merge, and warns diff --git a/src/resources/extensions/sf/memory-ingest.js b/src/resources/extensions/sf/memory-ingest.js index 87fe5bf15..64a64398b 100644 --- a/src/resources/extensions/sf/memory-ingest.js +++ b/src/resources/extensions/sf/memory-ingest.js @@ -8,7 +8,7 @@ // // All four functions are safe to call without an LLM — they still persist the // source. This means ingestion is decoupled from extraction; a later -// `/sf memory rebuild` can re-extract from persisted sources. +// `/memory rebuild` can re-extract from persisted sources. import { existsSync, readFileSync, statSync } from "node:fs"; import { basename, isAbsolute, resolve } from "node:path"; import { buildMemoryLLMCall, parseMemoryResponse } from "./memory-extractor.js"; diff --git a/src/resources/extensions/sf/memory-relations.js b/src/resources/extensions/sf/memory-relations.js index 137808b55..0347d6b78 100644 --- a/src/resources/extensions/sf/memory-relations.js +++ b/src/resources/extensions/sf/memory-relations.js @@ -4,7 +4,7 @@ // (a) `applyMemoryActions` auto-linking co-extracted memories with // `related_to` (confidence 0.5) — same-batch memories from the // extractor share narrative context. -// (b) `/sf memory import` loading explicit edges from a JSON export. +// (b) `/memory import` loading explicit edges from a JSON export. // Read consumers: // (1) `getRelevantMemoriesRanked` walks edges of cosine top-N memories // and applies a one-pass intra-pool score boost (damping 0.4). diff --git a/src/resources/extensions/sf/memory-sleeper.js b/src/resources/extensions/sf/memory-sleeper.js index 86b9e22cb..d9ef54185 100644 --- a/src/resources/extensions/sf/memory-sleeper.js +++ b/src/resources/extensions/sf/memory-sleeper.js @@ -68,7 +68,7 @@ function maybeKnownFailureSteer(text) { severity: "warning", content: buildSteer( "SF dispatcher import failed", - "The installed SF command extension is trying to import commands/dispatcher.js and cannot resolve it. Check the extension sync/build path before retrying /sf auto.", + "The installed SF command extension is trying to import commands/dispatcher.js and cannot resolve it. Check the extension sync/build path before retrying /auto.", ), }); } diff --git a/src/resources/extensions/sf/migrate/command.js b/src/resources/extensions/sf/migrate/command.js index c4b7eda3d..eb3610922 100644 --- a/src/resources/extensions/sf/migrate/command.js +++ b/src/resources/extensions/sf/migrate/command.js @@ -1,5 +1,5 @@ /** - * /sf migrate — one-shot migration from .planning to .sf + * /migrate — one-shot migration from .planning to .sf * * Thin UX orchestrator: resolves paths, runs the validate → parse → transform → * preview → write pipeline, and shows confirmation UI via showNextAction. @@ -73,7 +73,7 @@ export async function handleMigrate(args, ctx, pi) { ctx.ui.notify( `Directory not found: ${sourcePath}\n\n` + "Migration converts a .planning/ directory (from older SF versions) into .sf/ format.\n" + - "If you are starting a new project, use /sf:new-project instead.\n" + + "If you are starting a new project, use /init instead.\n" + "If migrating, ensure the path contains a .planning/ directory.", "error", ); @@ -135,7 +135,7 @@ export async function handleMigrate(args, ctx, pi) { description: "Exit without writing anything", }, ], - notYetMessage: "Run /sf migrate again when ready.", + notYetMessage: "Run /migrate again when ready.", }); if (choice !== "confirm") { ctx.ui.notify("Migration cancelled — no files were written.", "info"); @@ -173,8 +173,7 @@ export async function handleMigrate(args, ctx, pi) { description: "Trust the migration output as-is", }, ], - notYetMessage: - "Run /sf migrate again to re-migrate, or review .sf manually.", + notYetMessage: "Run /migrate again to re-migrate, or review .sf manually.", }); if (reviewChoice === "review") { dispatchReview(pi, sourcePath, sfPath, preview); diff --git a/src/resources/extensions/sf/migrate/writer.js b/src/resources/extensions/sf/migrate/writer.js index 98ffc49c5..135fc3fbf 100644 --- a/src/resources/extensions/sf/migrate/writer.js +++ b/src/resources/extensions/sf/migrate/writer.js @@ -305,7 +305,7 @@ export function formatContext(milestoneId) { /** * Format STATE.md. * deriveState() does not read STATE.md — it recomputes from scratch. - * Write a minimal stub that will be overwritten on first /sf status. + * Write a minimal stub that will be overwritten on first /status. */ export function formatState(milestones) { const lines = []; diff --git a/src/resources/extensions/sf/milestone-actions.js b/src/resources/extensions/sf/milestone-actions.js index 9cb1e114a..618f40e56 100644 --- a/src/resources/extensions/sf/milestone-actions.js +++ b/src/resources/extensions/sf/milestone-actions.js @@ -86,7 +86,7 @@ export function unparkMilestone(basePath, milestoneId) { const dbThinksParked = isDbAvailable() && getMilestone(milestoneId)?.status === "parked"; // Recover the reverse desync too: DB can still say "parked" even when the - // PARKED marker was lost on disk, and /sf unpark should repair that state. + // PARKED marker was lost on disk, and /unpark should repair that state. if (!hadParkedFile && !dbThinksParked) return false; if (hadParkedFile) { unlinkSync(parkedPath); diff --git a/src/resources/extensions/sf/notification-overlay.js b/src/resources/extensions/sf/notification-overlay.js index 492c4b32f..d5698327c 100644 --- a/src/resources/extensions/sf/notification-overlay.js +++ b/src/resources/extensions/sf/notification-overlay.js @@ -1,6 +1,6 @@ // SF Extension — Notification History Overlay // Scrollable panel showing all persisted notifications with severity filtering. -// Toggled with Ctrl+Alt+N (⌃⌥N on macOS), Ctrl+Shift+N fallback, or /sf notifications. +// Toggled with Ctrl+Alt+N (⌃⌥N on macOS), Ctrl+Shift+N fallback, or /notifications. import { Key, matchesKey, diff --git a/src/resources/extensions/sf/parallel-merge.js b/src/resources/extensions/sf/parallel-merge.js index 3a1eed70f..78c70e14e 100644 --- a/src/resources/extensions/sf/parallel-merge.js +++ b/src/resources/extensions/sf/parallel-merge.js @@ -222,7 +222,7 @@ export function formatMergeResults(results) { lines.push(` - \`${f}\``); } lines.push( - ` Resolve conflicts manually and run \`/sf parallel merge ${r.milestoneId}\` to retry.`, + ` Resolve conflicts manually and run \`/parallel merge ${r.milestoneId}\` to retry.`, ); } else { lines.push(`- **${r.milestoneId}** — failed: ${r.error}`); diff --git a/src/resources/extensions/sf/parallel-monitor-overlay.js b/src/resources/extensions/sf/parallel-monitor-overlay.js index a8179e736..eaf17da9d 100644 --- a/src/resources/extensions/sf/parallel-monitor-overlay.js +++ b/src/resources/extensions/sf/parallel-monitor-overlay.js @@ -2,7 +2,7 @@ * SF Parallel Monitor Overlay * * Full-screen TUI overlay showing real-time parallel worker progress. - * Opened via `/sf parallel watch`, Ctrl+Alt+P (⌃⌥P on macOS), + * Opened via `/parallel watch`, Ctrl+Alt+P (⌃⌥P on macOS), * or Ctrl+Shift+P fallback. * Reads the same data sources as `scripts/parallel-monitor.mjs` but * renders as a native pi-tui overlay with theme integration. @@ -367,7 +367,7 @@ export class ParallelMonitorOverlay { if (this.workers.length === 0) { lines.push(""); lines.push(t.fg("warning", " No parallel workers found.")); - lines.push(t.fg("muted", " Run /sf parallel start to begin.")); + lines.push(t.fg("muted", " Run /parallel start to begin.")); } else { for (const wk of this.workers) { lines.push(""); diff --git a/src/resources/extensions/sf/parallel-orchestrator.js b/src/resources/extensions/sf/parallel-orchestrator.js index 42dd9515c..105238cf0 100644 --- a/src/resources/extensions/sf/parallel-orchestrator.js +++ b/src/resources/extensions/sf/parallel-orchestrator.js @@ -496,7 +496,7 @@ function createMilestoneWorktree(basePath, milestoneId) { * The worker runs `sf headless --json autonomous` in the milestone's worktree * with SF_MILESTONE_LOCK set to isolate state derivation. * - * IMPORTANT: We use `headless --json autonomous` instead of `--print "/sf autonomous"`. + * IMPORTANT: We use `headless --json autonomous` instead of `--print "/autonomous"`. * --print mode calls session.prompt() which returns immediately after the * extension command handler fires, because autonomous mode's ctx.newSession() * resets the session and unblocks the outer prompt() await. This causes diff --git a/src/resources/extensions/sf/planning-depth.js b/src/resources/extensions/sf/planning-depth.js index 449e0e614..f423da377 100644 --- a/src/resources/extensions/sf/planning-depth.js +++ b/src/resources/extensions/sf/planning-depth.js @@ -43,7 +43,7 @@ export function writeDefaultResearchSkipDecision(basePath) { * Set planning_depth in the project's .sf/PREFERENCES.md. * Creates the file if it does not exist. Preserves existing frontmatter * keys and body content. Intended to be called when the user opts into - * (or out of) deep mode via `/sf new-project --deep` or similar. + * (or out of) deep mode via `/new-project --deep` or similar. */ export function setPlanningDepth(basePath, depth) { const path = getProjectSFPreferencesFilePath(basePath); diff --git a/src/resources/extensions/sf/preferences-models.js b/src/resources/extensions/sf/preferences-models.js index 99183ef4a..73f83bf3b 100644 --- a/src/resources/extensions/sf/preferences-models.js +++ b/src/resources/extensions/sf/preferences-models.js @@ -493,7 +493,7 @@ export function resolveDefaultSessionModel(sessionProvider) { * proxies, etc.). * * Used by autonomous mode bootstrap to decide whether the session model - * (set via `/sf model`) should override `PREFERENCES.md`. Custom providers + * (set via `/model`) should override `PREFERENCES.md`. Custom providers * are never reachable from `PREFERENCES.md` (which only knows built-in * providers), so when the user has explicitly selected one, it must take * priority — otherwise autonomous mode tries to start the built-in provider from diff --git a/src/resources/extensions/sf/prompts/discuss.md b/src/resources/extensions/sf/prompts/discuss.md index bdcb2746e..925996ce6 100644 --- a/src/resources/extensions/sf/prompts/discuss.md +++ b/src/resources/extensions/sf/prompts/discuss.md @@ -385,8 +385,8 @@ If a milestone has no dependencies, omit the frontmatter. The dependency chain f For each remaining milestone **one at a time, in sequence**, decide the most likely readiness mode from the evidence you already have, then present the three options below to the user. **If `{{structuredQuestionsAvailable}}` is `true`:** use `ask_user_questions`. **If `{{structuredQuestionsAvailable}}` is `false`:** present the options as a plain-text numbered list and ask the user to type their choice. **Non-bypassable:** If the user does not respond, gives an ambiguous answer, or the tool fails, you MUST re-ask — never rationalize past the block or auto-select a readiness mode. Present three options: - **"Discuss now"** — The user wants to conduct a focused discussion for this milestone in the current session, while the context from the broader discussion is still fresh. Proceed with a focused discussion for this milestone (reflection → investigation → questioning → depth verification). When the discussion concludes, write a full `CONTEXT.md`. Then move to the gate for the next milestone. -- **"Write draft for later"** — This milestone has seed material from the current conversation but needs its own dedicated discussion in a future session. Write a `CONTEXT-DRAFT.md` capturing the seed material (what was discussed, key ideas, provisional scope, open questions). Mark it clearly as a draft, not a finalized context. **What happens downstream:** When autonomous mode reaches this milestone, it pauses and notifies the user: "M00x has draft context — needs discussion. Run /sf." The `/sf` wizard shows a "Discuss from draft" option that seeds the new discussion with this draft, so nothing from the current conversation is lost. After the dedicated discussion produces a full CONTEXT.md, the draft file is automatically deleted. -- **"Just queue it"** — This milestone is identified but intentionally left without context. No context file is written — the directory already exists from Phase 1. **What happens downstream:** When autonomous mode reaches this milestone, it pauses and notifies the user to run /sf. The wizard starts a full discussion from scratch. +- **"Write draft for later"** — This milestone has seed material from the current conversation but needs its own dedicated discussion in a future session. Write a `CONTEXT-DRAFT.md` capturing the seed material (what was discussed, key ideas, provisional scope, open questions). Mark it clearly as a draft, not a finalized context. **What happens downstream:** When autonomous mode reaches this milestone, it pauses and notifies the user: "M00x has draft context — needs discussion. Run /next." The `/next` wizard shows a "Discuss from draft" option that seeds the new discussion with this draft, so nothing from the current conversation is lost. After the dedicated discussion produces a full CONTEXT.md, the draft file is automatically deleted. +- **"Just queue it"** — This milestone is identified but intentionally left without context. No context file is written — the directory already exists from Phase 1. **What happens downstream:** When autonomous mode reaches this milestone, it pauses and notifies the user to run /next. The wizard starts a full discussion from scratch. **When "Discuss now" is chosen — Technical Assumption Verification is MANDATORY:** diff --git a/src/resources/extensions/sf/prompts/doctor-heal.md b/src/resources/extensions/sf/prompts/doctor-heal.md index 0c849a702..ce4347c0d 100644 --- a/src/resources/extensions/sf/prompts/doctor-heal.md +++ b/src/resources/extensions/sf/prompts/doctor-heal.md @@ -9,7 +9,7 @@ Rules: 4. For missing summaries or UAT files, generate the real artifact from existing slice/task context when possible — do not leave placeholders if you can reconstruct the real content. 5. For a missing milestone `CONTEXT.md` when the milestone is already past `pre-planning` (phase is `executing`, `summarizing`, `validating-milestone`, or `completing-milestone`): the artifact was skipped during bootstrap and must be reconstructed before execution can resume. Read `PROJECT.md`, `REQUIREMENTS.md`, the milestone's `ROADMAP.md`, and any slice-level context on disk, then call `sf_summary_save` with `artifact_type: "CONTEXT"` and the reconstructed context as `content` — the tool writes `.sf/milestones/{{milestoneId}}/{{milestoneId}}-CONTEXT.md` to disk and persists to DB. Do not leave a stub — the plan gate will reject it on the next cycle. 6. After each repair cluster, verify the relevant invariant directly from disk. -7. When done, rerun `/sf doctor {{doctorCommandSuffix}}` mentally by ensuring the remaining issue set for this scope is reduced or cleared. +7. When done, rerun `/doctor {{doctorCommandSuffix}}` mentally by ensuring the remaining issue set for this scope is reduced or cleared. 8. Do NOT query `.sf/sf.db` directly via `sqlite3` or `node -e require('better-sqlite3')` — use `sf_milestone_status` to inspect DB state. Direct access bypasses the WAL connection owned by the engine and can corrupt in-flight writes. ## Doctor Summary diff --git a/src/resources/extensions/sf/prompts/forensics.md b/src/resources/extensions/sf/prompts/forensics.md index 432d4d136..a866b70bc 100644 --- a/src/resources/extensions/sf/prompts/forensics.md +++ b/src/resources/extensions/sf/prompts/forensics.md @@ -169,7 +169,7 @@ cat > /tmp/sf-forensic-issue.md << 'SF_ISSUE_BODY' [Key anomalies, error traces, relevant tool call sequences from the report] --- -*Auto-generated by `/sf forensics`* +*Auto-generated by `/forensics`* SF_ISSUE_BODY ISSUE_URL=$(gh issue create --repo singularity-forge/sf-run \ diff --git a/src/resources/extensions/sf/prompts/review-migration.md b/src/resources/extensions/sf/prompts/review-migration.md index f24b1d8e4..c6e74a36c 100644 --- a/src/resources/extensions/sf/prompts/review-migration.md +++ b/src/resources/extensions/sf/prompts/review-migration.md @@ -1,6 +1,6 @@ ## Review Migrated .sf Directory -A `/sf migrate` command just wrote a `.sf/` directory from an old `.planning` source. Your job is to audit the output and verify it meets SF standards before the user starts working with it. +A `/migrate` command just wrote a `.sf/` directory from an old `.planning` source. Your job is to audit the output and verify it meets SF standards before the user starts working with it. ### Source - Old `.planning` directory: `{{sourcePath}}` diff --git a/src/resources/extensions/sf/prompts/system.md b/src/resources/extensions/sf/prompts/system.md index 3282fdf39..2361081e4 100644 --- a/src/resources/extensions/sf/prompts/system.md +++ b/src/resources/extensions/sf/prompts/system.md @@ -77,8 +77,8 @@ Titles live inside file content (headings, frontmatter), not in file or director DECISIONS.md (append-only register of architectural and pattern decisions) KNOWLEDGE.md (append-only register of project-specific rules, patterns, and lessons learned) CODEBASE.md (generated fallback codebase map cache — auto-refreshed when tracked files change) - OVERRIDES.md (user-issued overrides that supersede plan content via /sf steer) - QUEUE.md (append-only log of queued milestones via /sf queue) + OVERRIDES.md (user-issued overrides that supersede plan content via /steer) + QUEUE.md (append-only log of queued milestones via /queue) STATE.md runtime/ (system-managed — dispatch state, do not edit) activity/ (system-managed — JSONL execution logs, do not edit) @@ -119,7 +119,7 @@ In all modes, slices commit sequentially on the active branch; there are no per- - **REQUIREMENTS.md** tracks the requirement contract — requirements move between Active, Validated, Deferred, Blocked, and Out of Scope as slices prove or invalidate them. Update at slice completion when evidence supports a status change. - **DECISIONS.md** is an append-only register of architectural and pattern decisions - read it during planning/research, append to it during execution when a meaningful decision is made - **KNOWLEDGE.md** is an append-only register of project-specific rules, patterns, and lessons learned. Read it at the start of every unit. Append to it when you discover a recurring issue, a non-obvious pattern, or a rule that future agents should follow. -- **CODEBASE.md** is a generated fallback snapshot of the tracked repository. SF may inject it when available, but healthy Sift is the preferred live code index. Use CODEBASE only when Sift is unavailable, cold, degraded, or when you need a durable overview. Use `/sf codebase update` only when you need to force an immediate refresh. +- **CODEBASE.md** is a generated fallback snapshot of the tracked repository. SF may inject it when available, but healthy Sift is the preferred live code index. Use CODEBASE only when Sift is unavailable, cold, degraded, or when you need a durable overview. Use `/codebase update` only when you need to force an immediate refresh. - **CONTEXT.md** files (milestone or slice level) capture the brief — scope, goals, constraints, and key decisions from discussion. When present, they are the authoritative source for what a milestone or slice is trying to achieve. Read them before planning or executing. - **Milestones** are major project phases (M001, M002, ...) - **Slices** are demoable vertical increments (S01, S02, ...) ordered by risk. After each slice completes, the roadmap is reassessed before the next slice begins. @@ -141,13 +141,13 @@ Templates showing the expected format for each artifact type are in: ### Commands -- `/sf` - contextual wizard -- `/sf autonomous` - auto-execute (fresh context per task) -- `/sf stop` - stop autonomous mode -- `/sf status` - progress dashboard overlay -- `/sf queue` - queue future milestones (safe while autonomous mode is running) -- `/sf quick <task>` - quick task with SF guarantees (atomic commits, state tracking) but no milestone ceremony -- `/sf codebase [generate|update|stats|indexer]` - manage fallback `.sf/CODEBASE.md` and Sift code search +- `/next` - contextual wizard +- `/autonomous` - auto-execute (fresh context per task) +- `/autonomous stop` - stop autonomous mode +- `/status` - progress dashboard overlay +- `/queue` - queue future milestones (safe while autonomous mode is running) +- `/quick <task>` - quick task with SF guarantees (atomic commits, state tracking) but no milestone ceremony +- `/codebase [generate|update|stats|indexer]` - manage fallback `.sf/CODEBASE.md` and Sift code search - `{{shortcutDashboard}}` - toggle dashboard overlay - `{{shortcutShell}}` - show shell processes diff --git a/src/resources/extensions/sf/prompts/triage-captures.md b/src/resources/extensions/sf/prompts/triage-captures.md index 69e0ebe86..0e1d850b1 100644 --- a/src/resources/extensions/sf/prompts/triage-captures.md +++ b/src/resources/extensions/sf/prompts/triage-captures.md @@ -2,7 +2,7 @@ You are triaging user-captured thoughts during a SF session. ## UNIT: Triage Captures -The user captured thoughts during execution using `/sf capture`. Your job is to classify each capture, present your proposals, get user confirmation, and update CAPTURES.md with the final classifications. +The user captured thoughts during execution using `/capture`. Your job is to classify each capture, present your proposals, get user confirmation, and update CAPTURES.md with the final classifications. ## Pending Captures diff --git a/src/resources/extensions/sf/provider-error-pause.js b/src/resources/extensions/sf/provider-error-pause.js index 3210b2eda..108cf6f27 100644 --- a/src/resources/extensions/sf/provider-error-pause.js +++ b/src/resources/extensions/sf/provider-error-pause.js @@ -22,11 +22,11 @@ export async function pauseAutoForProviderError( ? "Rate limited" : "Server error (transient)"; ui.notify( - `${reason}${errorDetail}. Auto-resuming in ${delaySec}s...`, + `${reason}${errorDetail}. Resuming automatically in ${delaySec}s...`, "warning", ); await pause(); - // Schedule auto-resume after the delay + // Schedule resume after the delay. setTimeout(() => { const resumeMsg = options.isRateLimit ? "Rate limit window elapsed. Resuming autonomous mode." diff --git a/src/resources/extensions/sf/quick.js b/src/resources/extensions/sf/quick.js index 34e557888..6062d9028 100644 --- a/src/resources/extensions/sf/quick.js +++ b/src/resources/extensions/sf/quick.js @@ -1,5 +1,5 @@ /** - * SF Quick Mode — /sf quick <task> + * SF Quick Mode — /quick <task> * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net> * * Lightweight task execution with SF guarantees (atomic commits, state @@ -147,7 +147,7 @@ export async function handleQuick(args, ctx, pi) { // Validate: .sf/ must exist if (!existsSync(root)) { ctx.ui.notify( - "No .sf/ directory found. Run /sf to initialize a project first.", + "No .sf/ directory found. Run /init to initialize a project first.", "error", ); return; @@ -156,7 +156,7 @@ export async function handleQuick(args, ctx, pi) { const description = args.trim(); if (!description) { ctx.ui.notify( - "Usage: /sf quick <task description>\n\nExample: /sf quick fix login button not responding on mobile", + "Usage: /quick <task description>\n\nExample: /quick fix login button not responding on mobile", "info", ); return; diff --git a/src/resources/extensions/sf/repo-identity.js b/src/resources/extensions/sf/repo-identity.js index 71c250f0c..4fbb61cb5 100644 --- a/src/resources/extensions/sf/repo-identity.js +++ b/src/resources/extensions/sf/repo-identity.js @@ -111,7 +111,7 @@ export function readRepoMeta(externalPath) { * cross-project state leaks (#1639). * * When the git root already has a project `.sf`, the directory is a - * legitimate subdirectory of an existing SF project — `cd src/ && /sf` + * legitimate subdirectory of an existing SF project — `cd src/ && /next` * should still load the parent project's milestones. */ export function isInheritedRepo(basePath) { diff --git a/src/resources/extensions/sf/repo-profiler.js b/src/resources/extensions/sf/repo-profiler.js index 5f77d4e81..32a581974 100644 --- a/src/resources/extensions/sf/repo-profiler.js +++ b/src/resources/extensions/sf/repo-profiler.js @@ -246,7 +246,7 @@ function canonicalRemote(remote) { * Purpose: seed harness evolution with observable repo facts while preserving * user ownership of untracked and modified files. * - * Consumer: `/sf harness profile` and future pre-plan harness snapshots. + * Consumer: `/harness profile` and future pre-plan harness snapshots. */ export function profileRepository(basePath, options = {}) { const createdAt = options.now?.() ?? new Date().toISOString(); diff --git a/src/resources/extensions/sf/reports.js b/src/resources/extensions/sf/reports.js index 762e8ca1e..c8b85b8ca 100644 --- a/src/resources/extensions/sf/reports.js +++ b/src/resources/extensions/sf/reports.js @@ -11,7 +11,7 @@ * final-20260201T090000.html full-project final snapshot * * Auto-triggered: after each milestone completion (when auto_report: true). - * Manual: /sf export --html + * Manual: /export --html */ import { existsSync, mkdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; @@ -255,7 +255,7 @@ function buildIndexHtml(index) { ${ sorted.length > 0 ? `<div class="cards-grid">${cardHtml}</div>` - : '<p class="empty">No reports generated yet. Run <code>/sf export --html</code> or enable <code>auto_report: true</code>.</p>' + : '<p class="empty">No reports generated yet. Run <code>/export --html</code> or enable <code>auto_report: true</code>.</p>' } </section> </main> diff --git a/src/resources/extensions/sf/rethink.js b/src/resources/extensions/sf/rethink.js index a5a68f741..35cc54c6b 100644 --- a/src/resources/extensions/sf/rethink.js +++ b/src/resources/extensions/sf/rethink.js @@ -29,7 +29,7 @@ export async function handleRethink(_args, ctx, pi) { const basePath = process.cwd(); const root = sfRoot(basePath); if (!existsSync(root)) { - ctx.ui.notify("No SF project found. Run /sf init first.", "warning"); + ctx.ui.notify("No SF project found. Run /init first.", "warning"); return; } ctx.ui.notify("Building project snapshot for rethink...", "info"); diff --git a/src/resources/extensions/sf/rule-registry.js b/src/resources/extensions/sf/rule-registry.js index 5ced74d2d..b4adc565a 100644 --- a/src/resources/extensions/sf/rule-registry.js +++ b/src/resources/extensions/sf/rule-registry.js @@ -156,7 +156,7 @@ export class RuleRegistry { } return { action: "stop", - reason: `Unhandled phase "${ctx.state.phase}" — run /sf doctor to diagnose.`, + reason: `Unhandled phase "${ctx.state.phase}" — run /doctor to diagnose.`, level: "info", matchedRule: "<no-match>", }; diff --git a/src/resources/extensions/sf/run-manager.js b/src/resources/extensions/sf/run-manager.js index 79c1cbd44..66e7f8a1c 100644 --- a/src/resources/extensions/sf/run-manager.js +++ b/src/resources/extensions/sf/run-manager.js @@ -128,7 +128,7 @@ export function createRun(basePath, defName, overrides) { /** * Create a new isolated run directory from an already-built definition. * - * Purpose: let non-YAML authoring surfaces, such as `/sf start` templates, + * Purpose: let non-YAML authoring surfaces, such as `/start` templates, * enter the same graph-backed runtime as custom YAML definitions. * * Consumer: workflow template commands after resolving and compiling a template. diff --git a/src/resources/extensions/sf/safety/git-checkpoint.js b/src/resources/extensions/sf/safety/git-checkpoint.js index 87eabb869..3d404b32a 100644 --- a/src/resources/extensions/sf/safety/git-checkpoint.js +++ b/src/resources/extensions/sf/safety/git-checkpoint.js @@ -1,6 +1,6 @@ /** * Pre-unit git checkpoint and rollback for autonomous mode safety harness. - * Uses the existing refs/sf/ namespace (already pruned by doctor). + * Uses the existing refs/next/ namespace (already pruned by doctor). * * Creates a lightweight ref at HEAD before unit execution. On failure, * the ref can be used to rollback the branch to the pre-unit state. @@ -11,7 +11,7 @@ import { execFileSync } from "node:child_process"; import { logWarning } from "../workflow-logger.js"; // ─── Constants ────────────────────────────────────────────────────────────── -const CHECKPOINT_PREFIX = "refs/sf/checkpoints/"; +const CHECKPOINT_PREFIX = "refs/next/checkpoints/"; /** * Sanitize a unitId for use as a git ref component. * Git ref names prohibit: spaces, control chars, ~, ^, :, ?, *, [, \, .., diff --git a/src/resources/extensions/sf/schedule-launch-banner.js b/src/resources/extensions/sf/schedule-launch-banner.js index 9671bac27..8e0ea59cd 100644 --- a/src/resources/extensions/sf/schedule-launch-banner.js +++ b/src/resources/extensions/sf/schedule-launch-banner.js @@ -2,7 +2,7 @@ * Schedule Launch Banner — one-line stderr banner for due scheduled items. * * Purpose: surface pending schedule entries to the user on every sf launch - * so reminders and due tasks are visible without running /sf schedule list. + * so reminders and due tasks are visible without running /schedule list. * * Consumer: cli.ts interactive startup path. */ @@ -53,7 +53,7 @@ export function printScheduleBanner(basePath) { passive.length === 1 ? "scheduled item due" : "scheduled items due"; process.stderr.write( - `[forge] ${passive.length} ${label}: ${titles.join(", ")}${more}. Manage: /sf schedule list\n`, + `[forge] ${passive.length} ${label}: ${titles.join(", ")}${more}. Manage: /schedule list\n`, ); } diff --git a/src/resources/extensions/sf/schedule/schedule-autonomous-dispatch.js b/src/resources/extensions/sf/schedule/schedule-autonomous-dispatch.js index f664cfa37..b8c7ba733 100644 --- a/src/resources/extensions/sf/schedule/schedule-autonomous-dispatch.js +++ b/src/resources/extensions/sf/schedule/schedule-autonomous-dispatch.js @@ -89,7 +89,7 @@ export function markProjectScheduleCancelled(basePath, entry, reason) { * Purpose: make `kind: "command", autonomous_dispatch: true` behave like a repo cron * job in autonomous mode, with durable success/failure status in `.sf`. * - * Consumer: auto-dispatch.js schedule rule and `/sf schedule run`. + * Consumer: auto-dispatch.js schedule rule and `/schedule run`. * * @param {string} basePath * @param {import("./schedule-types.js").ScheduleEntry} entry diff --git a/src/resources/extensions/sf/service-tier.js b/src/resources/extensions/sf/service-tier.js index 8ecbd4852..bf0925f6c 100644 --- a/src/resources/extensions/sf/service-tier.js +++ b/src/resources/extensions/sf/service-tier.js @@ -1,6 +1,6 @@ /** * Service Tier — gating, status formatting, icon resolution, and - * the /sf fast command handler. + * the /fast command handler. * * Service tiers (priority/flex) are an OpenAI feature that only applies * to gpt-5.4 variants. This module centralizes the model-gating logic @@ -60,9 +60,9 @@ export function formatServiceTierStatus(tier) { header, "", "Usage:", - " /sf fast on Set to priority (2x cost, faster)", - " /sf fast flex Set to flex (0.5x cost, slower)", - " /sf fast off Disable service tier", + " /fast on Set to priority (2x cost, faster)", + " /fast flex Set to flex (0.5x cost, slower)", + " /fast off Disable service tier", "", SERVICE_TIER_SCOPE_NOTE, ].join("\n"); @@ -75,9 +75,9 @@ export function formatServiceTierStatus(tier) { `Service tier: ${label}`, "", "Usage:", - " /sf fast on Set to priority (2x cost, faster)", - " /sf fast flex Set to flex (0.5x cost, slower)", - " /sf fast off Disable service tier", + " /fast on Set to priority (2x cost, faster)", + " /fast flex Set to flex (0.5x cost, slower)", + " /fast off Disable service tier", "", SERVICE_TIER_SCOPE_NOTE, ].join("\n"); @@ -156,7 +156,7 @@ async function writeGlobalServiceTier(ctx, tier) { } // ─── Command Handler ───────────────────────────────────────────────────────── /** - * Handle `/sf fast [on|off|flex|status]`. + * Handle `/fast [on|off|flex|status]`. */ export async function handleFast(args, ctx) { const trimmed = args.trim().toLowerCase(); @@ -196,7 +196,7 @@ export async function handleFast(args, ctx) { return; } ctx.ui.notify( - "Usage: /sf fast [on|off|flex|status]\n\n on Priority tier (2x cost, faster)\n off Disable service tier\n flex Flex tier (0.5x cost, slower)\n status Show current setting", + "Usage: /fast [on|off|flex|status]\n\n on Priority tier (2x cost, faster)\n off Disable service tier\n flex Flex tier (0.5x cost, slower)\n status Show current setting", "warning", ); } diff --git a/src/resources/extensions/sf/session-lock.js b/src/resources/extensions/sf/session-lock.js index bfdeab9c1..836695d45 100644 --- a/src/resources/extensions/sf/session-lock.js +++ b/src/resources/extensions/sf/session-lock.js @@ -274,7 +274,7 @@ export function acquireSessionLock(basePath, sessionInfo) { // #3218: Pre-flight stale lock cleanup — if the .lock/ directory exists but // no auto.lock metadata is present (or the PID is dead), remove the lock // directory before attempting acquisition. This prevents the 30-min stale - // window from blocking /sf after crashes, SIGKILL, or laptop sleep. + // window from blocking /after crashes, SIGKILL, or laptop sleep. const lockDir = lockTarget + ".lock"; if (existsSync(lockDir)) { const existingData = readExistingLockData(lp); diff --git a/src/resources/extensions/sf/session-model-override.js b/src/resources/extensions/sf/session-model-override.js index 326b0e163..caad548a8 100644 --- a/src/resources/extensions/sf/session-model-override.js +++ b/src/resources/extensions/sf/session-model-override.js @@ -3,7 +3,7 @@ function normalizeSessionId(sessionId) { return typeof sessionId === "string" ? sessionId.trim() : ""; } /** - * Store a model override for a session (e.g., from /sf model command in autonomous mode). + * Store a model override for a session (e.g., from /model command in autonomous mode). */ export function setSessionModelOverride(sessionId, override) { const key = normalizeSessionId(sessionId); diff --git a/src/resources/extensions/sf/setup-catalog.js b/src/resources/extensions/sf/setup-catalog.js index 9025b5d3a..4df9675d5 100644 --- a/src/resources/extensions/sf/setup-catalog.js +++ b/src/resources/extensions/sf/setup-catalog.js @@ -1,7 +1,7 @@ // SF — Setup catalog (single source of truth for onboarding steps + provider sub-views) // // Re-exports filtered views over PROVIDER_REGISTRY (key-manager.ts) and owns the -// canonical ONBOARDING_STEPS list. Consumers (CLI wizard, /sf setup hub, +// canonical ONBOARDING_STEPS list. Consumers (CLI wizard, /setup hub, // onboarding handler, web alignment) all read from here so adding a step or // provider lands in one place. Keep this module thin: no behavior beyond // filters + lookup helpers, so it stays cycle-safe even though it depends on diff --git a/src/resources/extensions/sf/sf-db.js b/src/resources/extensions/sf/sf-db.js index 172aad015..92fbb0663 100644 --- a/src/resources/extensions/sf/sf-db.js +++ b/src/resources/extensions/sf/sf-db.js @@ -621,7 +621,7 @@ function initSchema(db, fileBacked) { db.exec( "CREATE INDEX IF NOT EXISTS idx_memory_sources_content_hash ON memory_sources(content_hash)", ); - // Category GROUP BY queries (e.g. /sf memory stats) need a covering + // Category GROUP BY queries (e.g. /memory stats) need a covering // index that filters active memories and groups by category. db.exec( "CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(superseded_by, category)", @@ -2937,7 +2937,7 @@ export function autoHealSketchFlags(milestoneId, hasPlanFile) { * (pending OR awaiting_review); pass includeResolved=true to also return * resolved-but-still-recorded entries (audit trail). * - * Used by `/sf escalate list` to enumerate cross-slice escalations. + * Used by `/escalate list` to enumerate cross-slice escalations. */ export function listEscalationArtifacts(milestoneId, includeResolved = false) { if (!currentDb) return []; @@ -3769,7 +3769,7 @@ export function updateMilestoneStatus(milestoneId, status, completedAt) { * Purpose: make roadmap priority/order queryable and schema-owned instead of * relying on `.sf/QUEUE-ORDER.json` as a peer source of truth. * - * Consumer: queue-order.js when `/sf queue` or rethink reorders milestones. + * Consumer: queue-order.js when `/queue` or rethink reorders milestones. */ export function updateMilestoneQueueOrder(order) { if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open"); @@ -5483,7 +5483,7 @@ function scheduleEntryFromRow(row) { * Purpose: keep time-bound reminders in structured SQLite state so status, * due-date, and scope queries are schema-owned instead of JSONL-owned. * - * Consumer: schedule-store.js for /sf schedule and launch/auto due-item checks. + * Consumer: schedule-store.js for /schedule and launch/auto due-item checks. */ export function insertScheduleEntry(scope, entry, importedFrom = null) { if (!currentDb) return; @@ -5571,7 +5571,7 @@ function asStringOrNull(value) { * Purpose: make harness evolution's read-only repo facts queryable across * sessions while preserving first-seen timestamps for untracked observations. * - * Consumer: `/sf harness profile` and future pre-plan profile snapshots. + * Consumer: `/harness profile` and future pre-plan profile snapshots. */ export function recordRepoProfile(profile) { if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open"); @@ -5771,7 +5771,7 @@ function headlessRunFromRow(row) { * harness flows, UOK, and future memory retention instead of treating ignored * `.sf/evals` JSON/JSONL evidence files as project state. * - * Consumer: `/sf solver-eval` after each run completes. + * Consumer: `/solver-eval` after each run completes. */ export function recordSolverEvalRun(report) { if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open"); @@ -5847,7 +5847,7 @@ export function recordSolverEvalRun(report) { * Purpose: let operators inspect benchmark history without scraping generated * report files. * - * Consumer: `/sf solver-eval history`. + * Consumer: `/solver-eval history`. */ export function listSolverEvalRuns(limit = 10) { if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open"); @@ -5863,7 +5863,7 @@ export function listSolverEvalRuns(limit = 10) { /** * Read one autonomous solver eval run by id. * - * Purpose: support `/sf solver-eval show <run-id>` and future evidence + * Purpose: support `/solver-eval show <run-id>` and future evidence * promotion without parsing JSON artifacts. * * Consumer: solver eval command handlers. @@ -5883,7 +5883,7 @@ export function getSolverEvalRun(runId) { * * Purpose: show raw-vs-SF comparisons from DB evidence. * - * Consumer: `/sf solver-eval show <run-id>`. + * Consumer: `/solver-eval show <run-id>`. */ export function getSolverEvalCaseResults(runId) { if (!currentDb) throw new SFError(SF_STALE_STATE, "sf-db: No database open"); diff --git a/src/resources/extensions/sf/shortcut-defs.js b/src/resources/extensions/sf/shortcut-defs.js index 82b5ed6c9..65dd819b7 100644 --- a/src/resources/extensions/sf/shortcut-defs.js +++ b/src/resources/extensions/sf/shortcut-defs.js @@ -4,19 +4,19 @@ export const SF_SHORTCUTS = { dashboard: { key: "g", action: "Open SF dashboard", - command: "/sf status", + command: "/status", hasFallback: true, }, notifications: { key: "n", action: "Open notification history", - command: "/sf notifications", + command: "/notifications", hasFallback: true, }, parallel: { key: "p", action: "Open parallel worker monitor", - command: "/sf parallel watch", + command: "/parallel watch", hasFallback: false, // Ctrl+Shift+P conflicts with cycleModelBackward }, }; diff --git a/src/resources/extensions/sf/skill-catalog.js b/src/resources/extensions/sf/skill-catalog.js index 84254ec7d..4346b38ed 100644 --- a/src/resources/extensions/sf/skill-catalog.js +++ b/src/resources/extensions/sf/skill-catalog.js @@ -655,7 +655,7 @@ export const SKILL_CATALOG = [ * NOT shown directly to users during init (greenfield installs essentials * only and defers stack-specific skills). These mappings are available for: * 1. The LLM to install skills after establishing a design - * 2. The `/sf skills` command (explicit user request) + * 2. The `/skills` command (explicit user request) * 3. Re-running brownfield detection after project files are created */ export const GREENFIELD_STACKS = [ @@ -1049,7 +1049,7 @@ export async function runSkillInstallStep(ctx, signals) { description: "Install skills later with npx skills add", }, ], - notYetMessage: "Run /sf init when ready.", + notYetMessage: "Run /init when ready.", }); if (choice === "install") { const labels = await installPacksBatched(toInstall, (label) => { @@ -1097,7 +1097,7 @@ export async function runSkillInstallStep(ctx, signals) { description: "Install skills later with npx skills add", }, ], - notYetMessage: "Run /sf init when ready.", + notYetMessage: "Run /init when ready.", }); if (choice === "install") { const labels = await installPacksBatched(essentials, (label) => { diff --git a/src/resources/extensions/sf/skills/context-doctor/SKILL.md b/src/resources/extensions/sf/skills/context-doctor/SKILL.md index bfc2091d1..c4019345c 100644 --- a/src/resources/extensions/sf/skills/context-doctor/SKILL.md +++ b/src/resources/extensions/sf/skills/context-doctor/SKILL.md @@ -37,8 +37,8 @@ Read each persistent context file and judge: For the memory store, use SF's DB-backed memory/query tools rather than direct `sqlite3` access: ```bash -/sf memory status -/sf memory search "pattern" +/memory status +/memory search "pattern" ``` Inside an agent session, prefer the registered `memory_query` tool for targeted lookups. @@ -73,7 +73,7 @@ For non-trivial cleanups, present this plan to the user before executing — sf' ### Step 4 — Execute Repairs -Apply edits with `Edit`. For memory cleanup, use the current `/sf memory` commands or registered memory tools over direct `sqlite3` writes — direct DB mutation bypasses tool-level invariants. +Apply edits with `Edit`. For memory cleanup, use the current `/memory` commands or registered memory tools over direct `sqlite3` writes — direct DB mutation bypasses tool-level invariants. **Scope rules:** diff --git a/src/resources/extensions/sf/skills/researcher/SKILL.md b/src/resources/extensions/sf/skills/researcher/SKILL.md index df929897d..7576eff31 100644 --- a/src/resources/extensions/sf/skills/researcher/SKILL.md +++ b/src/resources/extensions/sf/skills/researcher/SKILL.md @@ -83,7 +83,7 @@ Use the runtime query tools instead of opening `.sf/sf.db` directly: - `sf_milestone_status` — read milestone, slice, and task status inside an agent session. - `sf headless query` — get the full DB-backed project snapshot when running from the shell. -- `/sf inspect db` — inspect schema/version diagnostics when the user asks for database health. +- `/inspect db` — inspect schema/version diagnostics when the user asks for database health. **Web search — use the search-the-web tool directly for current information.** </quick_start> diff --git a/src/resources/extensions/sf/skills/sf-headless/SKILL.md b/src/resources/extensions/sf/skills/sf-headless/SKILL.md index 3601bbec6..9543a5b5b 100644 --- a/src/resources/extensions/sf/skills/sf-headless/SKILL.md +++ b/src/resources/extensions/sf/skills/sf-headless/SKILL.md @@ -20,7 +20,7 @@ sf headless [flags] [command] [args...] - `--verbose` — show tool calls in progress output - `--supervised` — forward interactive UI requests to orchestrator via stdout/stdin - `--response-timeout N` — timeout for orchestrator response in supervised mode (default 30000) -- `--max-restarts N` — auto-restart on crash with backoff (default 3, 0 to disable) +- `--max-restarts N` — automatic restart on crash with backoff (default 3, 0 to disable) - `--answers <path>` — pre-supply answers and secrets from JSON file - `--events <types>` — filter JSONL output to specific event types (comma-separated, implies `--json`) diff --git a/src/resources/extensions/sf/skills/sf-headless/references/multi-session.md b/src/resources/extensions/sf/skills/sf-headless/references/multi-session.md index 369825102..d37cf6884 100644 --- a/src/resources/extensions/sf/skills/sf-headless/references/multi-session.md +++ b/src/resources/extensions/sf/skills/sf-headless/references/multi-session.md @@ -168,9 +168,9 @@ Inside an interactive SF session, these commands manage the parallel orchestrato | Command | Description | |---------|-------------| -| `/sf parallel start` | Analyze eligibility, spawn workers | -| `/sf parallel status` | Show all workers, costs, progress | -| `/sf parallel stop [MID]` | Stop one or all workers | -| `/sf parallel pause [MID]` | Pause without killing | -| `/sf parallel resume [MID]` | Resume paused worker | -| `/sf parallel merge [MID]` | Merge completed milestone branch | +| `/parallel start` | Analyze eligibility, spawn workers | +| `/parallel status` | Show all workers, costs, progress | +| `/parallel stop [MID]` | Stop one or all workers | +| `/parallel pause [MID]` | Pause without killing | +| `/parallel resume [MID]` | Resume paused worker | +| `/parallel merge [MID]` | Merge completed milestone branch | diff --git a/src/resources/extensions/sf/skills/working-in-parallel/SKILL.md b/src/resources/extensions/sf/skills/working-in-parallel/SKILL.md index 2c0797c2d..98fcd2467 100644 --- a/src/resources/extensions/sf/skills/working-in-parallel/SKILL.md +++ b/src/resources/extensions/sf/skills/working-in-parallel/SKILL.md @@ -20,7 +20,7 @@ Use the lightest parallelism that is safe: - **Inline tool batching** for independent reads/searches inside one agent turn. - **`subagent` research/review swarms** for independent questions or review lenses. - **2-3 worker execution swarms** only when one task has disjoint file/directory shards and the parent can merge and verify the result. -- **Git worktrees / `/sf parallel`** when workers need isolated branches or when edits may overlap, touch shared interfaces, or run for a long time. +- **Git worktrees / `/parallel`** when workers need isolated branches or when edits may overlap, touch shared interfaces, or run for a long time. If file ownership is ambiguous, prefer worktree isolation over same-checkout subagents. @@ -111,6 +111,6 @@ Some users prefer cloning the repo multiple times (`gh repo clone singularity-ng If sf's auto-loop is running in the main repo dir, **do not** start a worktree on the same branch — the auto-loop's commits and yours will race. Either: -- Pause the loop (`/sf pause` or `Ctrl-C`). +- Pause the loop (`/pause` or `Ctrl-C`). - Work on a different branch in the worktree. - Or wait for the current slice to merge. diff --git a/src/resources/extensions/sf/slice-parallel-orchestrator.js b/src/resources/extensions/sf/slice-parallel-orchestrator.js index 0e2d9157b..deaf9b937 100644 --- a/src/resources/extensions/sf/slice-parallel-orchestrator.js +++ b/src/resources/extensions/sf/slice-parallel-orchestrator.js @@ -52,7 +52,7 @@ export function getSliceOrchestratorState() { /** * Start parallel execution for eligible slices within a milestone. * - * For each eligible slice: create a worktree, spawn `sf --mode json --print "/sf autonomous"` + * For each eligible slice: create a worktree, spawn `sf --mode json --print "/autonomous"` * with env SF_SLICE_LOCK=<SID> + SF_MILESTONE_LOCK=<MID> + SF_PARALLEL_WORKER=1. */ /** @@ -285,7 +285,7 @@ function resolveSfBin() { } /** * Spawn a worker process for a slice. - * The worker runs `sf --mode json --print "/sf autonomous"` in the slice's worktree + * The worker runs `sf --mode json --print "/autonomous"` in the slice's worktree * with SF_SLICE_LOCK, SF_MILESTONE_LOCK, and SF_PARALLEL_WORKER set. */ function spawnSliceWorker(basePath, milestoneId, sliceId) { @@ -298,7 +298,7 @@ function spawnSliceWorker(basePath, milestoneId, sliceId) { let child; try { const shellWrapper = sliceState.shellWrapper ?? []; - const workerArgs = [binPath, "--mode", "json", "--print", "/sf autonomous"]; + const workerArgs = [binPath, "--mode", "json", "--print", "/autonomous"]; const [spawnCmd, spawnArgs] = shellWrapper.length > 0 ? [ diff --git a/src/resources/extensions/sf/spec-projections.js b/src/resources/extensions/sf/spec-projections.js index 9c2e2320c..7ebf0c327 100644 --- a/src/resources/extensions/sf/spec-projections.js +++ b/src/resources/extensions/sf/spec-projections.js @@ -5,7 +5,7 @@ * model agents use in `.sf`, so git history captures the contract without * making docs a second source of truth. * - * Consumer: commands-plan.js for `/sf plan specs generate|diff|check`. + * Consumer: commands-plan.js for `/plan specs generate|diff|check`. */ import { existsSync } from "node:fs"; @@ -93,7 +93,7 @@ export function buildWorkingModelInputs(basePath) { * Purpose: provide one deterministic human-readable export of SF's terminology, * state, and source-placement contract from the repo-local working model. * - * Consumer: `/sf plan specs generate` and `/sf plan specs check`. + * Consumer: `/plan specs generate` and `/plan specs check`. */ export function generateOperatingModelSpec(basePath) { return [ @@ -157,6 +157,11 @@ export function generateOperatingModelSpec(basePath) { "", "`auto` is not a run-control mode. Use **autonomous** for continuous run control; use **assisted** for bounded human-guided progression.", "", + "> Competitor note: Copilot CLI calls continuous run control autopilot.", + "> SF does not use that product name. The SF term is autonomous mode,", + "> and it stays separate from permission profiles, surfaces, protocols,", + "> and output formats.", + "", "UOK kernel records carry `runControl` as a first-class lifecycle field. Workflow phases such as planning, building, verification, and finalization are separate execution stages, not run-control modes.", "", "## Permission Profile", @@ -199,7 +204,7 @@ export function generateOperatingModelSpec(basePath) { "", "### Core Flow", "", - "- `src/resources/extensions/sf/` owns the SF workflow extension: planning tools, UOK/runtime state, `/sf` commands, prompts, templates, doctors, schedule, and DB-backed state.", + "- `src/resources/extensions/sf/` owns the SF workflow extension: planning tools, UOK/runtime state, `/next` commands, prompts, templates, doctors, schedule, and DB-backed state.", "- `src/resources/extensions/` owns bundled extension packages loaded into the runtime.", "- `src/resources/agents/`, `src/resources/skills/`, and `src/resources/workflows/` own bundled runtime resources, not independent product flows.", "", diff --git a/src/resources/extensions/sf/state.js b/src/resources/extensions/sf/state.js index a0c695a07..a681048c2 100644 --- a/src/resources/extensions/sf/state.js +++ b/src/resources/extensions/sf/state.js @@ -171,7 +171,7 @@ export async function getActiveMilestoneId(basePath) { if (isDbAvailable()) { const allMilestones = getAllMilestones(); if (allMilestones.length > 0) { - // Respect queue-order.json so /sf queue reordering is honored (#2556). + // Respect queue-order.json so /queue reordering is honored (#2556). // Without this, the DB path uses lexicographic sort while the dispatch // guard uses queue order — causing a deadlock. const customOrder = loadQueueOrder(basePath); @@ -507,7 +507,7 @@ function handleNoActiveMilestone(registry, requirements, milestoneProgress) { phase: "pre-planning", recentDecisions: [], blockers: [], - nextAction: `All remaining milestones are parked (${parkedIds}). Run /sf unpark <id> or create a new milestone.`, + nextAction: `All remaining milestones are parked (${parkedIds}). Run /unpark <id> or create a new milestone.`, registry, requirements, progress: { milestones: milestoneProgress }, @@ -521,7 +521,7 @@ function handleNoActiveMilestone(registry, requirements, milestoneProgress) { phase: "pre-planning", recentDecisions: [], blockers: [], - nextAction: "No milestones found. Run /sf to create one.", + nextAction: "No milestones found. Run /next to create one.", registry: [], requirements, progress: { milestones: { done: 0, total: 0 } }, @@ -745,7 +745,7 @@ export async function deriveStateFromDb(basePath) { phase: "pre-planning", recentDecisions: [], blockers: [], - nextAction: "No milestones found. Run /sf to create one.", + nextAction: "No milestones found. Run /next to create one.", registry: [], requirements, progress: { milestones: { done: 0, total: 0 } }, @@ -1135,7 +1135,7 @@ export async function _deriveStateImpl(basePath) { phase: "pre-planning", recentDecisions: [], blockers: [], - nextAction: "No milestones found. Run /sf to create one.", + nextAction: "No milestones found. Run /next to create one.", registry: [], requirements, progress: { @@ -1424,7 +1424,7 @@ export async function _deriveStateImpl(basePath) { phase: "pre-planning", recentDecisions: [], blockers: [], - nextAction: `All remaining milestones are parked (${parkedIds}). Run /sf unpark <id> or create a new milestone.`, + nextAction: `All remaining milestones are parked (${parkedIds}). Run /unpark <id> or create a new milestone.`, registry, requirements, progress: { @@ -1441,7 +1441,7 @@ export async function _deriveStateImpl(basePath) { phase: "pre-planning", recentDecisions: [], blockers: [], - nextAction: "No milestones found. Run /sf to create one.", + nextAction: "No milestones found. Run /next to create one.", registry: [], requirements, progress: { @@ -1817,7 +1817,7 @@ export async function _deriveStateImpl(basePath) { } // ── Mid-execution escalation (ADR-011 P2 — SF ADR) ──────────────── // Pause the loop if any task in the active slice has escalation_pending=1 - // and an unresolved escalation artifact. The user must run /sf escalate + // and an unresolved escalation artifact. The user must run /escalate // resolve before autonomous mode will continue. Falls through (returns null // from detectPendingEscalation) when nothing is paused — no perf cost // in the common path. @@ -1834,7 +1834,7 @@ export async function _deriveStateImpl(basePath) { blockers: [ `Task ${escalatingTaskId} requires a user decision before the loop can proceed`, ], - nextAction: `Run \`/sf escalate show ${escalatingTaskId}\` to review the options, then \`/sf escalate resolve ${escalatingTaskId} <choice>\` to proceed.`, + nextAction: `Run \`/escalate show ${escalatingTaskId}\` to review the options, then \`/escalate resolve ${escalatingTaskId} <choice>\` to proceed.`, registry, requirements, progress: { @@ -1899,7 +1899,7 @@ export async function _deriveStateImpl(basePath) { // REPLAN.md exists — loop protection: fall through to normal executing } // ── REPLAN-TRIGGER detection: triage-initiated replan ────────────────── - // Manual `/sf triage` writes REPLAN-TRIGGER.md when a capture is classified + // Manual `/triage` writes REPLAN-TRIGGER.md when a capture is classified // as "replan". Detect it here and transition to replanning-slice so the // dispatch loop picks it up (instead of silently advancing past it). if (!blockerTaskId) { diff --git a/src/resources/extensions/sf/templates/PREFERENCES.md b/src/resources/extensions/sf/templates/PREFERENCES.md index 3d8d3e530..aa5840a1a 100644 --- a/src/resources/extensions/sf/templates/PREFERENCES.md +++ b/src/resources/extensions/sf/templates/PREFERENCES.md @@ -46,9 +46,9 @@ phases: mid_execution_escalation: # When true (default), an escalation in autonomous mode auto-accepts the agent's # recommendation and continues — autonomous mode is autonomous. The user can - # review/override later via `/sf escalate list --all`. Set false to keep + # review/override later via `/escalate list --all`. Set false to keep # SF's pause-and-ask behavior (loop halts until user runs - # `/sf escalate resolve`). + # `/escalate resolve`). escalation_auto_accept: # Deep-mode planning gate (top-level, not under phases). When set to "deep", # autonomous mode runs project-level discussion → requirements → optional research @@ -128,4 +128,4 @@ These are runtime-only; SF reads them at startup, never persists them, never log | `SF_LLM_GATEWAY_EMBED_MODEL` | Embedding model id served by the gateway. | `Qwen/Qwen3-Embedding-4B` | | `SF_LLM_GATEWAY_RERANK_MODEL` | Rerank model id. When the gateway key is set, SF defaults to `Qwen/Qwen3-Reranker-0.6B`; if no rerank worker is online, rerank silently degrades and the cosine pass alone ranks results. | `Qwen/Qwen3-Reranker-0.6B` | -Once `SF_LLM_GATEWAY_KEY` is set, the agent_end hook opportunistically backfills embeddings for any memories without vectors (50 per turn, 16 per batch). `/sf memory search "<query>"` lights up the embedding-ranked path; without the key it shows static rank. +Once `SF_LLM_GATEWAY_KEY` is set, the agent_end hook opportunistically backfills embeddings for any memories without vectors (50 per turn, 16 per batch). `/memory search "<query>"` lights up the embedding-ranked path; without the key it shows static rank. diff --git a/src/resources/extensions/sf/tests/autonomous-command-args.test.mjs b/src/resources/extensions/sf/tests/autonomous-command-args.test.mjs index 6c743567f..cdee05a14 100644 --- a/src/resources/extensions/sf/tests/autonomous-command-args.test.mjs +++ b/src/resources/extensions/sf/tests/autonomous-command-args.test.mjs @@ -24,9 +24,6 @@ test("handleAutonomousCommand_when_unsupported_argument_requested_reports_error" assert.equal(handled, true); assert.equal(notifications.length, 1); assert.equal(notifications[0].level, "error"); - assert.match( - notifications[0].message, - /Unsupported \/sf autonomous argument/, - ); + assert.match(notifications[0].message, /Unsupported \/autonomous argument/); assert.match(notifications[0].message, /--turbo/); }); diff --git a/src/resources/extensions/sf/tests/autonomous-solver-eval.test.mjs b/src/resources/extensions/sf/tests/autonomous-solver-eval.test.mjs index d88b59a0f..cc6e189f2 100644 --- a/src/resources/extensions/sf/tests/autonomous-solver-eval.test.mjs +++ b/src/resources/extensions/sf/tests/autonomous-solver-eval.test.mjs @@ -150,7 +150,7 @@ describe("autonomous solver eval", () => { recordHeadlessRun({ runId: "headless-timeout-test", - command: "/sf autonomous", + command: "/autonomous", status: "timeout", exitCode: 1, timedOut: true, @@ -170,7 +170,7 @@ describe("autonomous solver eval", () => { expect(runs).toHaveLength(1); expect(runs[0]).toMatchObject({ runId: "headless-timeout-test", - command: "/sf autonomous", + command: "/autonomous", status: "timeout", exitCode: 1, timedOut: true, diff --git a/src/resources/extensions/sf/tests/autonomous-solver.test.mjs b/src/resources/extensions/sf/tests/autonomous-solver.test.mjs index cfbebfb60..53d46195d 100644 --- a/src/resources/extensions/sf/tests/autonomous-solver.test.mjs +++ b/src/resources/extensions/sf/tests/autonomous-solver.test.mjs @@ -34,7 +34,7 @@ afterEach(() => { function pdd(overrides = {}) { return { purpose: "Protect autonomous execution from vague hidden retries.", - consumer: "/sf autonomous unit executor.", + consumer: "/autonomous unit executor.", contract: "Checkpoint contains outcome, progress, evidence, and remaining work.", failureBoundary: @@ -121,7 +121,7 @@ describe("autonomous solver", () => { maxIterations: 12, }); - expect(prompt).toContain("/sf autonomous iteration 3 of 12"); + expect(prompt).toContain("/autonomous iteration 3 of 12"); expect(prompt).toContain("sf_autonomous_checkpoint"); expect(prompt).toContain("Writing SUMMARY.md"); expect(prompt).toContain("is not a checkpoint"); @@ -271,7 +271,7 @@ describe("autonomous solver", () => { test("steering_append_consume_is_idempotent", () => { const project = makeProject(); appendAutonomousSolverSteering(project, "Prefer runtime enforcement."); - appendAutonomousSolverSteering(project, "Keep /sf autonomous only."); + appendAutonomousSolverSteering(project, "Keep /autonomous only."); const first = consumePendingAutonomousSolverSteering(project); const second = consumePendingAutonomousSolverSteering(project); diff --git a/src/resources/extensions/sf/tests/auto-startup-doctor.test.mjs b/src/resources/extensions/sf/tests/autonomous-startup-doctor.test.mjs similarity index 95% rename from src/resources/extensions/sf/tests/auto-startup-doctor.test.mjs rename to src/resources/extensions/sf/tests/autonomous-startup-doctor.test.mjs index 162cb1a0d..0321c5fff 100644 --- a/src/resources/extensions/sf/tests/auto-startup-doctor.test.mjs +++ b/src/resources/extensions/sf/tests/autonomous-startup-doctor.test.mjs @@ -8,7 +8,7 @@ const autoSource = readFileSync( "utf-8", ); -describe("auto startup doctor", () => { +describe("autonomous startup doctor", () => { test("startAuto_when_session_not_running_runs_startup_doctor_fix_before_resume_dispatch", () => { const runningGuard = autoSource.indexOf('classification === "running"'); const doctorCall = autoSource.indexOf( diff --git a/src/resources/extensions/sf/tests/commands-escalate-db.test.mjs b/src/resources/extensions/sf/tests/commands-escalate-db.test.mjs index b1a644a3f..95f2755e7 100644 --- a/src/resources/extensions/sf/tests/commands-escalate-db.test.mjs +++ b/src/resources/extensions/sf/tests/commands-escalate-db.test.mjs @@ -1,5 +1,5 @@ /** - * commands-escalate-db.test.mjs — `/sf escalate` database binding coverage. + * commands-escalate-db.test.mjs — `/escalate` database binding coverage. * * Purpose: prove escalation commands read the current project SQLite DB instead * of reusing a previously-open DB from another project. diff --git a/src/resources/extensions/sf/tests/commands-inspect-db.test.mjs b/src/resources/extensions/sf/tests/commands-inspect-db.test.mjs index a73fd371d..1b1ef54de 100644 --- a/src/resources/extensions/sf/tests/commands-inspect-db.test.mjs +++ b/src/resources/extensions/sf/tests/commands-inspect-db.test.mjs @@ -1,5 +1,5 @@ /** - * commands-inspect-db.test.mjs — `/sf inspect` database binding coverage. + * commands-inspect-db.test.mjs — `/inspect` database binding coverage. * * Purpose: prove inspect diagnostics read the current project SQLite DB instead * of reusing a previously-open DB from another project. diff --git a/src/resources/extensions/sf/tests/commands-memory-db.test.mjs b/src/resources/extensions/sf/tests/commands-memory-db.test.mjs index f2b745291..4b006e0eb 100644 --- a/src/resources/extensions/sf/tests/commands-memory-db.test.mjs +++ b/src/resources/extensions/sf/tests/commands-memory-db.test.mjs @@ -1,5 +1,5 @@ /** - * commands-memory-db.test.mjs — `/sf memory` database binding coverage. + * commands-memory-db.test.mjs — `/memory` database binding coverage. * * Purpose: prove memory commands bind to the current project SQLite DB and do * not reuse an already-open DB from another project in long-lived SF processes. diff --git a/src/resources/extensions/sf/tests/commands-schedule.test.mjs b/src/resources/extensions/sf/tests/commands-schedule.test.mjs index da9e6ee99..d83b63c20 100644 --- a/src/resources/extensions/sf/tests/commands-schedule.test.mjs +++ b/src/resources/extensions/sf/tests/commands-schedule.test.mjs @@ -1,7 +1,7 @@ /** * Schedule CLI command tests. * - * Purpose: verify the /sf schedule subcommands: add, list, done, cancel, + * Purpose: verify the /schedule subcommands: add, list, done, cancel, * snooze, run, and the duration parser. * * Consumer: CI test runner (vitest). diff --git a/src/resources/extensions/sf/tests/direct-command-surface.test.mjs b/src/resources/extensions/sf/tests/direct-command-surface.test.mjs new file mode 100644 index 000000000..c4d8522a0 --- /dev/null +++ b/src/resources/extensions/sf/tests/direct-command-surface.test.mjs @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import { test } from "vitest"; + +import { + DIRECT_SF_COMMAND_NAMES, + getSfTopLevelCommandCompletions, +} from "../commands/catalog.js"; +import { registerSFCommands } from "../commands/index.js"; + +test("direct SF command surface registers workflow verbs without legacy sf namespace", () => { + const registered = []; + const pi = { + registerCommand(name, options) { + registered.push({ name, options }); + }, + }; + + registerSFCommands(pi); + const names = registered.map((entry) => entry.name).sort(); + + assert.ok(names.includes("autonomous")); + assert.ok(names.includes("plan")); + assert.ok(names.includes("schedule")); + assert.ok(names.includes("doctor")); + assert.ok(names.includes("status")); + assert.ok(!names.includes("sf")); + assert.ok(!names.includes("stop")); + assert.deepEqual(names, [...DIRECT_SF_COMMAND_NAMES].sort()); +}); + +test("direct command completions strip the already typed command name", () => { + assert.deepEqual(getSfTopLevelCommandCompletions("autonomous", "--"), [ + { + value: "--verbose", + label: "--verbose", + description: "Show detailed execution output", + }, + { + value: "--debug", + label: "--debug", + description: "Enable debug logging", + }, + ]); +}); diff --git a/src/resources/extensions/sf/tests/schedule-launch-banner.test.mjs b/src/resources/extensions/sf/tests/schedule-launch-banner.test.mjs index 9c8b638c9..9883dc815 100644 --- a/src/resources/extensions/sf/tests/schedule-launch-banner.test.mjs +++ b/src/resources/extensions/sf/tests/schedule-launch-banner.test.mjs @@ -87,7 +87,7 @@ describe("schedule-launch-banner", () => { const output = captureStderr(() => printScheduleBanner(testDir)); assert.ok(output.includes("1 scheduled item due")); assert.ok(output.includes("Review PR")); - assert.ok(output.includes("Manage: /sf schedule list")); + assert.ok(output.includes("Manage: /schedule list")); }); it("prints a banner for multiple due entries", () => { @@ -130,7 +130,7 @@ describe("schedule-launch-banner", () => { assert.ok( output.includes("autonomous mode will consume project entries"), ); - assert.equal(output.includes("Manage: /sf schedule list"), false); + assert.equal(output.includes("Manage: /schedule list"), false); }); it("truncates to 3 titles with a +more suffix", () => { diff --git a/src/resources/extensions/sf/tools/memory-tools.js b/src/resources/extensions/sf/tools/memory-tools.js index df2439def..f702164f8 100644 --- a/src/resources/extensions/sf/tools/memory-tools.js +++ b/src/resources/extensions/sf/tools/memory-tools.js @@ -154,7 +154,7 @@ export function executeSfGraph(params) { type: "text", text: "sf_graph build acknowledged. Graph edges are populated incrementally by memory " + - "extraction (including LINK actions). Use `/sf memory extract <SRC-...>` to trigger " + + "extraction (including LINK actions). Use `/memory extract <SRC-...>` to trigger " + "extraction against a specific ingested source.", }, ], diff --git a/src/resources/extensions/sf/triage-ui.js b/src/resources/extensions/sf/triage-ui.js index 9eb9dcc79..6e822aee3 100644 --- a/src/resources/extensions/sf/triage-ui.js +++ b/src/resources/extensions/sf/triage-ui.js @@ -2,7 +2,7 @@ * SF Triage UI — Confirmation flow for programmatic triage results * * Used by autonomous mode dispatch (S02) when triage fires between tasks. - * For manual `/sf triage`, the LLM session handles confirmation directly. + * For manual `/triage`, the LLM session handles confirmation directly. * * This module provides `showTriageConfirmation` which presents each * triage result to the user via `showNextAction` and returns the diff --git a/src/resources/extensions/sf/undo.js b/src/resources/extensions/sf/undo.js index 805b78857..fff2d1cbb 100644 --- a/src/resources/extensions/sf/undo.js +++ b/src/resources/extensions/sf/undo.js @@ -70,7 +70,7 @@ export async function handleUndo(args, ctx, _pi, basePath) { ` - Delete summary artifacts\n` + ` - Uncheck task in PLAN (if execute-task)\n` + ` - Attempt to revert associated git commits\n\n` + - `Run /sf undo --force to confirm.`, + `Run /undo --force to confirm.`, "warning", ); return; @@ -217,7 +217,7 @@ export async function handleUndoTask(args, ctx, _pi, basePath) { const rawId = args.replace("--force", "").trim(); if (!rawId) { ctx.ui.notify( - "Usage: /sf undo-task <taskId> [--force]\n\n" + + "Usage: /undo-task <taskId> [--force]\n\n" + "Accepts: T01, S01/T01, or M001/S01/T01\n" + "Resets the task's DB status to pending and re-renders plan checkboxes.", "warning", @@ -244,7 +244,7 @@ export async function handleUndoTask(args, ctx, _pi, basePath) { ` - Set task status to "pending" in DB\n` + ` - Delete task summary file (if exists)\n` + ` - Re-render plan checkboxes\n\n` + - `Run /sf undo-task ${rawId} --force to confirm.`, + `Run /undo-task ${rawId} --force to confirm.`, "warning", ); return; @@ -279,7 +279,7 @@ export async function handleResetSlice(args, ctx, _pi, basePath) { const rawId = args.replace("--force", "").trim(); if (!rawId) { ctx.ui.notify( - "Usage: /sf reset-slice <sliceId> [--force]\n\n" + + "Usage: /reset-slice <sliceId> [--force]\n\n" + "Accepts: S01 or M001/S01\n" + "Resets the slice and all its tasks, re-renders plan + roadmap checkboxes.", "warning", @@ -309,7 +309,7 @@ export async function handleResetSlice(args, ctx, _pi, basePath) { ` - Set slice status to "active" in DB\n` + ` - Delete task summary files, slice summary, and UAT files\n` + ` - Re-render plan + roadmap checkboxes\n\n` + - `Run /sf reset-slice ${rawId} --force to confirm.`, + `Run /reset-slice ${rawId} --force to confirm.`, "warning", ); return; diff --git a/src/resources/extensions/sf/uok/diagnostic-synthesis.js b/src/resources/extensions/sf/uok/diagnostic-synthesis.js index 686478aa9..59add9e4b 100644 --- a/src/resources/extensions/sf/uok/diagnostic-synthesis.js +++ b/src/resources/extensions/sf/uok/diagnostic-synthesis.js @@ -129,7 +129,7 @@ function latestEndedRun(runs) { * Purpose: give headless/status/doctor/widget one operator-facing diagnosis instead of * requiring humans to correlate ps, auto.lock, UOK parity, DB rows, and runtime files. * - * Consumer: UOK kernel startup, headless query, /sf uok status, doctor, and progress widget. + * Consumer: UOK kernel startup, headless query, /uok status, doctor, and progress widget. */ export function synthesizeUokDiagnostics(basePath, options = {}) { const nowMs = options.nowMs ?? Date.now(); diff --git a/src/resources/extensions/sf/uok/metrics-exposition.js b/src/resources/extensions/sf/uok/metrics-exposition.js index 97c2712d3..1d2281629 100644 --- a/src/resources/extensions/sf/uok/metrics-exposition.js +++ b/src/resources/extensions/sf/uok/metrics-exposition.js @@ -5,7 +5,7 @@ * histograms in Prometheus text format so external collectors (or a local * dashboard) can scrape them without needing SQLite access. * - * Consumer: health widgets, /sf uok status, and external monitoring. + * Consumer: health widgets, /uok status, and external monitoring. */ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; diff --git a/src/resources/extensions/sf/visualizer-views.js b/src/resources/extensions/sf/visualizer-views.js index 3ef611211..f8d2aa365 100644 --- a/src/resources/extensions/sf/visualizer-views.js +++ b/src/resources/extensions/sf/visualizer-views.js @@ -1186,7 +1186,7 @@ export function renderHealthView(data, th, width) { ); if (topIssue) lines.push(` ${th.fg("warning", "⚠")} ${th.fg("dim", topIssue)}`); - lines.push(` ${th.fg("dim", "→ /sf skill-health for full report")}`); + lines.push(` ${th.fg("dim", "→ /skill-health for full report")}`); } return lines; } diff --git a/src/resources/extensions/sf/workflow-dispatch.js b/src/resources/extensions/sf/workflow-dispatch.js index 28c8e23cd..e60d7fcaa 100644 --- a/src/resources/extensions/sf/workflow-dispatch.js +++ b/src/resources/extensions/sf/workflow-dispatch.js @@ -1,8 +1,8 @@ /** * workflow-dispatch.ts — Shared dispatchers for workflow plugins. * - * Called by both `/sf start <template>` (existing markdown path) and - * `/sf workflow <name>` (new direct dispatch). Keeps the prompt-build + * Called by both `/start <template>` (existing markdown path) and + * `/workflow <name>` (new direct dispatch). Keeps the prompt-build * logic in one place so md template behavior stays consistent. */ import { readFileSync } from "node:fs"; diff --git a/src/resources/extensions/sf/workflow-install.js b/src/resources/extensions/sf/workflow-install.js index a12fe8573..1c974387b 100644 --- a/src/resources/extensions/sf/workflow-install.js +++ b/src/resources/extensions/sf/workflow-install.js @@ -10,8 +10,8 @@ * `.sf/workflows/<name>.<ext>` with the `--project` flag. * * A provenance file `~/.sf/workflows/.installed.json` (or project equivalent) - * records source URL, timestamp, and sha256 so `/sf workflow uninstall` can - * clean up and future `/sf workflow update` can refresh. + * records source URL, timestamp, and sha256 so `/workflow uninstall` can + * clean up and future `/workflow update` can refresh. */ import { createHash } from "node:crypto"; diff --git a/src/resources/extensions/sf/workflow-plugins.js b/src/resources/extensions/sf/workflow-plugins.js index ab48cbe95..c1df678b1 100644 --- a/src/resources/extensions/sf/workflow-plugins.js +++ b/src/resources/extensions/sf/workflow-plugins.js @@ -3,12 +3,12 @@ * * Discovers workflow definitions from three tiers (project > global > bundled) * in both YAML and markdown formats. Each plugin declares an execution mode - * that controls how `/sf workflow <name>` dispatches it: + * that controls how `/workflow <name>` dispatches it: * * oneshot — prompt-only, no state or scaffolding * yaml-step — CustomWorkflowEngine run with GRAPH.yaml * markdown-phase — STATE.json + phase gates (current md template behavior) - * auto-milestone — hooks into /sf autonomous pipeline (full-project only) + * auto-milestone — hooks into /autonomous pipeline (full-project only) * * Precedence: project > global > bundled. Same-named file wins. */ @@ -271,7 +271,7 @@ export function resolvePlugin(basePath, name) { export function listPluginsFormatted(basePath) { const plugins = discoverPlugins(basePath); if (plugins.size === 0) { - return "No workflow plugins found.\n\nRun /sf workflow new to author one."; + return "No workflow plugins found.\n\nRun /workflow new to author one."; } const groups = { oneshot: [], @@ -298,13 +298,13 @@ export function listPluginsFormatted(basePath) { lines.push(""); } lines.push("Usage:"); - lines.push(" /sf workflow <name> Run a plugin directly"); - lines.push(" /sf workflow info <name> Show plugin details"); - lines.push(" /sf workflow install <src> Install a plugin from a URL"); + lines.push(" /workflow <name> Run a plugin directly"); + lines.push(" /workflow info <name> Show plugin details"); + lines.push(" /workflow install <src> Install a plugin from a URL"); return lines.join("\n"); } /** - * Format a single plugin's metadata for `/sf workflow info <name>`. + * Format a single plugin's metadata for `/workflow info <name>`. */ export function formatPluginInfo(plugin) { const lines = [ diff --git a/src/resources/extensions/sf/workflow-template-compiler.js b/src/resources/extensions/sf/workflow-template-compiler.js index c24409982..c52990854 100644 --- a/src/resources/extensions/sf/workflow-template-compiler.js +++ b/src/resources/extensions/sf/workflow-template-compiler.js @@ -1,8 +1,8 @@ /** - * workflow-template-compiler.ts — Compile `/sf start` templates into run definitions. + * workflow-template-compiler.ts — Compile `/start` templates into run definitions. * * Purpose: let prompt-oriented workflow templates enter the same graph-backed - * runtime as YAML workflow definitions while preserving the ergonomic `/sf start` + * runtime as YAML workflow definitions while preserving the ergonomic `/start` * authoring surface. */ /** @@ -35,7 +35,7 @@ function phasePrompt(input, phase) { "", "Use the workflow template below as the source of truth. Execute only the current phase, produce the phase's required evidence, and leave enough notes for the next graph step to continue without re-deciding prior scope.", guided - ? "This is a guided /sf start run: ask concise clarifying questions when product intent, scope, acceptance criteria, or readiness is unclear, and pause rather than guessing through important product decisions." + ? "This is a guided /start run: ask concise clarifying questions when product intent, scope, acceptance criteria, or readiness is unclear, and pause rather than guessing through important product decisions." : "", "", `Current phase: ${phase}`, @@ -58,14 +58,14 @@ function hasGuidedReviewGate(input, phase) { /** * Compile a resolved workflow template into a V1 workflow definition. * - * Purpose: bridge `/sf start` templates into the canonical custom workflow + * Purpose: bridge `/start` templates into the canonical custom workflow * graph runtime without requiring users to author YAML definitions. * * Consumer: `handleStart` before creating a template-backed workflow run. */ /** * Compile a workflow template into a WorkflowDefinition. - * Bridges /sf start templates into the custom workflow graph runtime. + * Bridges /start templates into the custom workflow graph runtime. */ export function compileTemplateRun(input) { return { diff --git a/src/resources/extensions/sf/workflow-templates.js b/src/resources/extensions/sf/workflow-templates.js index 5ff47f325..821a1de71 100644 --- a/src/resources/extensions/sf/workflow-templates.js +++ b/src/resources/extensions/sf/workflow-templates.js @@ -52,13 +52,13 @@ export function loadRegistry() { /** * Return registry-backed workflow template command entries for completion and help. * - * Purpose: keep `/sf start` discovery surfaces tied to the template registry so + * Purpose: keep `/start` discovery surfaces tied to the template registry so * newly registered workflows are visible without editing multiple hardcoded lists. * - * Consumer: `/sf` command completion catalogs and `/sf start` usage rendering. + * Consumer: `/next` command completion catalogs and `/start` usage rendering. */ /** - * Get registry-backed command definitions for /sf start completion and help. + * Get registry-backed command definitions for /start completion and help. */ export function workflowTemplateCommandDefinitions() { const registry = loadRegistry(); @@ -68,36 +68,36 @@ export function workflowTemplateCommandDefinitions() { })); } /** - * Format registry-backed `/sf start` usage text. + * Format registry-backed `/start` usage text. * * Purpose: present the autoflow template family from the same source of truth as * resolution and completion, preventing stale command help. * - * Consumer: `/sf start` when called without a resolvable template. + * Consumer: `/start` when called without a resolvable template. */ /** - * Format usage text for /sf start command with available templates. + * Format usage text for /start command with available templates. */ export function formatStartUsage() { const templates = workflowTemplateCommandDefinitions() .map(({ cmd, desc }) => ` ${cmd.padEnd(16)} ${desc}`) .join("\n"); return ( - "Usage: /sf start <template> [description]\n\n" + - "/sf start templates are autoflow planning templates. " + - "YAML workflow definitions use /sf workflow run <definition>.\n\n" + + "Usage: /start <template> [description]\n\n" + + "/start templates are autoflow planning templates. " + + "YAML workflow definitions use /workflow run <definition>.\n\n" + "Templates:\n" + templates + "\n\n" + "Examples:\n" + - " /sf start bugfix fix login button not responding\n" + - " /sf start product-plan plan the product we need to develop\n" + - " /sf start product-tracking create product analytics tracking plan\n" + - " /sf start spike evaluate auth libraries\n\n" + + " /start bugfix fix login button not responding\n" + + " /start product-plan plan the product we need to develop\n" + + " /start product-tracking create product analytics tracking plan\n" + + " /start spike evaluate auth libraries\n\n" + "Flags:\n" + " --dry-run Preview what would happen without executing\n" + " --issue <ref> Link to a GitHub issue\n\n" + - "Run /sf templates for detailed template info." + "Run /templates for detailed template info." ); } /** @@ -237,8 +237,8 @@ export function listTemplates() { ); lines.push(""); } - lines.push("Usage: /sf start <template> [description]"); - lines.push(" /sf templates info <name>"); + lines.push("Usage: /start <template> [description]"); + lines.push(" /templates info <name>"); return lines.join("\n"); } /** diff --git a/src/resources/extensions/sf/workflow-templates/accessibility-audit.md b/src/resources/extensions/sf/workflow-templates/accessibility-audit.md index 4815157b9..55fbbaf19 100644 --- a/src/resources/extensions/sf/workflow-templates/accessibility-audit.md +++ b/src/resources/extensions/sf/workflow-templates/accessibility-audit.md @@ -83,6 +83,6 @@ For each component/page: Suggestions should be specific enough to act on, but don't edit any files. If the user wants to apply the fixes, suggest: -> `/sf workflow refactor "apply a11y fixes"` with this report as context. +> `/workflow refactor "apply a11y fixes"` with this report as context. </instructions> diff --git a/src/resources/extensions/sf/workflow-templates/api-breaking-change.md b/src/resources/extensions/sf/workflow-templates/api-breaking-change.md index 97d0bdb65..da39129d7 100644 --- a/src/resources/extensions/sf/workflow-templates/api-breaking-change.md +++ b/src/resources/extensions/sf/workflow-templates/api-breaking-change.md @@ -90,7 +90,7 @@ dependents where feasible. - Draft a release-notes entry with before/after code examples. - If the API has external users, draft an issue or blog post. -4. **Ship** the deprecation release (coordinate with `/sf workflow release`). +4. **Ship** the deprecation release (coordinate with `/workflow release`). 5. **Gate:** Deprecation is live, callers have had time to migrate. Decide the removal timeline (typically next minor or next major). diff --git a/src/resources/extensions/sf/workflow-templates/dead-code.md b/src/resources/extensions/sf/workflow-templates/dead-code.md index f7911b104..fb48f0392 100644 --- a/src/resources/extensions/sf/workflow-templates/dead-code.md +++ b/src/resources/extensions/sf/workflow-templates/dead-code.md @@ -73,7 +73,7 @@ If a symbol is only referenced in a test, distinguish: ## 5. Don't delete End with: -> To remove high-confidence items, run `/sf workflow refactor "remove dead code"` +> To remove high-confidence items, run `/workflow refactor "remove dead code"` > and pass the list above as context. The user decides. Do **not** delete files, `git rm`, or open a PR. diff --git a/src/resources/extensions/sf/workflow-templates/full-project.md b/src/resources/extensions/sf/workflow-templates/full-project.md index 5f704d1d7..297f7685e 100644 --- a/src/resources/extensions/sf/workflow-templates/full-project.md +++ b/src/resources/extensions/sf/workflow-templates/full-project.md @@ -13,7 +13,7 @@ research, planning, execution, and verification. Use for greenfield projects or major features that need the full planning apparatus. This template wraps the existing SF workflow for registry completeness. -When selected, it routes to the standard /sf init → /sf autonomous pipeline. +When selected, it routes to the standard /init → /autonomous pipeline. </purpose> <phases> @@ -28,12 +28,12 @@ When selected, it routes to the standard /sf init → /sf autonomous pipeline. ## Routing to Standard SF -This template is a convenience entry point. When selected via `/sf start full-project`, +This template is a convenience entry point. When selected via `/start full-project`, it should route to the standard SF workflow: -1. If `.sf/` doesn't exist: Run `/sf init` to bootstrap the project -2. If `.sf/` exists but no milestones: Start the discuss phase via `/sf discuss` -3. If milestones exist: Resume via `/sf autonomous` or `/sf next` +1. If `.sf/` doesn't exist: Run `/init` to bootstrap the project +2. If `.sf/` exists but no milestones: Start the discuss phase via `/discuss` +3. If milestones exist: Resume via `/autonomous` or `/next` The full SF workflow protocol is defined in `~/.sf/agent/SF-WORKFLOW.md` and handles all phases, state tracking, and agent orchestration. diff --git a/src/resources/extensions/sf/workflow-templates/issue-triage.md b/src/resources/extensions/sf/workflow-templates/issue-triage.md index 664dacd84..d85312150 100644 --- a/src/resources/extensions/sf/workflow-templates/issue-triage.md +++ b/src/resources/extensions/sf/workflow-templates/issue-triage.md @@ -56,7 +56,7 @@ Write ONE of these, with a concrete next step: - **Ask for info:** list the 1–3 specific things missing (repro steps, version, logs). Draft the comment text. - **Accept and schedule:** suggest a workflow to run next (e.g. - `/sf start bugfix --issue #123` or `/sf workflow small-feature`). + `/start bugfix --issue #123` or `/workflow small-feature`). - **Close:** draft a polite close comment with the reason. - **Escalate:** flag for human review with a specific reason. diff --git a/src/resources/extensions/sf/workflow-templates/product-plan.md b/src/resources/extensions/sf/workflow-templates/product-plan.md index e0ced3a04..99a443d25 100644 --- a/src/resources/extensions/sf/workflow-templates/product-plan.md +++ b/src/resources/extensions/sf/workflow-templates/product-plan.md @@ -71,7 +71,7 @@ acceptance path, and verification strategy. **Goal:** Decide whether development can begin without re-deciding the product. 1. Review the product model, scope, and slice plan for missing purpose, missing consumers, untestable behavior, and unresolved blockers. -2. Decide whether product tracking should run now or later. If now, hand off to `/sf start product-tracking`. +2. Decide whether product tracking should run now or later. If now, hand off to `/start product-tracking`. 3. Record verification commands, release gates, remaining open questions, and implementation start criteria. 4. Produce `READY.md` in the artifact directory. diff --git a/src/resources/extensions/sf/workflow-templates/rename-symbol.yaml b/src/resources/extensions/sf/workflow-templates/rename-symbol.yaml index d52556f81..8544b0c75 100644 --- a/src/resources/extensions/sf/workflow-templates/rename-symbol.yaml +++ b/src/resources/extensions/sf/workflow-templates/rename-symbol.yaml @@ -3,7 +3,7 @@ # Rename a symbol across the codebase. Inventories call sites, renames each # file atomically, and verifies the project still builds and tests pass. # -# Run with: /sf workflow rename-symbol old_name=foo new_name=bar +# Run with: /workflow rename-symbol old_name=foo new_name=bar version: 1 name: rename-symbol diff --git a/src/resources/extensions/sf/workflow-templates/small-feature.md b/src/resources/extensions/sf/workflow-templates/small-feature.md index a75db0522..aec81b77b 100644 --- a/src/resources/extensions/sf/workflow-templates/small-feature.md +++ b/src/resources/extensions/sf/workflow-templates/small-feature.md @@ -9,7 +9,7 @@ artifact_dir: .sf/workflows/features/ <purpose> Build a small-to-medium feature with lightweight planning. Designed for work that -needs more structure than /sf quick but doesn't warrant full milestone ceremony. +needs more structure than /quick but doesn't warrant full milestone ceremony. Typical scope: a new command, endpoint, component, or module. </purpose> diff --git a/src/resources/extensions/sf/workspace-index.js b/src/resources/extensions/sf/workspace-index.js index ed87af90e..9573f7bd2 100644 --- a/src/resources/extensions/sf/workspace-index.js +++ b/src/resources/extensions/sf/workspace-index.js @@ -258,14 +258,14 @@ export async function getSuggestedNextCommands(basePath) { ? `${index.active.milestoneId}/${index.active.sliceId}` : index.active.milestoneId; const commands = new Set(); - if (index.active.phase === "planning") commands.add("/sf"); + if (index.active.phase === "planning") commands.add("/next"); if ( index.active.phase === "executing" || index.active.phase === "summarizing" ) - commands.add("/sf autonomous"); - if (scope) commands.add(`/sf doctor ${scope}`); - if (scope) commands.add(`/sf doctor fix ${scope}`); - commands.add("/sf status"); + commands.add("/autonomous"); + if (scope) commands.add(`/doctor ${scope}`); + if (scope) commands.add(`/doctor fix ${scope}`); + commands.add("/status"); return [...commands]; } diff --git a/src/resources/extensions/sf/worktree-command.js b/src/resources/extensions/sf/worktree-command.js index 94657a978..dbf48c6ba 100644 --- a/src/resources/extensions/sf/worktree-command.js +++ b/src/resources/extensions/sf/worktree-command.js @@ -366,7 +366,7 @@ async function handleCreate(basePath, name, ctx) { `This worktree inherited existing SF milestones from the main branch.`, ``, ` Continue — keep milestones and pick up where main left off`, - ` Start fresh — clear milestones so /sf autonomous starts a new project`, + ` Start fresh — clear milestones so /autonomous starts a new project`, ].join("\n"), confirmLabel: "Continue", declineLabel: "Start fresh", @@ -380,7 +380,7 @@ async function handleCreate(basePath, name, ctx) { ? ` ${CLR.muted("Auto-committed on previous branch before switching.")}` : ""; const freshNote = clearedPlans - ? ` ${CLR.ok("✓")} Cleared milestones — ${CLR.hint("/sf autonomous")} will start fresh.` + ? ` ${CLR.ok("✓")} Cleared milestones — ${CLR.hint("/autonomous")} will start fresh.` : ""; ctx.ui.notify( [ diff --git a/src/resources/extensions/sf/worktree-manager.js b/src/resources/extensions/sf/worktree-manager.js index 704f5fa50..964eee216 100644 --- a/src/resources/extensions/sf/worktree-manager.js +++ b/src/resources/extensions/sf/worktree-manager.js @@ -604,7 +604,7 @@ export function removeWorktree(basePath, name, opts = {}) { // worktree remove), force-remove the git internal worktree metadata first, // then remove the filesystem directory. Without this, the .git/worktrees/<name> // lock prevents rmSync from cleaning up, and the orphaned worktree directory - // causes every subsequent `/sf autonomous` to re-enter the stale worktree. + // causes every subsequent `/autonomous` to re-enter the stale worktree. if (existsSync(resolvedWtPath)) { try { const wtInternalDir = join(basePath, ".git", "worktrees", name); diff --git a/src/resources/extensions/sf/worktree-resolver.js b/src/resources/extensions/sf/worktree-resolver.js index 64664defb..7b87ce993 100644 --- a/src/resources/extensions/sf/worktree-resolver.js +++ b/src/resources/extensions/sf/worktree-resolver.js @@ -502,11 +502,11 @@ export class WorktreeResolver { }); // Surface a clear, actionable error. The worktree and milestone branch are // intentionally preserved — nothing has been deleted. The user can retry - // /sf dispatch complete-milestone or merge manually once the underlying + // /dispatch complete-milestone or merge manually once the underlying // issue is fixed (e.g. checkout to wrong branch, unresolved conflicts). // (#1668, #1891) ctx.notify( - `Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry with \`/sf dispatch complete-milestone\` or merge manually.`, + `Milestone merge failed: ${msg}. Your worktree and milestone branch are preserved — retry with \`/dispatch complete-milestone\` or merge manually.`, "warning", ); // Clean up stale merge state left by failed squash-merge (#1389) diff --git a/src/tests/auto-mode-piped.test.ts b/src/tests/autonomous-mode-piped.test.ts similarity index 97% rename from src/tests/auto-mode-piped.test.ts rename to src/tests/autonomous-mode-piped.test.ts index 23ee6616b..f4d77a8ee 100644 --- a/src/tests/auto-mode-piped.test.ts +++ b/src/tests/autonomous-mode-piped.test.ts @@ -36,7 +36,7 @@ function cliSourceHandlesAutonomousBeforeTUI(): boolean { const tuiMatch = cliSource.match(/new\s+InteractiveMode\s*\(/); if (!tuiMatch) return false; - // The auto handler must appear BEFORE the TUI in the source + // The autonomous handler must appear BEFORE the TUI in the source const autoPos = cliSource.indexOf(autoHandlerMatch[0]); const tuiPos = cliSource.indexOf(tuiMatch[0]); @@ -57,7 +57,7 @@ test("cli.ts handles `autonomous` subcommand before interactive TUI (#2732)", () }); // ═══════════════════════════════════════════════════════════════════════════ -// Verify the auto handler routes to headless (not a stub/no-op) +// Verify the autonomous handler routes to headless (not a stub/no-op) // ═══════════════════════════════════════════════════════════════════════════ test("cli.ts routes `autonomous` to headless runner", () => { diff --git a/src/tests/auto-piped-io.test.ts b/src/tests/autonomous-piped-io.test.ts similarity index 81% rename from src/tests/auto-piped-io.test.ts rename to src/tests/autonomous-piped-io.test.ts index ee1d64459..3d78c3b13 100644 --- a/src/tests/auto-piped-io.test.ts +++ b/src/tests/autonomous-piped-io.test.ts @@ -31,12 +31,12 @@ const EXPLICIT_SUBCOMMANDS = new Set([ ]); /** - * Detect whether the current subcommand should be auto-redirected + * Detect whether the current subcommand should be redirected * to machine surface when stdout is not a TTY. * * Returns true when the subcommand is "autonomous" and stdout is piped. */ -function shouldRedirectAutoToHeadless( +function shouldRedirectAutonomousToHeadless( subcommand: string | undefined, stdoutIsTTY: boolean, ): boolean { @@ -63,18 +63,18 @@ function isExplicitSubcommand(subcommand: string | undefined): boolean { return subcommand !== undefined && EXPLICIT_SUBCOMMANDS.has(subcommand); } -// ─── shouldRedirectAutoToHeadless ───────────────────────────────────────── +// ─── shouldRedirectAutonomousToHeadless ───────────────────────────────────────── test("redirects 'autonomous' to headless when stdout is piped", () => { - assert.ok(shouldRedirectAutoToHeadless("autonomous", false)); + assert.ok(shouldRedirectAutonomousToHeadless("autonomous", false)); }); -test("does NOT redirect non-auto subcommands when stdout is piped", () => { - assert.ok(!shouldRedirectAutoToHeadless("auto", false)); - assert.ok(!shouldRedirectAutoToHeadless("headless", false)); - assert.ok(!shouldRedirectAutoToHeadless("config", false)); - assert.ok(!shouldRedirectAutoToHeadless("update", false)); - assert.ok(!shouldRedirectAutoToHeadless(undefined, false)); +test("does NOT redirect non-autonomous subcommands when stdout is piped", () => { + assert.ok(!shouldRedirectAutonomousToHeadless("auto", false)); + assert.ok(!shouldRedirectAutonomousToHeadless("headless", false)); + assert.ok(!shouldRedirectAutonomousToHeadless("config", false)); + assert.ok(!shouldRedirectAutonomousToHeadless("update", false)); + assert.ok(!shouldRedirectAutonomousToHeadless(undefined, false)); }); // ─── canEnterInteractiveMode ────────────────────────────────────────────── @@ -125,7 +125,7 @@ test("scenario: 'sf autonomous 2>&1 | cat' — should redirect to headless", () // Interactive mode should be blocked assert.ok(!canEnterInteractiveMode(stdinIsTTY, stdoutIsTTY)); - assert.ok(shouldRedirectAutoToHeadless(subcommand, stdoutIsTTY)); + assert.ok(shouldRedirectAutonomousToHeadless(subcommand, stdoutIsTTY)); }); test("scenario: 'sf autonomous > /tmp/output.txt' — should redirect to headless", () => { @@ -134,7 +134,7 @@ test("scenario: 'sf autonomous > /tmp/output.txt' — should redirect to headles const stdoutIsTTY = false; assert.ok(!canEnterInteractiveMode(stdinIsTTY, stdoutIsTTY)); - assert.ok(shouldRedirectAutoToHeadless(subcommand, stdoutIsTTY)); + assert.ok(shouldRedirectAutonomousToHeadless(subcommand, stdoutIsTTY)); }); test("scenario: 'sf autonomous' in terminal — normal interactive mode", () => { @@ -143,7 +143,7 @@ test("scenario: 'sf autonomous' in terminal — normal interactive mode", () => const stdoutIsTTY = true; assert.ok(canEnterInteractiveMode(stdinIsTTY, stdoutIsTTY)); - assert.ok(!shouldRedirectAutoToHeadless(subcommand, stdoutIsTTY)); + assert.ok(!shouldRedirectAutonomousToHeadless(subcommand, stdoutIsTTY)); }); test("scenario: 'echo msg | sf autonomous' — stdin piped, should redirect", () => { @@ -151,8 +151,8 @@ test("scenario: 'echo msg | sf autonomous' — stdin piped, should redirect", () const stdinIsTTY = false; const stdoutIsTTY = true; // stdout is TTY even though stdin is piped - // stdout is TTY, so auto redirect doesn't trigger... - assert.ok(!shouldRedirectAutoToHeadless(subcommand, stdoutIsTTY)); + // stdout is TTY, so autonomous redirect doesn't trigger... + assert.ok(!shouldRedirectAutonomousToHeadless(subcommand, stdoutIsTTY)); // ...but interactive mode is blocked because stdin is piped assert.ok(!canEnterInteractiveMode(stdinIsTTY, stdoutIsTTY)); }); @@ -163,5 +163,5 @@ test("scenario: 'echo msg | sf autonomous | cat' — both piped", () => { const stdoutIsTTY = false; assert.ok(!canEnterInteractiveMode(stdinIsTTY, stdoutIsTTY)); - assert.ok(shouldRedirectAutoToHeadless(subcommand, stdoutIsTTY)); + assert.ok(shouldRedirectAutonomousToHeadless(subcommand, stdoutIsTTY)); }); diff --git a/src/tests/auto-resume-resource-loader.test.ts b/src/tests/autonomous-resume-resource-loader.test.ts similarity index 100% rename from src/tests/auto-resume-resource-loader.test.ts rename to src/tests/autonomous-resume-resource-loader.test.ts diff --git a/src/tests/headless-events.test.ts b/src/tests/headless-events.test.ts index 19e72df6e..79843e705 100644 --- a/src/tests/headless-events.test.ts +++ b/src/tests/headless-events.test.ts @@ -198,11 +198,11 @@ import { EXIT_CANCELLED, EXIT_ERROR, EXIT_SUCCESS, - isAutoResumeScheduledNotification, isBlockedNotification, isInteractiveHeadlessTool, isMilestoneReadyText, isPauseNotification, + isScheduledResumeNotification, isTerminalNotification, mapStatusToExitCode, shouldArmHeadlessIdleTimeout, @@ -255,7 +255,7 @@ test("isInteractiveHeadlessTool: non-interactive tools stay false", () => { assert.equal(isInteractiveHeadlessTool(undefined), false); }); -test("isTerminalNotification: auto pause is terminal for headless callers", () => { +test("isTerminalNotification: autonomous pause is terminal for headless callers", () => { assert.equal( isTerminalNotification({ type: "extension_ui_request", @@ -267,7 +267,7 @@ test("isTerminalNotification: auto pause is terminal for headless callers", () = ); }); -test("isBlockedNotification: auto pause exits as blocked", () => { +test("isBlockedNotification: autonomous pause exits as blocked", () => { assert.equal( isBlockedNotification({ type: "extension_ui_request", @@ -278,17 +278,18 @@ test("isBlockedNotification: auto pause exits as blocked", () => { ); }); -test("isAutoResumeScheduledNotification detects provider auto-resume notices", () => { +test("isScheduledResumeNotification detects provider scheduled resume notices", () => { assert.equal( - isAutoResumeScheduledNotification({ + isScheduledResumeNotification({ type: "extension_ui_request", method: "notify", - message: "Rate limited: rate limit exceeded. Auto-resuming in 60s...", + message: + "Rate limited: rate limit exceeded. Resuming automatically in 60s...", }), true, ); assert.equal( - isAutoResumeScheduledNotification({ + isScheduledResumeNotification({ type: "extension_ui_request", method: "notify", message: "Autonomous mode paused (Escape). Type to interact.", @@ -308,7 +309,7 @@ test("isMilestoneReadyText detects ready marker in assistant text stream", () => ); }); -test("isPauseNotification detects pause banners separately from auto-resume notices", () => { +test("isPauseNotification detects pause banners separately from scheduled resume notices", () => { assert.equal( isPauseNotification({ type: "extension_ui_request", @@ -321,7 +322,8 @@ test("isPauseNotification detects pause banners separately from auto-resume noti isPauseNotification({ type: "extension_ui_request", method: "notify", - message: "Rate limited: rate limit exceeded. Auto-resuming in 60s...", + message: + "Rate limited: rate limit exceeded. Resuming automatically in 60s...", }), false, ); diff --git a/src/tests/integration/e2e-headless.test.ts b/src/tests/integration/e2e-headless.test.ts index 1cab16cfc..f24ba708c 100644 --- a/src/tests/integration/e2e-headless.test.ts +++ b/src/tests/integration/e2e-headless.test.ts @@ -38,6 +38,7 @@ type RunResult = { stdout: string; stderr: string; code: number | null; + signal: NodeJS.Signals | null; timedOut: boolean; }; @@ -75,9 +76,9 @@ function runSf( child.kill("SIGTERM"); }, timeoutMs); - child.on("close", (code) => { + child.on("close", (code, signal) => { clearTimeout(timer); - resolve({ stdout, stderr, code, timedOut }); + resolve({ stdout, stderr, code, signal, timedOut }); }); }); } @@ -117,9 +118,9 @@ function spawnSf( }, timeoutMs); const result = new Promise<RunResult>((resolve) => { - child.on("close", (code) => { + child.on("close", (code, signal) => { clearTimeout(timer); - resolve({ stdout, stderr, code, timedOut }); + resolve({ stdout, stderr, code, signal, timedOut }); }); }); @@ -326,8 +327,11 @@ test("headless exits with code 11 after SIGINT", async (_t) => { const combined = stripAnsi(result.stdout + result.stderr); assertNoCrashMarkers(combined); assert.ok( - result.code === 0 || result.code === 1 || result.code === 11, - `expected clean exit (0, 1, or 11), got ${result.code}`, + result.code === 0 || + result.code === 1 || + result.code === 11 || + result.signal === "SIGINT", + `expected clean exit (0, 1, 11, or SIGINT signal), got code=${result.code} signal=${result.signal}`, ); } }); diff --git a/src/tests/integration/web-command-parity-contract.test.ts b/src/tests/integration/web-command-parity-contract.test.ts index c890d062c..9ffd52d39 100644 --- a/src/tests/integration/web-command-parity-contract.test.ts +++ b/src/tests/integration/web-command-parity-contract.test.ts @@ -193,47 +193,35 @@ describe("browser-local aliases and legacy helpers stay explicit", () => { }); }); -test("registered SF command roots stay on the prompt/extension path", async () => { +test("registered SF command roots expose direct browser outcomes or prompt passthrough", async () => { const registeredRoots = await collectRegisteredSfCommandRoots(); - assert.deepEqual( - registeredRoots, - ["exit", "kill", "sf", "worktree", "wt"], - "browser parity contract only expects the current SF command roots", + assert.ok( + registeredRoots.includes("autonomous"), + "browser parity contract should include the canonical autonomous root", + ); + assert.ok( + !registeredRoots.includes("sf"), + "SF extension roots are registered directly; legacy sf prefix is not a browser root", ); - // Non-sf roots are extension commands that pass through to the bridge. - // Derived dynamically so adding a new registration fails this assertion loudly. - const nonSfRoots = registeredRoots.filter((r) => r !== "sf" && r !== "exit"); - assert.equal( - nonSfRoots.length, - 3, - "expected exactly 3 non-sf passthrough roots; update this count when adding registrations", - ); - for (const root of nonSfRoots) { - assertPromptPassthrough(`/${root}`); + for (const root of registeredRoots.filter((r) => r !== "exit")) { + const outcome = dispatchBrowserSlashCommand(`/${root}`); + const expected = EXPECTED_SF_OUTCOMES.get(root); + if (expected) { + assert.equal(outcome.kind, expected, `/${root} should dispatch as ${expected}`); + } else { + assert.equal(outcome.kind, "prompt", `/${root} should pass through to extension bridge`); + } } - - // Bare /sf passes through to bridge (equivalent to /sf next) - const bareSf = dispatchBrowserSlashCommand("/sf"); - assert.equal(bareSf.kind, "prompt", "bare /sf should pass through to bridge"); - assert.equal( - bareSf.command.message, - "/sf", - "bare /sf should preserve exact input", - ); }); -describe("current SF command family samples dispatch to correct outcomes after S02", () => { - test("/sf (bare) still passes through to bridge", () => { - assertPromptPassthrough("/sf"); - }); - - test("/sf status now dispatches to surface", () => { - const outcome = dispatchBrowserSlashCommand("/sf status"); +describe("current SF command family samples dispatch to direct outcomes", () => { + test("/status now dispatches to surface", () => { + const outcome = dispatchBrowserSlashCommand("/status"); assert.equal( outcome.kind, "surface", - "/sf status should dispatch to surface after T01", + "/status should dispatch to surface", ); assert.equal(outcome.surface, "sf-status"); }); @@ -244,18 +232,18 @@ describe("current SF command family samples dispatch to correct outcomes after S assertPromptPassthrough("/kill"); }); - test("/sf status dispatches to surface regardless of streaming state", () => { - const streaming = dispatchBrowserSlashCommand("/sf status", { + test("/status dispatches to surface regardless of streaming state", () => { + const streaming = dispatchBrowserSlashCommand("/status", { isStreaming: true, }); assert.equal( streaming.kind, "surface", - "/sf status should be surface even when streaming", + "/status should be surface even when streaming", ); assert.equal(streaming.surface, "sf-status"); - const idle = dispatchBrowserSlashCommand("/sf status", { + const idle = dispatchBrowserSlashCommand("/status", { isStreaming: false, }); assert.equal(idle.kind, "surface"); @@ -285,13 +273,11 @@ const EXPECTED_SF_OUTCOMES = new Map< ["hooks", "surface"], ["mode", "surface"], ["steer", "surface"], - ["export", "surface"], ["cleanup", "surface"], ["queue", "surface"], // Bridge passthrough (9) ["autonomous", "prompt"], ["next", "prompt"], - ["stop", "prompt"], ["pause", "prompt"], ["skip", "prompt"], ["discuss", "prompt"], @@ -302,101 +288,76 @@ const EXPECTED_SF_OUTCOMES = new Map< ["help", "local"], ]); -describe("every registered /sf subcommand has an explicit browser dispatch outcome", () => { +describe("every browser-owned SF command has an explicit browser dispatch outcome", () => { assert.equal( EXPECTED_SF_OUTCOMES.size, - 30, - "EXPECTED_SF_OUTCOMES must cover all 30 SF subcommands (19 surface + 1 view-navigate + 9 passthrough + 1 help)", + 28, + "EXPECTED_SF_OUTCOMES must cover direct browser SF commands (18 surface + 1 view-navigate + 8 passthrough + 1 help)", ); for (const [subcommand, expectedKind] of EXPECTED_SF_OUTCOMES) { - test(`/sf ${subcommand} -> ${expectedKind}`, () => { - const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`); + test(`/${subcommand} -> ${expectedKind}`, () => { + const outcome = dispatchBrowserSlashCommand(`/${subcommand}`); assert.equal( outcome.kind, expectedKind, - `/sf ${subcommand} should dispatch to ${expectedKind}, got ${outcome.kind}`, + `/${subcommand} should dispatch to ${expectedKind}, got ${outcome.kind}`, ); }); if (expectedKind === "surface") { - test(`/sf ${subcommand} opens sf-${subcommand} surface`, () => { - const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`) as any; + test(`/${subcommand} opens sf-${subcommand} surface`, () => { + const outcome = dispatchBrowserSlashCommand(`/${subcommand}`) as any; assert.equal( outcome.surface, `sf-${subcommand}`, - `/sf ${subcommand} should open the sf-${subcommand} surface`, + `/${subcommand} should open the sf-${subcommand} surface`, ); }); } if (expectedKind === "prompt") { - test(`/sf ${subcommand} preserves exact input text`, () => { - const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`) as any; + test(`/${subcommand} preserves exact input text`, () => { + const outcome = dispatchBrowserSlashCommand(`/${subcommand}`) as any; assert.equal( outcome.command.message, - `/sf ${subcommand}`, - `/sf ${subcommand} should preserve exact input text for bridge delivery`, + `/${subcommand}`, + `/ should preserve exact input text for bridge delivery`, ); }); } if (expectedKind === "local") { - test(`/sf ${subcommand} dispatches to sf_help action`, () => { - const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`) as any; + test(`/${subcommand} dispatches to sf_help action`, () => { + const outcome = dispatchBrowserSlashCommand(`/${subcommand}`) as any; assert.equal( outcome.action, "sf_help", - `/sf ${subcommand} should dispatch to sf_help action`, + `/ should dispatch to sf_help action`, ); }); } if (expectedKind === "view-navigate") { - test(`/sf ${subcommand} navigates to the ${subcommand} view`, () => { - const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`) as any; + test(`/${subcommand} navigates to the ${subcommand} view`, () => { + const outcome = dispatchBrowserSlashCommand(`/${subcommand}`) as any; assert.equal( outcome.view, subcommand, - `/sf ${subcommand} should navigate to the ${subcommand} view`, + `/ should navigate to the ${subcommand} view`, ); }); } } }); -describe("SF dispatch edge cases", () => { - test("/sf (bare, no subcommand) passes through to bridge", () => { - const outcome = dispatchBrowserSlashCommand("/sf"); - assert.equal(outcome.kind, "prompt"); - assert.equal(outcome.command.message, "/sf"); - }); - - test("/sf help dispatches to local sf_help action", () => { - const outcome = dispatchBrowserSlashCommand("/sf help"); +describe("direct SF dispatch edge cases", () => { + test("/help dispatches to local sf_help action", () => { + const outcome = dispatchBrowserSlashCommand("/help"); assert.equal(outcome.kind, "local"); assert.equal(outcome.action, "sf_help"); }); - test("/sf unknown-xyz passes through to bridge", () => { - const outcome = dispatchBrowserSlashCommand("/sf unknown-xyz"); - assert.equal( - outcome.kind, - "prompt", - "unknown subcommand should pass through to bridge", - ); - assert.equal( - outcome.command.message, - "/sf unknown-xyz", - "unknown subcommand should preserve exact input", - ); - assert.equal( - outcome.slashCommandName, - "sf", - "unknown subcommand should identify as sf command", - ); - }); - test("/export is built-in session export, not sf-export", () => { const outcome = dispatchBrowserSlashCommand("/export"); assert.equal(outcome.kind, "surface"); @@ -407,18 +368,8 @@ describe("SF dispatch edge cases", () => { ); }); - test("/sf export is SF milestone export, distinct from built-in /export", () => { - const outcome = dispatchBrowserSlashCommand("/sf export"); - assert.equal(outcome.kind, "surface"); - assert.equal( - outcome.surface, - "sf-export", - "/sf export should be the SF milestone export surface", - ); - }); - - test("/sf forensics detailed preserves sub-args", () => { - const outcome = dispatchBrowserSlashCommand("/sf forensics detailed"); + test("/forensics detailed preserves args", () => { + const outcome = dispatchBrowserSlashCommand("/forensics detailed"); assert.equal(outcome.kind, "surface"); assert.equal(outcome.surface, "sf-forensics"); assert.equal( @@ -429,14 +380,14 @@ describe("SF dispatch edge cases", () => { }); test("SF surface commands produce system terminal notice", () => { - const outcome = dispatchBrowserSlashCommand("/sf status"); + const outcome = dispatchBrowserSlashCommand("/status"); const notice = getBrowserSlashCommandTerminalNotice(outcome); assert.ok(notice, "surface outcome should produce a terminal notice"); assert.equal(notice.type, "system"); }); test("SF passthrough commands produce no terminal notice", () => { - const outcome = dispatchBrowserSlashCommand("/sf autonomous"); + const outcome = dispatchBrowserSlashCommand("/autonomous"); const notice = getBrowserSlashCommandTerminalNotice(outcome); assert.equal( notice, @@ -453,13 +404,13 @@ describe("every SF surface dispatches through the contract wiring end-to-end", ( assert.equal( sfSurfaces.length, - 19, - "should have exactly 19 SF surface subcommands", + 18, + "should have exactly 18 SF surface commands", ); for (const [subcommand] of sfSurfaces) { - test(`/sf ${subcommand} -> dispatch -> open request -> surface state`, () => { - const outcome = dispatchBrowserSlashCommand(`/sf ${subcommand}`); + test(`/${subcommand} -> dispatch -> open request -> surface state`, () => { + const outcome = dispatchBrowserSlashCommand(`/${subcommand}`); assert.equal(outcome.kind, "surface"); const openRequest = surfaceOutcomeToOpenRequest(outcome, {}); @@ -500,8 +451,8 @@ describe("every SF surface dispatches through the contract wiring end-to-end", ( } }); -test("/sf visualize dispatches as view-navigate to the visualizer view", () => { - const outcome = dispatchBrowserSlashCommand("/sf visualize"); +test("/visualize dispatches as view-navigate to the visualizer view", () => { + const outcome = dispatchBrowserSlashCommand("/visualize"); assert.equal(outcome.kind, "view-navigate"); assert.equal(outcome.view, "visualize"); }); diff --git a/src/tests/integration/web-diagnostics-contract.test.ts b/src/tests/integration/web-diagnostics-contract.test.ts index 9fc3de36d..c6da762cc 100644 --- a/src/tests/integration/web-diagnostics-contract.test.ts +++ b/src/tests/integration/web-diagnostics-contract.test.ts @@ -283,35 +283,36 @@ describe("diagnostics contract state", () => { // ─── Block 3: Dispatch→surface pipeline (R103, R104, R105) ────────────────── describe("diagnostics dispatch→surface pipeline", () => { - it("/sf forensics dispatches to sf-forensics surface", () => { - const outcome = dispatchBrowserSlashCommand("/sf forensics", {}); + it("/forensics dispatches to sf-forensics surface", () => { + const outcome = dispatchBrowserSlashCommand("/forensics", {}); assert.equal(outcome.kind, "surface"); if (outcome.kind === "surface") { assert.equal(outcome.surface, "sf-forensics"); } }); - it("/sf doctor dispatches to sf-doctor surface", () => { - const outcome = dispatchBrowserSlashCommand("/sf doctor", {}); + it("/doctor dispatches to sf-doctor surface", () => { + const outcome = dispatchBrowserSlashCommand("/doctor", {}); assert.equal(outcome.kind, "surface"); if (outcome.kind === "surface") { assert.equal(outcome.surface, "sf-doctor"); } }); - it("/sf skill-health dispatches to sf-skill-health surface", () => { - const outcome = dispatchBrowserSlashCommand("/sf skill-health", {}); + it("/skill-health dispatches to sf-skill-health surface", () => { + const outcome = dispatchBrowserSlashCommand("/skill-health", {}); assert.equal(outcome.kind, "surface"); if (outcome.kind === "surface") { assert.equal(outcome.surface, "sf-skill-health"); } }); - it("/sf doctor fix dispatches to sf-doctor surface with args", () => { - const outcome = dispatchBrowserSlashCommand("/sf doctor fix", {}); + it("/doctor fix dispatches to sf-doctor surface with args", () => { + const outcome = dispatchBrowserSlashCommand("/doctor fix", {}); assert.equal(outcome.kind, "surface"); if (outcome.kind === "surface") { assert.equal(outcome.surface, "sf-doctor"); + assert.equal(outcome.args, "fix"); } }); }); diff --git a/src/tests/integration/web-mode-onboarding.test.ts b/src/tests/integration/web-mode-onboarding.test.ts index 03e015fd6..f4176d438 100644 --- a/src/tests/integration/web-mode-onboarding.test.ts +++ b/src/tests/integration/web-mode-onboarding.test.ts @@ -519,7 +519,36 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un SF_WEB_TEST_FAKE_API_KEY_VALIDATION: "1", ANTHROPIC_API_KEY: "", OPENAI_API_KEY: "", + OPENROUTER_API_KEY: "", GOOGLE_API_KEY: "", + GEMINI_API_KEY: "", + GOOGLE_GENERATIVE_AI_API_KEY: "", + GROQ_API_KEY: "", + CEREBRAS_API_KEY: "", + XAI_API_KEY: "", + AI_GATEWAY_API_KEY: "", + ZAI_API_KEY: "", + MISTRAL_API_KEY: "", + MINIMAX_API_KEY: "", + MINIMAX_CN_API_KEY: "", + HF_TOKEN: "", + OPENCODE_API_KEY: "", + OPENCODE_GO_API_KEY: "", + KIMI_API_KEY: "", + XIAOMIAI_API_KEY: "", + XIAOMI_API_KEY: "", + ALIBABA_API_KEY: "", + DASHSCOPE_API_KEY: "", + OLLAMA_API_KEY: "", + CUSTOM_OPENAI_API_KEY: "", + LONGCAT_API_KEY: "", + AWS_ACCESS_KEY_ID: "", + AWS_SECRET_ACCESS_KEY: "", + AWS_PROFILE: "", + AWS_BEARER_TOKEN_BEDROCK: "", + AWS_CONTAINER_CREDENTIALS_RELATIVE_URI: "", + AWS_CONTAINER_CREDENTIALS_FULL_URI: "", + AWS_WEB_IDENTITY_TOKEN_FILE: "", }, }); port = launch.port; diff --git a/web/lib/browser-slash-command-dispatch.ts b/web/lib/browser-slash-command-dispatch.ts index 812d62dca..7135a56bc 100644 --- a/web/lib/browser-slash-command-dispatch.ts +++ b/web/lib/browser-slash-command-dispatch.ts @@ -13,7 +13,7 @@ export type BrowserSlashCommandSurface = | "logout" | "session" | "export" - // SF subcommand surfaces (S02) + // SF command surfaces | "sf-status" | "sf-visualize" | "sf-forensics" @@ -114,9 +114,9 @@ const SURFACE_COMMANDS = new Map<string, BrowserSlashCommandSurface>([ ["export", "export"], ]); -// --- SF subcommand dispatch (S02) --- +// --- SF command dispatch --- -const SF_SURFACE_SUBCOMMANDS = new Map<string, BrowserSlashCommandSurface>([ +const SF_SURFACE_COMMANDS = new Map<string, BrowserSlashCommandSurface>([ ["status", "sf-status"], ["visualize", "sf-visualize"], ["forensics", "sf-forensics"], @@ -129,21 +129,18 @@ const SF_SURFACE_SUBCOMMANDS = new Map<string, BrowserSlashCommandSurface>([ ["history", "sf-history"], ["undo", "sf-undo"], ["inspect", "sf-inspect"], - ["model", "model"], ["prefs", "sf-prefs"], ["config", "sf-config"], ["hooks", "sf-hooks"], ["mode", "sf-mode"], ["steer", "sf-steer"], - ["export", "sf-export"], ["cleanup", "sf-cleanup"], ["queue", "sf-queue"], ]); -const SF_PASSTHROUGH_SUBCOMMANDS = new Set<string>([ +const SF_PASSTHROUGH_COMMANDS = new Set<string>([ "autonomous", "next", - "stop", "pause", "skip", "discuss", @@ -152,79 +149,60 @@ const SF_PASSTHROUGH_SUBCOMMANDS = new Set<string>([ "remote", ]); -export const SF_HELP_TEXT = `Available /sf subcommands: +export const SF_HELP_TEXT = `Available SF commands: -Workflow: next · autonomous · stop · pause · skip · queue · quick · capture · triage +Workflow: next · autonomous · autonomous stop · pause · skip · queue · quick · capture · triage Diagnostics: status · visualize · forensics · doctor · skill-health · inspect Context: knowledge · history · undo · discuss Settings: model · prefs · config · hooks · mode · steer Advanced: export · cleanup · run-hook · migrate · remote -Type /sf <subcommand> to run. Use /sf help for this message.`; +Type /<command> to run. Use /help for this message.`; -function dispatchSFSubcommand( +function dispatchSFCommand( input: string, + commandName: string, args: string, options: BrowserSlashCommandDispatchOptions, ): BrowserSlashCommandDispatchResult { - const trimmedArgs = args.trim(); - const spaceIndex = trimmedArgs.search(/\s/); - const subcommand = - spaceIndex === -1 ? trimmedArgs : trimmedArgs.slice(0, spaceIndex); - const subArgs = - spaceIndex === -1 ? "" : trimmedArgs.slice(spaceIndex + 1).trim(); - - // Bare `/sf` — equivalent to `/sf next`, pass through to bridge - if (!subcommand) { - return { - kind: "prompt", - input, - slashCommandName: "sf", - command: { - type: getPromptCommandType(options), - message: input, - }, - }; - } - - // `/sf help` — render inline help locally - if (subcommand === "help") { + // `/help` — render inline help locally + if (commandName === "help") { return { kind: "local", input, - commandName: "sf", + commandName, action: "sf_help", }; } - // `/sf visualize` — navigate to the visualizer view directly - if (subcommand === "visualize") { + // `/visualize` — navigate to the visualizer view directly + if (commandName === "visualize") { return { kind: "view-navigate", input, - commandName: "sf", + commandName, view: "visualize", }; } - // Surface-routed subcommands — open browser-native UI - const surface = SF_SURFACE_SUBCOMMANDS.get(subcommand); + // Surface-routed commands — open browser-native UI + const surface = SF_SURFACE_COMMANDS.get(commandName); if (surface) { return { kind: "surface", input, - commandName: "sf", + commandName, surface, - args: subArgs, + args, }; } - // Bridge-passthrough subcommands — let the extension handle them - if (SF_PASSTHROUGH_SUBCOMMANDS.has(subcommand)) { + // Bridge-passthrough commands — let the extension handle them + if (SF_PASSTHROUGH_COMMANDS.has(commandName)) { return { kind: "prompt", input, - slashCommandName: "sf", + slashCommandName: commandName, command: { type: getPromptCommandType(options), message: input, @@ -232,16 +210,7 @@ function dispatchSFSubcommand( }; } - // Unknown subcommand — pass through; extension handler will show "Unknown" - return { - kind: "prompt", - input, - slashCommandName: "sf", - command: { - type: getPromptCommandType(options), - message: input, - }, - }; + return buildDeferredBuiltinReject(input, commandName); } function parseSlashCommand( @@ -359,10 +328,13 @@ export function dispatchBrowserSlashCommand( }; } - // SF subcommand dispatch — must precede SURFACE_COMMANDS to avoid - // `/sf export` colliding with the built-in `/export` surface. - if (parsed.name === "sf") { - return dispatchSFSubcommand(trimmed, parsed.args, options); + if ( + parsed.name === "help" || + parsed.name === "visualize" || + SF_SURFACE_COMMANDS.has(parsed.name) || + SF_PASSTHROUGH_COMMANDS.has(parsed.name) + ) { + return dispatchSFCommand(trimmed, parsed.name, parsed.args, options); } const browserSurface = SURFACE_COMMANDS.get(parsed.name);