From 22cbd83675303bb3a7db62223a8f606323ffeccb Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Sat, 9 May 2026 00:17:47 +0200 Subject: [PATCH] fix: update test snapshots for queryInstruction and complete /sf prefix Phase 2 deprecation - Fix memory-embeddings-llm-gateway tests: add queryInstruction field to expected config objects after loadGatewayConfigFromEnv was updated to return it - Add STYLEGUIDE.md: SF code standards adapted from ace-coder patterns (purpose doctrine, principles, anti-patterns STY001-012, thresholds, naming, patterns, documentation sections) - Phase 2 /sf prefix removal: update all web components, browser dispatch, and tests to use direct commands (/autonomous, /stop, /next, /discuss, /init, /new-milestone) instead of /sf-prefixed forms - workflow-actions.ts: all command strings updated - chat-mode.tsx: SF_ACTIONS array updated - project-welcome.tsx: primaryCommand values updated - command-surface.tsx: fallback display updated - remaining-command-panels.tsx: usage examples updated - browser-slash-command-dispatch.ts: add stop/new-milestone/init to SF_PASSTHROUGH_COMMANDS so they route correctly to the extension - recovery-diagnostics-service.ts: suggestion commands updated - welcome-screen.ts: hint text updated - All affected tests updated to match new command strings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- STYLEGUIDE.md | 271 ++++++++++++++++++ copilot-thoughts.md | 2 +- .../memory-embeddings-llm-gateway.test.mjs | 4 + .../web-recovery-diagnostics-contract.test.ts | 2 +- .../web-workflow-controls-contract.test.ts | 42 +-- src/tests/welcome-screen.test.ts | 2 +- src/web/recovery-diagnostics-service.ts | 12 +- src/welcome-screen.ts | 2 +- web/components/sf/chat-mode.tsx | 42 +-- web/components/sf/command-surface.tsx | 2 +- web/components/sf/project-welcome.tsx | 10 +- .../sf/remaining-command-panels.tsx | 6 +- web/lib/browser-slash-command-dispatch.ts | 3 + web/lib/workflow-actions.ts | 30 +- 14 files changed, 354 insertions(+), 76 deletions(-) create mode 100644 STYLEGUIDE.md diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md new file mode 100644 index 000000000..aaa0d6658 --- /dev/null +++ b/STYLEGUIDE.md @@ -0,0 +1,271 @@ +# SF Code Standards + +Code patterns for AI-assisted development. Full rules: [AGENTS.md](AGENTS.md) · Planning contract: [docs/adr/0000-purpose-to-software-compiler.md](docs/adr/0000-purpose-to-software-compiler.md) + +--- + +## Quick Index + +Agent-facing docs are for model consumption first: terse, structured, low-ceremony. Compress wording, not semantics — never remove purpose, value, consumer, consequence, invariants, or action thresholds to save tokens. + +| Section | Description | +|---------|-------------| +| [1. Purpose Doctrine](#1-purpose-doctrine) | The #1 rule: every symbol must answer why it exists | +| [2. Principles](#2-principles) | Core coding principles | +| [3. Anti-Patterns](#3-anti-patterns) | Blocked patterns and required replacements | +| [4. Thresholds](#4-thresholds) | Code quality limits | +| [5. Naming](#5-naming) | Naming conventions | +| [6. Patterns](#6-patterns) | Architectural patterns | +| [7. Documentation](#7-documentation) | JSDoc / comment standards | + +--- + +## 1. Purpose Doctrine + +**Purpose is the most important thing in any symbol.** + +Every exported function, class, constant, and module must answer: + +- **why** it exists (not what it does — the signature shows that) +- **what value** it creates or protects +- **who** calls it in production (a real consumer, not just tests) +- **what breaks** if it returns the wrong answer + +If any answer is missing: `BLOCKED: purpose unclear — [field]`. + +### JSDoc format + +```js +/** + * Acquire a unit claim atomically. Returns true on success, false if another + * worker already holds an unexpired lease. + * + * Purpose: prevent two workers from dispatching the same unit when the + * run-lock is unavailable — the conditional UPDATE is the safety net. + * + * Consumer: autonomous dispatch.ts when picking the next eligible unit per + * poll tick. + */ +export function claimUnit(unitId, leaseMs) { ... } +``` + +Required sections for non-trivial exports: + +- **First line** — what it returns / does, present tense. +- **Purpose:** — why it exists; the value it protects. +- **Consumer:** — who calls it in production. No consumer = symbol shouldn't exist yet. + +A bare `/** Helper. */` is a code smell. Either write the purpose or delete the symbol. + +### Module-level JSDoc + +```js +// session-recorder.js — per-process session lifecycle manager +// +// Purpose: capture the session/turn/file-touch/ref stream into DB rows so +// the memory pipeline has structured data to embed and cross-session search +// has rows to query. +// +// Consumer: bootstrap/register-hooks.js wires all 7 lifecycle events here. +``` + +--- + +## 2. Principles + +| Principle | Rule | +|-----------|------| +| **Purpose first** | No symbol ships without a clear why, value, consumer, and falsifier. | +| **Single responsibility** | One concern per module/function. Adding a second concern = split or extract. | +| **DRY** | Single source of truth for mappings, defaults, and shared logic. | +| **Self-documenting names** | Names reveal intent. A comment explaining *what* something is = rename it. | +| **Constants over magic values** | No raw defaults, timeouts, or limits in logic. Named constants only. | +| **Observability** | Failures log at `logWarning` / `logError`. Happy path stays silent. | +| **Dead code zero** | No unused exports, no commented-out blocks, no unreachable branches. | +| **Small units** | Stay within thresholds (§ 4). Extract or split when approaching limits. | +| **Fallbacks only when real** | A fallback that can't deliver working behavior is noise. Omit it. | +| **Finish bounded refactors** | Rewire and remove the old path in the same PR. No shims, no dual paths. | +| **Single writer** | `sf-db.js` is the only file that issues write SQL. All others call its exports. | +| **Spec-first TDD** | Write the failing test before implementing. Test name = contract claim. | + +--- + +## 3. Anti-Patterns + +| Anti-pattern | Why | Required replacement | Rule | +|---|---|---|---| +| `throw new Error(...)` bare in business logic | Callers can't distinguish failure classes | Throw with a descriptive prefix: `throw new Error("session-recorder.initSessionRecorder: db unavailable")` | **STY001** | +| Silent `catch` swallowing | Hides breakage | `logWarning(module, msg)` then decide: re-throw or return explicit failure | **STY002** | +| Magic status strings inline | Spreads typo-prone comparisons | Named constant or exported string literal at definition site | **STY003** | +| Generic names: `utils`, `helpers`, `common`, `misc` | Unsearchable, no domain signal | Name by capability: `memory-source-store.js`, `embed-circuit.js` | **STY004** | +| `// TODO: fix later` without ticket / owner | Permanent invisible debt | Fix now, or add a dated `// TODO(owner): ` with `node scripts/tech-debt-scan.mjs` visibility | **STY005** | +| Calling `db.prepare(...)` outside `sf-db.js` | Breaks single-writer invariant | Add an exported wrapper in `sf-db.js` | **STY006** | +| Embedding logic in hook wiring | Blurs responsibilities; untestable | Extract to a purpose-named module; wire only the call in `register-hooks.js` | **STY007** | +| Docstring = "Helper." or no docstring | Purpose is invisible to RAG and reviewers | Full JSDoc with Purpose + Consumer (§ 1) | **STY008** | +| Bare `process.env.FOO` scattered in logic | Config not auditable or testable | Named constant + `loadXxxConfigFromEnv()` function with null-guard | **STY009** | +| Test name = `"test X"` / `"works"` | Not a contract claim | `what_when_expected` form: `claimUnit_whenLeaseExpired_returnsTrue` | **STY010** | +| Mechanical test (counts mocks, not behavior) | Breaks on refactors that don't change behavior | Test what the *consumer receives*; label implementation guards `// guard:` | **STY011** | +| Committing to `dist/` or `~/.sf/agent/` | Generated output, not source | `dist/` is gitignored build output; run `npm run copy-resources` to rebuild | **STY012** | + +--- + +## 4. Thresholds + +Two-tier: **Warn** = flag in review; **Error** = blocks merge. + +| Metric | Warn | Error | +|--------|------|-------| +| Function lines | 50 | 75 | +| File lines | 800 | 1500 | +| Function arguments | 5 | 8 | +| Nesting depth | 4 | 6 | +| Dead code | 0 tolerance | — | +| `TODO`/`FIXME` count | per `tech-debt-scan.mjs` thresholds | — | + +Infrastructure files (`sf-db.js`, generated schemas) may exceed file-line limits when extraction would harm clarity. Add a comment explaining why. + +--- + +## 5. Naming + +### Files + +| Kind | Convention | Example | +|------|-----------|---------| +| Module | `kebab-case.js` | `session-recorder.js`, `memory-embeddings-llm-gateway.js` | +| Test | `kebab-case.test.mjs` / `.test.ts` | `sf-db-migration.test.mjs` | +| Prompt template | `kebab-case.md` | `execute-task.md` | +| Bootstrap/wiring | `register-hooks.js`, `init-*.js` | — | + +### Functions and variables + +- **Verb + noun**: `createGatewayEmbedFn`, `recordTurnStart`, `listUnembeddedMemoryIds` +- **No vague verbs alone**: not `run`, `do`, `handle` — add the object +- **No marketing words**: not `simple`, `unified`, `enhanced`, `smart` +- **Verbose over abbreviated**: `embeddingModel` not `embModel`; `queryInstruction` not `queryInstr` +- **Predicate booleans**: `embedCircuitIsOpen()`, `isDbAvailable()` — reads as a question + +### Constants + +| Pattern | Use for | Example | +|---------|---------|---------| +| `DEFAULT_*` | Default values | `DEFAULT_EMBEDDING_MODEL`, `DEFAULT_TIMEOUT_MS` | +| `MAX_*`, `MIN_*` | Bounds | `MAX_PER_INVOCATION`, `MIN_INTERVAL_MS` | +| `*_THRESHOLD` | Trigger limits | `EMBED_CIRCUIT_THRESHOLD` | +| `*_TO_*`, `*_MAP` | Domain A → B mappings | `UNIT_TYPE_TO_LABEL` | +| `ENV_*` | Env var name strings | `ENV_KEY`, `ENV_EMBED_MODEL` | +| `SCHEMA_VERSION` | Single integer, bumped per migration | — | + +--- + +## 6. Patterns + +### Single-writer DB + +`sf-db.js` is the only file that prepares and executes write SQL. All other modules call exported wrappers. This makes the write surface auditable, testable, and migration-safe. + +```js +// ✅ Correct — call the exported wrapper +import { upsertSession } from "./sf-db.js"; +upsertSession({ id, cwd, branch }); + +// ❌ Wrong — raw SQL outside sf-db.js +const stmt = db.prepare("INSERT INTO sessions ..."); +``` + +### Config from env + +Always read env vars through a named `loadXxxConfigFromEnv()` function that returns `null` when required keys are absent (opt-in) or throws with a clear message (required). + +```js +export function loadGatewayConfigFromEnv() { + const keyEntry = firstEnvEntry(KEY_ALIASES); + if (!keyEntry) return null; // opt-in: absent = no-op + ... + return { url, apiKey, embeddingModel, queryInstruction }; +} +``` + +### Circuit breaker + +When a remote dependency can stall (timeout), implement a circuit breaker that: +- Counts consecutive failures +- Opens for `CIRCUIT_OPEN_MS` after `THRESHOLD` failures +- Logs once per open period (throttled) +- Half-opens automatically after cooldown + +See `embedCircuit` in `memory-embeddings-llm-gateway.js` as the reference. + +### Asymmetric embeddings (Qwen3) + +Qwen3-Embedding uses asymmetric retrieval. Always pass `instruction` for queries; omit for documents. + +```js +// Query embedding — instruction required +const embedFn = createGatewayEmbedFn(cfg, { instruction: cfg.queryInstruction }); + +// Document/backfill embedding — no instruction +const embedFn = createGatewayEmbedFn(cfg); +``` + +### Hook wiring + +`bootstrap/register-hooks.js` wires lifecycle events to module functions. Keep each hook body thin: import, call, done. No business logic in hooks. + +```js +pi.on("agent_end", async (event) => { + const text = event.messages?.at(-1)?.content?.find(b => b.type === "text")?.text ?? ""; + await recordTurnEnd(text); +}); +``` + +### Test contracts + +Test names are contract claims: `what_when_expected`. + +```js +// ✅ Contract claim +test("claimUnit_whenLeaseExpired_returnsTrue", () => { ... }); + +// ❌ Not a contract +test("claimUnit works", () => { ... }); +``` + +Three tiers: +1. **Behaviour contracts** — what the consumer receives. Primary. Spec. +2. **Degradation contracts** — what happens when dependencies fail (DB down, gateway unreachable). +3. **Implementation guards** — labelled `// guard:` — protect specific failure modes. Refactors may update these. + +--- + +## 7. Documentation + +### When to comment + +- **Always**: exported symbols with non-trivial behavior (full JSDoc per § 1) +- **Rarely**: inline comments only when the *why* is genuinely non-obvious from reading the code +- **Never**: comments that restate what the code does; comments as TODO parking + +### Keeping docs current + +When you change behavior, update the JSDoc Purpose and Consumer in the same commit. A stale Purpose is worse than no Purpose — it actively misleads the next reader. + +### Module headers + +```js +// module-name.js — one-line description +// +// Purpose: why this module exists as a separable unit. +// +// Consumer: who imports this at runtime (or "internal" if only tests). +``` + +--- + +## See Also + +- [AGENTS.md](AGENTS.md) — planning conventions, spec-first TDD, test naming +- [docs/adr/0000-purpose-to-software-compiler.md](docs/adr/0000-purpose-to-software-compiler.md) — foundational product contract +- [docs/SPEC_FIRST_TDD.md](docs/SPEC_FIRST_TDD.md) — test-first constitution +- [biome.json](biome.json) — linter config (`npm run lint`) +- [scripts/tech-debt-scan.mjs](scripts/tech-debt-scan.mjs) — TODO/FIXME threshold tracking diff --git a/copilot-thoughts.md b/copilot-thoughts.md index e92971fa4..114e45ed8 100644 --- a/copilot-thoughts.md +++ b/copilot-thoughts.md @@ -750,7 +750,7 @@ Already directionally right: Still needed: -- Remove `/sf` from docs/web/tests (Phase 2 deprecation) +- ~~Remove `/sf` from docs/web/tests (Phase 2 deprecation)~~ ✓ Complete Completed ✓ (RA.Aid Patterns — Phase 2): diff --git a/src/resources/extensions/sf/tests/memory-embeddings-llm-gateway.test.mjs b/src/resources/extensions/sf/tests/memory-embeddings-llm-gateway.test.mjs index 4f5b7bcb8..93961c4ea 100644 --- a/src/resources/extensions/sf/tests/memory-embeddings-llm-gateway.test.mjs +++ b/src/resources/extensions/sf/tests/memory-embeddings-llm-gateway.test.mjs @@ -43,6 +43,8 @@ test("loadGatewayConfigFromEnv accepts SF-prefixed configuration", () => { urlSource: "SF_LLM_GATEWAY_URL", embeddingModel: "embed-model", rerankModel: "rerank-model", + queryInstruction: + "Instruct: Retrieve relevant software engineering memories, facts, and project decisions for the given query\nQuery: ", }); }); }); @@ -59,6 +61,8 @@ test("loadGatewayConfigFromEnv accepts llm-gateway shell aliases", () => { urlSource: "LLM_GATEWAY_BASE_URL", embeddingModel: "Qwen/Qwen3-Embedding-4B", rerankModel: "Qwen/Qwen3-Reranker-0.6B", + queryInstruction: + "Instruct: Retrieve relevant software engineering memories, facts, and project decisions for the given query\nQuery: ", }); }); }); diff --git a/src/tests/integration/web-recovery-diagnostics-contract.test.ts b/src/tests/integration/web-recovery-diagnostics-contract.test.ts index b2fa2d476..b5ce753e8 100644 --- a/src/tests/integration/web-recovery-diagnostics-contract.test.ts +++ b/src/tests/integration/web-recovery-diagnostics-contract.test.ts @@ -328,7 +328,7 @@ test("/api/recovery returns structured recovery diagnostics and redacts secrets" ); assert.ok( payload.actions.commands.some((entry: { command: string }) => - entry.command.includes("/sf doctor"), + entry.command.includes("/doctor"), ), ); diff --git a/src/tests/integration/web-workflow-controls-contract.test.ts b/src/tests/integration/web-workflow-controls-contract.test.ts index 39ca6a78a..40aa3a891 100644 --- a/src/tests/integration/web-workflow-controls-contract.test.ts +++ b/src/tests/integration/web-workflow-controls-contract.test.ts @@ -23,83 +23,83 @@ function baseInput( } // ─── Group 1: Phase → action mapping ────────────────────────────────── -test("planning + no auto → primary is /sf with label Plan", () => { +test("planning + no auto → primary is /discuss with label Plan", () => { const result = deriveWorkflowAction(baseInput({ phase: "planning" })); assert.ok(result.primary); - assert.equal(result.primary.command, "/sf"); + assert.equal(result.primary.command, "/discuss"); assert.equal(result.primary.label, "Plan"); assert.equal(result.primary.variant, "default"); assert.equal(result.disabled, false); }); -test("executing + no auto → primary is /sf autonomous with label Start Autonomous", () => { +test("executing + no auto → primary is /autonomous with label Start Autonomous", () => { const result = deriveWorkflowAction(baseInput({ phase: "executing" })); assert.ok(result.primary); - assert.equal(result.primary.command, "/sf autonomous"); + assert.equal(result.primary.command, "/autonomous"); assert.equal(result.primary.label, "Start Autonomous"); }); -test("summarizing + no auto → primary is /sf autonomous with label Start Autonomous", () => { +test("summarizing + no auto → primary is /autonomous with label Start Autonomous", () => { const result = deriveWorkflowAction(baseInput({ phase: "summarizing" })); assert.ok(result.primary); - assert.equal(result.primary.command, "/sf autonomous"); + assert.equal(result.primary.command, "/autonomous"); assert.equal(result.primary.label, "Start Autonomous"); }); -test("auto active (not paused) → primary is /sf stop with destructive variant", () => { +test("auto active (not paused) → primary is /stop with destructive variant", () => { const result = deriveWorkflowAction( baseInput({ autoActive: true, autoPaused: false }), ); assert.ok(result.primary); - assert.equal(result.primary.command, "/sf stop"); + assert.equal(result.primary.command, "/stop"); assert.equal(result.primary.label, "Stop Autonomous"); assert.equal(result.primary.variant, "destructive"); }); -test("auto paused → primary is /sf autonomous with label Resume Autonomous", () => { +test("auto paused → primary is /autonomous with label Resume Autonomous", () => { const result = deriveWorkflowAction(baseInput({ autoPaused: true })); assert.ok(result.primary); - assert.equal(result.primary.command, "/sf autonomous"); + assert.equal(result.primary.command, "/autonomous"); assert.equal(result.primary.label, "Resume Autonomous"); assert.equal(result.primary.variant, "default"); }); -test("pre-planning + no milestones → primary is /sf with label Initialize Project", () => { +test("pre-planning + no milestones → primary is /init with label Initialize Project", () => { const result = deriveWorkflowAction( baseInput({ phase: "pre-planning", hasMilestones: false }), ); assert.ok(result.primary); - assert.equal(result.primary.command, "/sf"); + assert.equal(result.primary.command, "/init"); assert.equal(result.primary.label, "Initialize Project"); }); -test("pre-planning + has milestones → primary is /sf with label Continue", () => { +test("pre-planning + has milestones → primary is /discuss with label Continue", () => { const result = deriveWorkflowAction( baseInput({ phase: "pre-planning", hasMilestones: true }), ); assert.ok(result.primary); - assert.equal(result.primary.command, "/sf"); + assert.equal(result.primary.command, "/discuss"); assert.equal(result.primary.label, "Continue"); }); -test("other phases (e.g. researching) without auto → primary is Continue /sf", () => { +test("other phases (e.g. researching) without auto → primary is Continue /discuss", () => { const result = deriveWorkflowAction(baseInput({ phase: "researching" })); assert.ok(result.primary); - assert.equal(result.primary.command, "/sf"); + assert.equal(result.primary.command, "/discuss"); assert.equal(result.primary.label, "Continue"); }); -test("verifying phase without auto → primary is Continue /sf", () => { +test("verifying phase without auto → primary is Continue /discuss", () => { const result = deriveWorkflowAction(baseInput({ phase: "verifying" })); assert.ok(result.primary); - assert.equal(result.primary.command, "/sf"); + assert.equal(result.primary.command, "/discuss"); assert.equal(result.primary.label, "Continue"); }); -test("complete phase without auto → primary is New Milestone /sf with no step secondary", () => { +test("complete phase without auto → primary is New Milestone /new-milestone with no step secondary", () => { const result = deriveWorkflowAction(baseInput({ phase: "complete" })); assert.ok(result.primary); - assert.equal(result.primary.command, "/sf"); + assert.equal(result.primary.command, "/new-milestone"); assert.equal(result.primary.label, "New Milestone"); assert.equal(result.isNewMilestone, true); assert.deepEqual(result.secondaries, []); @@ -109,7 +109,7 @@ test("complete phase without auto → primary is New Milestone /sf with no step test("secondaries include Step when auto is not active", () => { const result = deriveWorkflowAction(baseInput({ phase: "executing" })); assert.ok(result.secondaries.length > 0); - const step = result.secondaries.find((s) => s.command === "/sf next"); + const step = result.secondaries.find((s) => s.command === "/next"); assert.ok(step, "Expected a Step secondary action"); assert.equal(step.label, "Step"); }); diff --git a/src/tests/welcome-screen.test.ts b/src/tests/welcome-screen.test.ts index 922edfb1b..4f29ea76b 100644 --- a/src/tests/welcome-screen.test.ts +++ b/src/tests/welcome-screen.test.ts @@ -56,7 +56,7 @@ test("renders model and provider", () => { test("renders cwd hint", () => { const out = strip(capture({ version: "1.0.0" })); - assert.ok(out.includes("/sf to begin"), "hint line missing"); + assert.ok(out.includes("/next to step"), "hint line missing"); }); test("skips when not a TTY", (_t) => { diff --git a/src/web/recovery-diagnostics-service.ts b/src/web/recovery-diagnostics-service.ts index c17d3fb49..397d7882a 100644 --- a/src/web/recovery-diagnostics-service.ts +++ b/src/web/recovery-diagnostics-service.ts @@ -246,16 +246,16 @@ function buildCommandSuggestions( } }; - if (phase === "planning") add("/sf", "Open SF planning"); + if (phase === "planning") add("/discuss", "Open SF planning"); if (phase === "executing" || phase === "summarizing") - add("/sf autonomous", "Resume SF autonomous mode"); + add("/autonomous", "Resume SF autonomous mode"); if (activeScope) - add(`/sf doctor ${activeScope}`, "Inspect scoped doctor report"); + add(`/doctor ${activeScope}`, "Inspect scoped doctor report"); if (activeScope) - add(`/sf doctor fix ${activeScope}`, "Apply scoped doctor fixes"); + add(`/doctor fix ${activeScope}`, "Apply scoped doctor fixes"); if (validationCount > 0 && activeScope) - add(`/sf doctor audit ${activeScope}`, "Audit validation diagnostics"); - add("/sf status", "Check current-project status"); + add(`/doctor audit ${activeScope}`, "Audit validation diagnostics"); + add("/status", "Check current-project status"); return [...suggestions.values()]; } diff --git a/src/welcome-screen.ts b/src/welcome-screen.ts index 2e1ebf0e5..adea3ac06 100644 --- a/src/welcome-screen.ts +++ b/src/welcome-screen.ts @@ -100,7 +100,7 @@ export function printWelcomeScreen(opts: WelcomeScreenOptions): void { // Tools left, hint right-aligned on the same row const toolsLeft = toolParts.length > 0 ? chalk.dim(" " + toolParts.join(" · ")) : ""; - const hintRight = chalk.dim("/sf to begin · /sf help"); + const hintRight = chalk.dim("/next to step · /help"); const footerFill = RIGHT_INNER - visLen(toolsLeft) - visLen(hintRight); const footerRow = toolsLeft + " ".repeat(Math.max(1, footerFill)) + hintRight; diff --git a/web/components/sf/chat-mode.tsx b/web/components/sf/chat-mode.tsx index 697fc3891..95d3235b3 100644 --- a/web/components/sf/chat-mode.tsx +++ b/web/components/sf/chat-mode.tsx @@ -111,7 +111,7 @@ const SF_ACTIONS: SFActionDef[] = [ // ── Top 3 (standalone buttons) ── { label: "Discuss", - command: "/sf discuss", + command: "/discuss", icon: MessageCircle, description: "Start guided milestone/slice discussion", category: "workflow", @@ -119,14 +119,14 @@ const SF_ACTIONS: SFActionDef[] = [ }, { label: "Next", - command: "/sf next", + command: "/next", icon: Play, description: "Execute next task, then pause", category: "workflow", }, { label: "Autonomous", - command: "/sf autonomous", + command: "/autonomous", icon: Zap, description: "Run all queued product units continuously", category: "workflow", @@ -134,14 +134,14 @@ const SF_ACTIONS: SFActionDef[] = [ // ── Overflow: Workflow ── { label: "Stop", - command: "/sf stop", + command: "/stop", icon: Square, description: "Stop autonomous mode gracefully", category: "workflow", }, { label: "Pause", - command: "/sf pause", + command: "/pause", icon: Pause, description: "Pause autonomous mode (preserves state)", category: "workflow", @@ -149,28 +149,28 @@ const SF_ACTIONS: SFActionDef[] = [ // ── Overflow: Visibility ── { label: "Status", - command: "/sf status", + command: "/status", icon: BarChart3, description: "Show progress dashboard", category: "visibility", }, { label: "Visualize", - command: "/sf visualize", + command: "/visualize", icon: LayoutGrid, description: "Interactive TUI (progress, deps, metrics, timeline)", category: "visibility", }, { label: "Queue", - command: "/sf queue", + command: "/queue", icon: ListOrdered, description: "Show queued/dispatched units and execution order", category: "visibility", }, { label: "History", - command: "/sf history", + command: "/history", icon: History, description: "View execution history with cost/phase/model details", category: "visibility", @@ -178,21 +178,21 @@ const SF_ACTIONS: SFActionDef[] = [ // ── Overflow: Course correction ── { label: "Steer", - command: "/sf steer", + command: "/steer", icon: Compass, description: "Apply user override to active work", category: "correction", }, { label: "Capture", - command: "/sf capture", + command: "/capture", icon: PenLine, description: "Quick-capture a thought to CAPTURES.md", category: "correction", }, { label: "Triage", - command: "/sf triage", + command: "/triage", icon: Inbox, description: "Classify and route pending captures", category: "correction", @@ -200,14 +200,14 @@ const SF_ACTIONS: SFActionDef[] = [ }, { label: "Skip", - command: "/sf skip", + command: "/skip", icon: SkipForward, description: "Prevent a unit from auto-mode dispatch", category: "correction", }, { label: "Undo", - command: "/sf undo", + command: "/undo", icon: Undo2, description: "Revert last completed unit", category: "correction", @@ -215,7 +215,7 @@ const SF_ACTIONS: SFActionDef[] = [ // ── Overflow: Knowledge ── { label: "Knowledge", - command: "/sf knowledge", + command: "/knowledge", icon: BookOpen, description: "Add rule, pattern, or lesson to KNOWLEDGE.md", category: "knowledge", @@ -223,14 +223,14 @@ const SF_ACTIONS: SFActionDef[] = [ // ── Overflow: Configuration ── { label: "Mode", - command: "/sf mode", + command: "/mode", icon: SlidersHorizontal, description: "Set workflow mode (solo/team)", category: "config", }, { label: "Prefs", - command: "/sf prefs", + command: "/prefs", icon: Settings, description: "Manage preferences (global/project)", category: "config", @@ -238,28 +238,28 @@ const SF_ACTIONS: SFActionDef[] = [ // ── Overflow: Maintenance ── { label: "Doctor", - command: "/sf doctor", + command: "/doctor", icon: Stethoscope, description: "Diagnose and repair .sf/ state", category: "maintenance", }, { label: "Export", - command: "/sf export", + command: "/export", icon: FileOutput, description: "Export milestone/slice results (JSON or Markdown)", category: "maintenance", }, { label: "Cleanup", - command: "/sf cleanup", + command: "/cleanup", icon: Trash2, description: "Remove merged branches or snapshots", category: "maintenance", }, { label: "Remote", - command: "/sf remote", + command: "/remote", icon: Globe, description: "Control remote auto-mode (Slack/Discord)", category: "maintenance", diff --git a/web/components/sf/command-surface.tsx b/web/components/sf/command-surface.tsx index 2b9938efd..e6c7d77cd 100644 --- a/web/components/sf/command-surface.tsx +++ b/web/components/sf/command-surface.tsx @@ -3056,7 +3056,7 @@ export function CommandSurface() { data-testid={`sf-surface-${commandSurface.section}`} >

- /sf {commandSurface.section.slice(4)} + /{commandSurface.section.slice(4)}

Unknown SF surface.

diff --git a/web/components/sf/project-welcome.tsx b/web/components/sf/project-welcome.tsx index e023bc66d..573196a46 100644 --- a/web/components/sf/project-welcome.tsx +++ b/web/components/sf/project-welcome.tsx @@ -39,7 +39,7 @@ function getVariant(detection: ProjectDetection): WelcomeVariant { headline: "Existing project detected", body: "SF will map your codebase and ask a few questions about what you want to build. From there it generates structured milestones and deliverable slices.", primaryLabel: "Map & Initialize", - primaryCommand: "/sf", + primaryCommand: "/init", secondary: { label: "Browse files first", action: "files-view", @@ -59,11 +59,11 @@ function getVariant(detection: ProjectDetection): WelcomeVariant { detail: "Your original files will be preserved — migration creates the new structure alongside them.", primaryLabel: "Migrate to v2", - primaryCommand: "/sf migrate", + primaryCommand: "/migrate", secondary: { label: "Start fresh instead", action: "command", - command: "/sf", + command: "/init", }, }; @@ -75,7 +75,7 @@ function getVariant(detection: ProjectDetection): WelcomeVariant { headline: "Start a new project", body: "This folder is empty. SF will ask what you want to build, then generate a structured plan — milestones broken into deliverable slices with risk-ordered execution.", primaryLabel: "Start Project Setup", - primaryCommand: "/sf", + primaryCommand: "/init", }; // active-sf and empty-sf shouldn't reach here, but handle gracefully @@ -85,7 +85,7 @@ function getVariant(detection: ProjectDetection): WelcomeVariant { headline: "Set up your project", body: "Run the SF wizard to get started.", primaryLabel: "Get Started", - primaryCommand: "/sf", + primaryCommand: "/init", }; } } diff --git a/web/components/sf/remaining-command-panels.tsx b/web/components/sf/remaining-command-panels.tsx index 25da22191..2289c700a 100644 --- a/web/components/sf/remaining-command-panels.tsx +++ b/web/components/sf/remaining-command-panels.tsx @@ -188,7 +188,7 @@ export function QuickPanel() { Usage
- /sf quick <description> + /quick <description>
@@ -209,7 +209,7 @@ export function QuickPanel() { > $ - /sf quick {example} + /quick {example} ))} @@ -1603,7 +1603,7 @@ export function StatusPanel() { )} {milestones.length === 0 && ( - + )} ); diff --git a/web/lib/browser-slash-command-dispatch.ts b/web/lib/browser-slash-command-dispatch.ts index 7135a56bc..955417a04 100644 --- a/web/lib/browser-slash-command-dispatch.ts +++ b/web/lib/browser-slash-command-dispatch.ts @@ -141,12 +141,15 @@ const SF_SURFACE_COMMANDS = new Map([ const SF_PASSTHROUGH_COMMANDS = new Set([ "autonomous", "next", + "stop", "pause", "skip", "discuss", "run-hook", "migrate", "remote", + "new-milestone", + "init", ]); export const SF_HELP_TEXT = `Available SF commands: diff --git a/web/lib/workflow-actions.ts b/web/lib/workflow-actions.ts index 3b74a1006..955863916 100644 --- a/web/lib/workflow-actions.ts +++ b/web/lib/workflow-actions.ts @@ -83,64 +83,64 @@ export function deriveWorkflowAction( if (autoActive && !autoPaused) { primary = { label: "Stop Autonomous", - command: "/sf stop", + command: "/stop", variant: "destructive", }; } else if (autoPaused) { primary = { label: "Resume Autonomous", - command: "/sf autonomous", + command: "/autonomous", variant: "default", }; } else { // Auto is not active if (phase === "complete") { // All milestones done — surface a distinct "New Milestone" action - primary = { label: "New Milestone", command: "/sf", variant: "default" }; + primary = { label: "New Milestone", command: "/new-milestone", variant: "default" }; isNewMilestone = true; } else if (phase === "planning") { - primary = { label: "Plan", command: "/sf", variant: "default" }; + primary = { label: "Plan", command: "/discuss", variant: "default" }; } else if (phase === "executing" || phase === "summarizing") { primary = { label: "Start Autonomous", - command: "/sf autonomous", + command: "/autonomous", variant: "default", }; } else if (phase === "pre-planning" && !hasMilestones) { primary = { label: "Initialize Project", - command: "/sf", + command: "/init", variant: "default", }; } else if (phase === "blocked") { - primary = { label: "Blocked", command: "/sf", variant: "default" }; + primary = { label: "Blocked", command: "/discuss", variant: "default" }; disabled = true; disabledReason = "Project is blocked — check blockers"; } else if (phase === "paused") { primary = { label: "Resume", - command: "/sf autonomous", + command: "/autonomous", variant: "default", }; } else if (phase === "validating-milestone") { - primary = { label: "Validate", command: "/sf", variant: "default" }; + primary = { label: "Validate", command: "/discuss", variant: "default" }; } else if (phase === "completing-milestone") { primary = { label: "Complete Milestone", - command: "/sf", + command: "/discuss", variant: "default", }; } else if (phase === "needs-discussion") { - primary = { label: "Discuss", command: "/sf", variant: "default" }; + primary = { label: "Discuss", command: "/discuss", variant: "default" }; } else if (phase === "replanning-slice") { - primary = { label: "Replan", command: "/sf", variant: "default" }; + primary = { label: "Replan", command: "/discuss", variant: "default" }; } else { - primary = { label: "Continue", command: "/sf", variant: "default" }; + primary = { label: "Continue", command: "/discuss", variant: "default" }; } // Add "Step" secondary when auto is not active (not for new milestone — no step concept there) - if (primary.command !== "/sf next" && !isNewMilestone) { - secondaries.push({ label: "Step", command: "/sf next" }); + if (primary.command !== "/next" && !isNewMilestone) { + secondaries.push({ label: "Step", command: "/next" }); } }