From 6fc054e7c38e724add8b0888cbbb7c5ccfe9f40f Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Fri, 8 May 2026 01:07:24 +0200 Subject: [PATCH] sf snapshot: uncommitted changes after 49m inactivity --- AGENTS.md | 4 +- README.md | 28 +- docs/README.md | 6 +- docs/dev/ADR-003-pipeline-simplification.md | 2 +- docs/dev/FILE-SYSTEM-MAP.md | 3 - docs/records/2026-05-02-bug-hunt-findings.md | 10 +- .../2026-05-07-cli-agent-code-survey.md | 6 + docs/specs/sf-operating-model.md | 17 +- docs/specs/sf-schedule.md | 14 +- .../{auto-mode.md => autonomous-mode.md} | 13 +- docs/user-docs/commands.md | 8 +- docs/user-docs/getting-started.md | 6 +- docs/user-docs/git-strategy.md | 2 +- docs/user-docs/troubleshooting.md | 14 +- packages/daemon/src/orchestrator.ts | 2 +- packages/daemon/src/session-manager.test.ts | 16 +- packages/daemon/src/session-manager.ts | 11 +- .../pi-coding-agent/src/core/agent-session.ts | 6 +- .../pi-coding-agent/src/core/lsp/client.ts | 2 +- .../src/core/model-registry-discovery.test.ts | 4 + .../pi-coding-agent/src/core/retry-handler.ts | 2 +- scripts/check-sf-extension-inventory.mjs | 2 +- scripts/recover-sf-1364.sh | 3 - scripts/recover-sf-1668.sh | 446 ------------------ src/cli.ts | 2 +- src/headless-events.ts | 28 +- src/headless-query.ts | 4 +- src/headless-ui.ts | 4 +- src/headless.ts | 98 ++-- src/help-text.ts | 4 +- src/loader.ts | 10 +- .../extensions/bg-shell/process-manager.js | 2 +- src/resources/extensions/ollama/index.js | 2 +- .../extensions/remote-questions/notify.js | 2 +- src/resources/extensions/sf/activity-log.js | 6 +- .../extensions/sf/auto-artifact-paths.js | 2 +- src/resources/extensions/sf/auto-budget.js | 2 +- src/resources/extensions/sf/auto-dashboard.js | 6 +- .../extensions/sf/auto-direct-dispatch.js | 2 +- src/resources/extensions/sf/auto-dispatch.js | 18 +- .../extensions/sf/auto-model-selection.js | 14 +- src/resources/extensions/sf/auto-post-unit.js | 22 +- src/resources/extensions/sf/auto-prompts.js | 6 +- src/resources/extensions/sf/auto-recovery.js | 16 +- .../extensions/sf/auto-runaway-guard.js | 2 +- .../extensions/sf/auto-runtime-state.js | 2 +- src/resources/extensions/sf/auto-start.js | 32 +- .../extensions/sf/auto-supervisor.js | 2 +- .../extensions/sf/auto-timeout-recovery.js | 6 +- src/resources/extensions/sf/auto-timers.js | 4 +- .../extensions/sf/auto-tool-tracking.js | 4 +- .../extensions/sf/auto-verification.js | 2 +- src/resources/extensions/sf/auto-worktree.js | 16 +- src/resources/extensions/sf/auto.js | 54 ++- src/resources/extensions/sf/auto/loop.js | 20 +- src/resources/extensions/sf/auto/phases.js | 24 +- src/resources/extensions/sf/auto/session.js | 8 +- src/resources/extensions/sf/auto/types.js | 2 +- .../sf/autonomous-command-args.d.ts | 12 + .../extensions/sf/autonomous-command-args.js | 53 +++ src/resources/extensions/sf/blocked-models.js | 2 +- .../sf/bootstrap/agent-end-recovery.js | 6 +- .../extensions/sf/bootstrap/ask-gate.js | 2 +- .../extensions/sf/bootstrap/db-tools.js | 8 +- .../extensions/sf/bootstrap/journal-tools.js | 4 +- .../sf/bootstrap/provider-error-resume.js | 4 +- .../extensions/sf/bootstrap/register-hooks.js | 10 +- .../extensions/sf/bootstrap/system-context.js | 2 +- .../sf/bootstrap/tool-call-loop-guard.js | 2 +- src/resources/extensions/sf/captures.js | 6 +- .../extensions/sf/commands-bootstrap.js | 2 +- .../extensions/sf/commands-escalate.js | 4 +- .../sf/commands-extract-learnings.js | 4 +- .../extensions/sf/commands-handlers.js | 4 +- src/resources/extensions/sf/commands-logs.js | 6 +- .../extensions/sf/commands-maintenance.js | 2 +- .../extensions/sf/commands-schedule.js | 21 +- src/resources/extensions/sf/commands-ship.js | 4 +- .../sf/commands-workflow-templates.js | 8 +- .../extensions/sf/commands/catalog.js | 2 +- .../extensions/sf/commands/dispatcher.js | 4 +- .../handlers/{auto.js => autonomous.js} | 25 +- .../extensions/sf/commands/handlers/core.js | 18 +- src/resources/extensions/sf/context-masker.js | 2 +- src/resources/extensions/sf/crash-recovery.js | 6 +- .../extensions/sf/dashboard-overlay.js | 2 +- src/resources/extensions/sf/db-writer.js | 2 +- src/resources/extensions/sf/debug-logger.js | 2 +- .../extensions/sf/dev-workflow-engine.js | 4 +- .../sf/docs/preferences-reference.md | 2 +- .../extensions/sf/doctor-proactive.js | 18 +- .../extensions/sf/doctor-runtime-checks.js | 4 +- src/resources/extensions/sf/doctor.js | 2 +- src/resources/extensions/sf/escalation.js | 6 +- .../extensions/sf/execution-policy.js | 16 +- src/resources/extensions/sf/exit-command.js | 4 +- src/resources/extensions/sf/forensics.js | 10 +- src/resources/extensions/sf/git-self-heal.js | 2 +- src/resources/extensions/sf/git-service.js | 10 +- .../extensions/sf/guided-flow-queue.js | 10 +- src/resources/extensions/sf/guided-flow.js | 30 +- .../extensions/sf/health-widget-core.js | 2 +- src/resources/extensions/sf/history.js | 4 +- .../extensions/sf/interrupted-session.js | 8 +- src/resources/extensions/sf/journal.js | 4 +- .../extensions/sf/memory-extractor.js | 4 +- src/resources/extensions/sf/memory-sleeper.js | 4 +- src/resources/extensions/sf/metrics.js | 6 +- .../sf/milestone-scope-classifier.js | 2 +- .../sf/milestone-summary-classifier.js | 2 +- .../extensions/sf/model-route-failure.js | 4 +- .../extensions/sf/native-git-bridge.js | 4 +- .../extensions/sf/notification-store.js | 4 +- src/resources/extensions/sf/notifications.js | 2 +- .../extensions/sf/operating-model.js | 92 ++++ .../extensions/sf/parallel-orchestrator.js | 6 +- src/resources/extensions/sf/phase-anchor.js | 2 +- .../extensions/sf/preferences-models.js | 14 +- .../extensions/sf/project-research-policy.js | 4 +- src/resources/extensions/sf/prompt-loader.js | 2 +- .../extensions/sf/prompts/forensics.md | 2 +- .../extensions/sf/provider-error-pause.js | 8 +- src/resources/extensions/sf/rethink.js | 4 +- src/resources/extensions/sf/roadmap-slices.js | 2 +- .../extensions/sf/safety/content-validator.js | 2 +- .../extensions/sf/safety/destructive-guard.js | 2 +- .../sf/safety/evidence-collector.js | 2 +- .../sf/safety/evidence-cross-ref.js | 2 +- .../sf/safety/file-change-validator.js | 2 +- .../extensions/sf/safety/git-checkpoint.js | 2 +- .../extensions/sf/safety/safety-harness.js | 2 +- .../extensions/sf/schedule-launch-banner.js | 20 +- ...tch.js => schedule-autonomous-dispatch.js} | 12 +- .../sf/schedule/schedule-store.d.ts | 4 +- .../extensions/sf/schedule/schedule-store.js | 4 +- .../extensions/sf/schedule/schedule-types.js | 8 +- .../extensions/sf/session-forensics.js | 2 +- src/resources/extensions/sf/session-lock.js | 12 +- .../extensions/sf/session-model-override.js | 4 +- src/resources/extensions/sf/sf-db.js | 16 +- src/resources/extensions/sf/skill-catalog.js | 2 +- .../extensions/sf/skill-discovery.js | 14 +- src/resources/extensions/sf/skill-health.js | 2 +- src/resources/extensions/sf/skill-manifest.js | 4 +- .../extensions/sf/skill-telemetry.js | 2 +- .../sf/skills/brainstorming/SKILL.md | 4 +- .../extensions/sf/skills/sf-headless/SKILL.md | 10 +- .../references/answer-injection.md | 2 +- .../skills/sf-headless/references/commands.md | 12 +- .../sf/skills/spec-first-tdd/SKILL.md | 2 +- .../extensions/sf/spec-projections.js | 6 +- src/resources/extensions/sf/state.js | 6 +- .../extensions/sf/templates/PREFERENCES.md | 8 +- .../tests/auto-command-full-removed.test.mjs | 29 -- .../sf/tests/autonomous-command-args.test.mjs | 32 ++ .../sf/tests/commands-schedule.test.mjs | 25 +- .../doctor-flow-audit-auto-cleanup.test.mjs | 6 +- .../sf/tests/execution-policy.test.mjs | 34 +- .../sf/tests/extension-models-gap5.test.mjs | 2 +- ...ion-detection-headless-medium-low.test.mjs | 2 +- .../sf/tests/operating-model.test.mjs | 46 ++ .../sf/tests/schedule-dispatch.test.mjs | 30 +- .../sf/tests/schedule-launch-banner.test.mjs | 6 +- .../sf/tests/uok-parity-report.test.mjs | 28 ++ .../extensions/sf/triage-resolution.js | 6 +- src/resources/extensions/sf/triage-ui.js | 4 +- .../extensions/sf/unit-context-manifest.js | 4 +- src/resources/extensions/sf/uok/contracts.js | 2 + .../extensions/sf/uok/dispatch-envelope.js | 2 + src/resources/extensions/sf/uok/flags.js | 2 + src/resources/extensions/sf/uok/kernel.js | 52 +- .../extensions/sf/uok/loop-adapter.js | 42 +- .../extensions/sf/uok/unit-runtime.js | 2 +- .../extensions/sf/visualizer-data.js | 2 +- .../extensions/sf/visualizer-views.js | 2 +- .../extensions/sf/worktree-command.js | 4 +- .../extensions/sf/worktree-manager.js | 2 +- .../extensions/sf/worktree-resolver.js | 2 +- .../extensions/sf/worktree-telemetry.js | 2 +- src/resources/extensions/sf/worktree.js | 2 +- .../references/sf-skill-ecosystem.md | 6 +- src/resources/skills/forensics/SKILL.md | 12 +- src/resources/traces.ts | 2 +- src/resources/workflows/build-from-spec.md | 10 +- src/tests/auto-mode-piped.test.ts | 2 +- src/tests/auto-piped-io.test.ts | 2 +- src/tests/auto-resume-resource-loader.test.ts | 2 +- src/tests/headless-cli-surface.test.ts | 50 +- src/tests/headless-detection.test.ts | 46 +- src/tests/headless-events.test.ts | 16 +- src/tests/headless-progress.test.ts | 4 +- src/tests/headless-v2-migration.test.ts | 14 +- src/tests/integration/e2e-headless.test.ts | 8 +- ...auto-dashboard-lock-reconciliation.test.ts | 2 +- src/tests/native-search.test.ts | 8 +- src/tests/read-tool-offset-clamp.test.ts | 2 +- src/tests/schedule-cli-noninteractive.test.ts | 2 +- src/tests/trace-export.test.ts | 4 +- src/traces.ts | 12 +- src/welcome-screen.ts | 4 +- 200 files changed, 1171 insertions(+), 1167 deletions(-) rename docs/user-docs/{auto-mode.md => autonomous-mode.md} (95%) delete mode 100755 scripts/recover-sf-1668.sh create mode 100644 src/resources/extensions/sf/autonomous-command-args.d.ts create mode 100644 src/resources/extensions/sf/autonomous-command-args.js rename src/resources/extensions/sf/commands/handlers/{auto.js => autonomous.js} (92%) create mode 100644 src/resources/extensions/sf/operating-model.js rename src/resources/extensions/sf/schedule/{schedule-auto-dispatch.js => schedule-autonomous-dispatch.js} (91%) delete mode 100644 src/resources/extensions/sf/tests/auto-command-full-removed.test.mjs create mode 100644 src/resources/extensions/sf/tests/autonomous-command-args.test.mjs create mode 100644 src/resources/extensions/sf/tests/operating-model.test.mjs diff --git a/AGENTS.md b/AGENTS.md index 2fb549c92..660aae0ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -121,7 +121,7 @@ Every exported function, type, class, and module-level constant opens with a JSD * unavailable (shared NFS, broken filesystem semantics) — the conditional UPDATE in * SQLite is the safety net. * - * Consumer: auto-dispatch.ts when picking the next eligible unit per poll tick. + * Consumer: autonomous dispatch.ts when picking the next eligible unit per poll tick. */ export function claimUnit(unitId: string, leaseMs: number): boolean { ... } ``` @@ -237,7 +237,7 @@ See [`docs/plans/README.md`](docs/plans/README.md), [`docs/adr/README.md`](docs/ ## SF Schedule -The SF schedule system (`/sf schedule`) stores project time-bound reminders in the repo SQLite DB (`.sf/sf.db`, `schedule_entries`) and global reminders in `~/.sf/sf.db`. Legacy `.sf/schedule.jsonl` rows are import-only compatibility input when a project has no schedule rows yet. Items surface on their due date via pull queries at launch and auto-mode boundaries — there is no background daemon. +The SF schedule system (`/sf schedule`) stores project time-bound reminders in the repo SQLite DB (`.sf/sf.db`, `schedule_entries`) and global reminders in `~/.sf/sf.db`. Legacy `.sf/schedule.jsonl` rows are import-only compatibility input when a project has no schedule rows yet. Items surface on their due date via pull queries at launch and autonomous mode boundaries — there is no background daemon. **When to use `sf schedule` vs backlog:** - **`sf schedule`** — time-bound items that must surface at a future date: a 2-week adoption review after shipping a feature, a 1-month audit of an architectural decision, a 30-minute reminder to run a command. Use when the *timing* matters, not just the *priority*. diff --git a/README.md b/README.md index 29f1b5957..ff2a481ad 100644 --- a/README.md +++ b/README.md @@ -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/auto-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 @@ -153,7 +153,7 @@ Full documentation is in the [`docs/`](./docs/) directory: The original SF was a collection of markdown prompts installed into `~/.claude/commands/`. It relied entirely on the LLM reading those prompts and doing the right thing. That worked surprisingly well — but it had hard limits: - **No context control.** The LLM accumulated garbage over a long session. Quality degraded. -- **No real automation.** "Auto mode" was the LLM calling itself in a loop, burning context on orchestration overhead. +- **No real automation.** The old continuous loop was the LLM calling itself, burning context on orchestration overhead. - **No crash recovery.** If the session died mid-task, you started over. - **No observability.** No cost tracking, no progress dashboard, no stuck detection. @@ -163,7 +163,7 @@ SF v2 solves all of these because it's not a prompt framework anymore — it's a | -------------------- | ---------------------------- | ------------------------------------------------------- | | Runtime | Claude Code slash commands | Standalone CLI via Pi SDK | | Context management | Hope the LLM doesn't fill up | Fresh session per task, programmatic | -| Auto mode | LLM self-loop | State machine reading `.sf/` files | +| Autonomous mode | LLM self-loop | State machine reading `.sf/` files | | Crash recovery | None | Lock files + session forensics | | Git strategy | LLM writes git commands | Worktree isolation, sequential commits, squash merge | | Cost tracking | None | Per-unit token/cost ledger with dashboard | @@ -235,7 +235,7 @@ This is what makes SF different. Run it, walk away, come back to built software. /sf autonomous ``` -Autonomous mode is governed by the Unified Operation Kernel (UOK), not by the LLM or a loose file loop. UOK reads canonical project state, records each run in the DB-backed ledger, projects runtime files for query/UI/backcompat, determines the next unit of work, creates a fresh agent session, injects a focused prompt with all relevant context pre-inlined, and lets the LLM execute. When the LLM finishes, autonomous mode reconciles the UOK ledger and projections before dispatching the next unit. Legacy `/sf auto` remains accepted only for compatibility; new prompts and docs use `/sf autonomous`. +Autonomous mode is governed by the Unified Operation Kernel (UOK), not by the LLM or a loose file loop. UOK reads canonical project state, records each run in the DB-backed ledger, projects runtime files for query/UI, determines the next unit of work, creates a fresh agent session, injects a focused prompt with all relevant context pre-inlined, and lets the LLM execute. When the LLM finishes, autonomous mode reconciles the UOK ledger and projections before dispatching the next unit. Use `/sf autonomous`; there is no separate `/sf auto` mode. **What happens under the hood:** @@ -263,18 +263,18 @@ Autonomous mode is governed by the Unified Operation Kernel (UOK), not by the LL 12. **Escape hatch** — Press Escape to pause. The conversation is preserved. Interact with the agent, inspect what happened, or just `/sf autonomous` to resume from disk state. -### `/sf` and `/sf next` — Step Mode +### `/sf` and `/sf next` — Assisted Mode -By default, `/sf` runs in **step mode**: the same UOK-governed dispatch loop as autonomous mode, but it pauses between units with a wizard showing what completed and what's next. You advance one step at a time, review the output, and continue when ready. +By default, `/sf` runs in **assisted mode**: the same UOK-governed dispatch loop as autonomous mode, but it pauses between units with a wizard showing what completed and what's next. You advance one step at a time, review the output, and continue when ready. - **No `.sf/` directory** → Start a new project. Discussion flow captures your vision, constraints, and preferences. - **Milestone exists, no roadmap** → Discuss or research the milestone. -- **Roadmap exists, slices pending** → Plan the next slice, execute one task, or switch to auto. +- **Roadmap exists, slices pending** → Plan the next slice, execute one task, or switch to autonomous mode. - **Mid-task** → Resume from where you left off. -`/sf next` is an explicit alias for step mode. You can switch from step → auto mid-session via the wizard. +`/sf next` is an explicit alias for assisted mode. You can switch from assisted mode to autonomous mode mid-session via the wizard. -Step mode is the on-ramp. Auto mode is the highway. +Assisted mode pauses after each unit. Autonomous mode continues until policy, evidence, budget, blockers, or completion stops it. --- @@ -313,7 +313,7 @@ sf SF opens an interactive agent session. From there, you have two ways to work: -**`/sf` — step mode.** Type `/sf` and SF executes one unit of work at a time, pausing between each with a wizard showing what completed and what's next. Same UOK lifecycle and recovery model as autonomous mode, but you stay in the loop. No project yet? It starts the discussion flow. Roadmap exists? It plans or executes the next step. +**`/sf` — assisted mode.** Type `/sf` and SF executes one unit of work at a time, pausing between each with a wizard showing what completed and what's next. Same UOK lifecycle and recovery model as autonomous mode, but you stay in the loop. No project yet? It starts the discussion flow. Roadmap exists? It plans or executes the next step. **`/sf autonomous` — autonomous mode.** Type `/sf autonomous` and walk away. SF researches, plans, executes, verifies, commits, and advances through every slice until the milestone is complete. Fresh context window per task. No babysitting. @@ -351,7 +351,7 @@ surface, not run control, not a permission profile, and not an output format. sf headless --timeout 600000 autonomous # Create and execute a milestone end-to-end -sf headless new-milestone --context spec.md --auto +sf headless new-milestone --context spec.md --autonomous # One unit at a time (cron-friendly) sf headless next @@ -394,8 +394,8 @@ On first run, SF launches a branded setup wizard that walks you through LLM prov | Command | What it does | | ----------------------- | --------------------------------------------------------------- | -| `/sf` | Step mode — executes one unit at a time, pauses between each | -| `/sf next` | Explicit step mode (same as bare `/sf`) | +| `/sf` | Assisted mode — executes one unit at a time, pauses between each | +| `/sf next` | Explicit assisted mode (same as bare `/sf`) | | `/sf autonomous` | Autonomous mode — researches, plans, executes, commits, repeats | | `/sf quick` | Execute a quick task with SF guarantees, skip planning overhead | | `/sf stop` | Stop autonomous mode gracefully | @@ -496,7 +496,7 @@ The verification ladder: static checks → command execution → behavioral test `Ctrl+Alt+G` or `/sf status` opens a real-time overlay showing: - Current milestone, slice, and task progress -- Auto mode elapsed time and phase +- Autonomous mode elapsed time and phase - Per-unit cost and token breakdown by phase, slice, and model - Cost projections based on completed work - Completed and in-progress units diff --git a/docs/README.md b/docs/README.md index 7ba44af39..f1a05d877 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,7 @@ Welcome to the SF documentation. SF is a purpose-to-software compiler: it turns bounded intent into PDD contracts, researches missing context, writes failing tests or executable evidence first, implements the smallest satisfying change, and records verification. See [ADR-0000](./adr/0000-purpose-to-software-compiler.md) and [Spec-First TDD](./SPEC_FIRST_TDD.md) before changing product behavior. -This index covers everything from getting started to advanced configuration, autonomous-mode internals, and extending SF with the Pi SDK. +This index covers everything from getting started to advanced configuration, autonomous mode internals, and extending SF with the Pi SDK. ## User Documentation @@ -11,7 +11,7 @@ Guides for installing, configuring, and using SF day-to-day. Located in [`user-d | Guide | Description | |-------|-------------| | [Getting Started](./user-docs/getting-started.md) | Installation, first run, and basic usage | -| [Autonomous Mode](./user-docs/auto-mode.md) | How autonomous execution works — the state machine, crash recovery, and steering | +| [Autonomous Mode](./user-docs/autonomous-mode.md) | How autonomous execution works — the state machine, crash recovery, and steering | | [Commands Reference](./user-docs/commands.md) | All commands, keyboard shortcuts, and CLI flags | | [Remote Questions](./user-docs/remote-questions.md) | Discord and Slack delivery for run-control-gated questions | | [Configuration](./user-docs/configuration.md) | Preferences, model selection, git settings, and token profiles | @@ -19,7 +19,7 @@ Guides for installing, configuring, and using SF day-to-day. Located in [`user-d | [Custom Models](./user-docs/custom-models.md) | Advanced model configuration — models.json schema, compat flags, overrides | | [Token Optimization](./user-docs/token-optimization.md) | Token profiles, context compression, complexity routing, and adaptive learning (v2.17) | | [Dynamic Model Routing](./user-docs/dynamic-model-routing.md) | Complexity-based model selection, cost tables, escalation, and budget pressure (v2.19) | -| [Captures & Triage](./user-docs/captures-triage.md) | Fire-and-forget thought capture during auto-mode with automated triage (v2.19) | +| [Captures & Triage](./user-docs/captures-triage.md) | Fire-and-forget thought capture during autonomous mode with automated triage (v2.19) | | [Workflow Visualizer](./user-docs/visualizer.md) | Interactive TUI overlay for progress, dependencies, metrics, and timeline (v2.19) | | [Cost Management](./user-docs/cost-management.md) | Budget ceilings, cost tracking, projections, and enforcement modes | | [Git Strategy](./user-docs/git-strategy.md) | Worktree isolation, branching model, and merge behavior | diff --git a/docs/dev/ADR-003-pipeline-simplification.md b/docs/dev/ADR-003-pipeline-simplification.md index a05981ee6..4f5621f09 100644 --- a/docs/dev/ADR-003-pipeline-simplification.md +++ b/docs/dev/ADR-003-pipeline-simplification.md @@ -711,7 +711,7 @@ The mechanical summary quality might be insufficient for complex slices. 24. Simplify `auto-stuck-detection.ts` — fewer unit type patterns 25. Simplify `auto-idempotency.ts` — fewer completed-key types 26. Review `auto-recovery.ts` — simplify recovery paths for unit types that are now fallback-only -27. Update auto-mode documentation (`docs/auto-mode.md`) +27. Update autonomous mode documentation (`docs/user-docs/autonomous-mode.md`) ## Audit Trail diff --git a/docs/dev/FILE-SYSTEM-MAP.md b/docs/dev/FILE-SYSTEM-MAP.md index 7fab04abf..2d740c4a1 100644 --- a/docs/dev/FILE-SYSTEM-MAP.md +++ b/docs/dev/FILE-SYSTEM-MAP.md @@ -959,9 +959,6 @@ | scripts/preview-dashboard.ts | Web Mode | Dashboard preview server | | scripts/ci_monitor.cjs | Build System | CI monitoring dashboard | | scripts/recover-sf-1364.sh | Build System, Migration | Recovery script for issue #1364 | -| scripts/recover-sf-1364.ps1 | Build System, Migration | Recovery script for issue #1364 (PowerShell) | -| scripts/recover-sf-1668.sh | Build System, Migration | Recovery script for issue #1668 | -| scripts/recover-sf-1668.ps1 | Build System, Migration | Recovery script for issue #1668 (PowerShell) | --- diff --git a/docs/records/2026-05-02-bug-hunt-findings.md b/docs/records/2026-05-02-bug-hunt-findings.md index 5da9b0681..b29007a39 100644 --- a/docs/records/2026-05-02-bug-hunt-findings.md +++ b/docs/records/2026-05-02-bug-hunt-findings.md @@ -303,12 +303,12 @@ cluster, no edits, no test runs. ### LOW -- `src/headless.ts:348-356` [autonomous → auto alias] **Status: CONFIRMED** - The `autonomous` command aliases to `auto` in CLI parsing, but `--auto` must be passed explicitly for auto-mode chaining to activate. Running `sf headless autonomous` without `--auto` does not chain, which contradicts the command name's intent. - Suggested fix: When the `autonomous` subcommand is parsed, implicitly set `options.auto = true`. +- `src/headless.ts:348-356` [autonomous command chaining] **Status: CONFIRMED** + The `autonomous` command took the wrong parser path, so autonomous chaining did not activate from the command name alone. + Suggested fix: When the `autonomous` subcommand is parsed, implicitly set the autonomous chaining option. -- `src/headless.ts:1189` [auto-mode chaining visibility] **Status: CONFIRMED** - With verbose output enabled, log lines containing "milestone X ready" could spuriously match `isMilestoneReadyText()` and trigger premature auto-mode chaining. +- `src/headless.ts:1189` [autonomous chaining visibility] **Status: CONFIRMED** + With verbose output enabled, log lines containing "milestone X ready" could spuriously match `isMilestoneReadyText()` and trigger premature autonomous chaining. Suggested fix: Restrict the detection to `message_delta` events of type `text` and exclude tool-output or log-prefixed content. --- diff --git a/docs/records/2026-05-07-cli-agent-code-survey.md b/docs/records/2026-05-07-cli-agent-code-survey.md index ec35c5a63..bfd63e3df 100644 --- a/docs/records/2026-05-07-cli-agent-code-survey.md +++ b/docs/records/2026-05-07-cli-agent-code-survey.md @@ -498,6 +498,12 @@ The terminal-agent pass adds concrete machine/API patterns: - Amazon Q has the richest declarative agent schema and useful lifecycle/text/ tool/state event taxonomy. Forge should adapt manifests and event taxonomy, not recursive delegate subprocesses or raw passthrough protocol events. +- GitHub Copilot CLI's autopilot documentation is a useful naming cross-check: + autopilot is the continuation behavior, `--allow-all`/`--yolo` are permission + expansion, and `--no-ask-user` is question suppression. Forge should keep the + same separation but use SF's own terms: run control is `manual | assisted | + autonomous`, permission profile is `restricted | normal | trusted | + unrestricted`, and headless/machine output is a surface/format concern. ## Local Spec-System Cross-Check diff --git a/docs/specs/sf-operating-model.md b/docs/specs/sf-operating-model.md index 2c27bc003..b83bb69f9 100644 --- a/docs/specs/sf-operating-model.md +++ b/docs/specs/sf-operating-model.md @@ -70,7 +70,15 @@ Run control describes how far SF continues through the flow before stopping for - **assisted** — SF proposes and executes bounded steps, with human approval for important or uncertain actions. - **autonomous** — SF continues through the flow until policy, evidence, budget, or completion stops it. -`auto` is acceptable only as shorthand in commands, flags, and UI labels. The concept name is **autonomous**. +`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. + +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 @@ -83,6 +91,8 @@ A permission profile describes what SF is allowed to touch when a run-control mo Run control and permission profile are independent. For example, `autonomous + restricted` can keep going with narrow permissions, while `manual + trusted` still asks before each consequential step but can perform broader approved actions. +UOK kernel records and execution-policy decisions carry `permissionProfile` as the trust posture. Permission expansion never implies autonomous continuation. + ## Naming Rules - Say **flow** for the shared planning/execution engine. @@ -104,6 +114,11 @@ 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. diff --git a/docs/specs/sf-schedule.md b/docs/specs/sf-schedule.md index 31e912443..20924410d 100644 --- a/docs/specs/sf-schedule.md +++ b/docs/specs/sf-schedule.md @@ -24,7 +24,7 @@ Use `sf schedule` when something needs to happen at a specific future time but c SF has no long-running daemon. Entries are not "fired" by a timer. Instead, the schedule store is queried at specific integration points: 1. **On launch** — `loader.ts` calls `findDue()` and prints a banner if items are due -2. **Auto-mode boundaries** — `sf headless query` (machine snapshot) and the TUI status overlay include due/upcoming entries in their output +2. **Autonomous mode boundaries** — `sf headless query` (machine snapshot) and the TUI status overlay include due/upcoming entries in their output 3. **CLI query** — `sf schedule list --due` shows items whose `due_at <= now` This means: if an item is scheduled for 3 AM and you open SF at 9 AM, you will see the item as overdue. There is no fire-at-exact-time guarantee. This is an explicit trade-off — see the [pull-based ADR](../adr/0002-sf-schedule-pull-based.md) for the full decision record. @@ -70,15 +70,15 @@ Legacy `schedule.jsonl` files are import-only compatibility inputs. Rows without "created_at": "2026-05-15T09:00:00.000Z", "snoozed_at": "2026-06-01T09:00:00.000Z", // ISO-8601 — set on each snooze "payload": { "message": "Review adoption metrics" }, // kind-specific - "created_by": "user", // user | auto | system - "auto_dispatch": false // if true + kind=reminder, surface in auto-mode dispatch + "created_by": "user", // user | agent | system + "autonomous_dispatch": false // if true + kind=prompt/command, consume from autonomous mode } ``` ### Legacy JSONL Line Example ``` -{"schemaVersion":1,"id":"01ARZ3NDEKTSV4RRFFQ69G5FAV","kind":"reminder","status":"pending","due_at":"2026-06-15T09:00:00.000Z","created_at":"2026-05-15T09:00:00.000Z","payload":{"message":"Review adoption metrics"},"created_by":"user","auto_dispatch":false} +{"schemaVersion":1,"id":"01ARZ3NDEKTSV4RRFFQ69G5FAV","kind":"reminder","status":"pending","due_at":"2026-06-15T09:00:00.000Z","created_at":"2026-05-15T09:00:00.000Z","payload":{"message":"Review adoption metrics"},"created_by":"user","autonomous_dispatch":false} ``` --- @@ -95,7 +95,7 @@ Legacy `schedule.jsonl` files are import-only compatibility inputs. Rows without | `audit` | Audit surfaced at next planning turn | `unitId?` | | `command` | Shell command run by explicit `sf schedule run ` | `command`, `capture?` | -`review` and `audit` kinds are surfaced to the next autonomous planning turn (TBD: integration point in `sf_plan_slice` / `sf_plan_task` / auto-dispatch). They are stored but not auto-dispatched without a consumer. +`review` and `audit` kinds are surfaced to the next autonomous planning turn (TBD: integration point in `sf_plan_slice` / `sf_plan_task` / autonomous dispatch). They are stored but not autonomous dispatched without a consumer. --- @@ -236,9 +236,9 @@ schedule: These entries are created at milestone creation time. The `in` field is relative to `now`. The `on_complete` variant fires a duration after milestone completion. -### Auto-Dispatch +### Autonomous Dispatch -When `auto_dispatch: true` and `kind: "reminder"`, the item is surfaced as a dispatch input in auto-mode when `due_at <= now`. This is the mechanism for time-bound autonomous reminders. +When `autonomous_dispatch: true` and `kind: "prompt"` or `kind: "command"`, the item is consumed by autonomous mode when `due_at <= now`. This is the mechanism for time-bound autonomous repo work. --- diff --git a/docs/user-docs/auto-mode.md b/docs/user-docs/autonomous-mode.md similarity index 95% rename from docs/user-docs/auto-mode.md rename to docs/user-docs/autonomous-mode.md index d09d5671c..0ea134972 100644 --- a/docs/user-docs/auto-mode.md +++ b/docs/user-docs/autonomous-mode.md @@ -2,6 +2,11 @@ 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. +> 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. + ## How It Works Autonomous mode is governed by the **Unified Operation Kernel (UOK)**. UOK reads canonical project state, records lifecycle and recovery in the DB-backed ledger, and writes runtime files as projections for query, UI, and compatibility. It determines the next unit of work, creates a fresh agent session, injects a focused prompt with all relevant context pre-inlined, and lets the LLM execute. When the LLM finishes, autonomous mode reconciles the UOK ledger and projections before dispatching the next unit. Markdown files are projections for humans when structured state exists. @@ -79,6 +84,12 @@ No manual intervention needed for transient errors — the session pauses briefl SF maintains a `KNOWLEDGE.md` file — an append-only register of project-specific rules, patterns, and lessons learned. The agent reads it at the start of every unit and appends to it when discovering recurring issues, non-obvious patterns, or rules that future sessions should follow. This gives autonomous mode cross-session memory that survives context window boundaries. +Reflection files and thought dumps are treated as raw observations. Autonomous +mode should triage them before acting: keep weak hypotheses as source evidence, +promote validated patterns into `.sf`/DB-backed knowledge or requirements, and +turn actionable findings into tests, tasks, or docs. A thought file by itself is +not an instruction queue. + ### Context Pressure Monitor (v2.26) When context usage reaches 70%, SF sends a wrap-up signal to the agent, nudging it to finish durable output (commit, write summaries) before the context window fills. This prevents sessions from hitting the hard context limit mid-task with no artifacts written. @@ -267,7 +278,7 @@ Open the workflow visualizer — interactive tabs for progress, dependencies, me `Ctrl+Alt+G` or `/sf status` shows real-time progress: - Current milestone, slice, and task -- Auto mode elapsed time and phase +- Autonomous mode elapsed time and phase - Per-unit cost and token breakdown - Cost projections - Completed and in-progress units diff --git a/docs/user-docs/commands.md b/docs/user-docs/commands.md index 14e3e0dc9..22776d6d8 100644 --- a/docs/user-docs/commands.md +++ b/docs/user-docs/commands.md @@ -4,8 +4,8 @@ | Command | Description | |---------|-------------| -| `/sf` | Step mode — execute one unit at a time, pause between each | -| `/sf next` | Explicit step mode (same as `/sf`) | +| `/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 | @@ -214,7 +214,7 @@ sf headless --timeout 600000 autonomous sf headless dispatch plan # Create a new milestone from a context file and start autonomous mode -sf headless new-milestone --context brief.md --auto +sf headless new-milestone --context brief.md --autonomous # Create a milestone from inline text sf headless new-milestone --context-text "Build a REST API with auth" @@ -232,7 +232,7 @@ echo "Build a CLI tool" | sf headless new-milestone --context - | `--model ID` | Override the model for the headless session | | `--context ` | Context file for `new-milestone` (use `-` for stdin) | | `--context-text ` | Inline context text for `new-milestone` | -| `--auto` | Chain into autonomous mode after milestone creation | +| `--autonomous` | Chain into autonomous mode after milestone creation | **Exit codes:** `0` = complete, `1` = error or timeout, `2` = blocked. diff --git a/docs/user-docs/getting-started.md b/docs/user-docs/getting-started.md index 5120d9d84..8c4054cea 100644 --- a/docs/user-docs/getting-started.md +++ b/docs/user-docs/getting-started.md @@ -177,7 +177,7 @@ Type `/sf` inside a session. SF executes one unit of work at a time, pausing bet - **Roadmap exists, slices pending** — plan the next slice or execute a task - **Mid-task** — resume where you left off -Step mode keeps you in the loop, reviewing output between each step. +Assisted mode keeps you in the loop, reviewing output between each step. ### Autonomous Mode — `/sf autonomous` @@ -187,7 +187,7 @@ Type `/sf autonomous` and walk away. SF researches, plans, executes, verifies, c /sf autonomous ``` -See [Autonomous Mode](./auto-mode.md) for full details. +See [Autonomous Mode](./autonomous-mode.md) for full details. --- @@ -317,7 +317,7 @@ For more, see [Troubleshooting](./troubleshooting.md). ## Next Steps -- [Autonomous Mode](./auto-mode.md) — deep dive into autonomous execution +- [Autonomous Mode](./autonomous-mode.md) — deep dive into autonomous execution - [Configuration](./configuration.md) — model selection, timeouts, budgets - [Commands Reference](./commands.md) — all commands and shortcuts - [Provider Setup](./providers.md) — detailed setup for every provider diff --git a/docs/user-docs/git-strategy.md b/docs/user-docs/git-strategy.md index 5887f8d20..4e8c13e8e 100644 --- a/docs/user-docs/git-strategy.md +++ b/docs/user-docs/git-strategy.md @@ -93,7 +93,7 @@ These features apply only in **worktree mode**. ### Automatic (Autonomous Mode) -Auto mode creates and manages worktrees automatically: +Autonomous mode creates and manages worktrees automatically: 1. When a milestone starts, a worktree is created at `.sf/worktrees//` on branch `milestone/` 2. Planning artifacts from `.sf/milestones/` are copied into the worktree diff --git a/docs/user-docs/troubleshooting.md b/docs/user-docs/troubleshooting.md index 6d6c0b44d..34b7721f8 100644 --- a/docs/user-docs/troubleshooting.md +++ b/docs/user-docs/troubleshooting.md @@ -17,7 +17,7 @@ It checks: ## Common Issues -### Auto mode loops on the same unit +### Autonomous mode loops on the same unit **Symptoms:** The same unit (e.g., `research-slice` or `plan-slice`) dispatches repeatedly until hitting the dispatch limit. @@ -27,7 +27,7 @@ It checks: **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. -### Auto mode stops with "Loop detected" +### Autonomous mode stops with "Loop detected" **Cause:** A unit failed to produce its expected artifact twice in a row. @@ -73,7 +73,7 @@ source ~/.bashrc ### Provider errors during autonomous mode -**Symptoms:** Auto mode pauses with a provider error (rate limit, server error, auth failure). +**Symptoms:** Autonomous mode pauses with a provider error (rate limit, server error, auth failure). **How SF handles it (v2.26):** @@ -99,13 +99,13 @@ For common provider setup issues (role errors, streaming errors, model ID mismat ### Budget ceiling reached -**Symptoms:** Auto mode pauses with "Budget ceiling reached." +**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`. ### Stale lock file -**Symptoms:** Auto mode won't start, says another session is running. +**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: @@ -122,7 +122,7 @@ rm -rf "$(dirname .sf)/.sf.lock" ### Pre-dispatch says the milestone integration branch no longer exists -**Symptoms:** Auto mode or `/sf doctor` reports that a milestone recorded an integration branch that no longer exists in git. +**Symptoms:** Autonomous mode or `/sf 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. @@ -258,7 +258,7 @@ rm -rf "$(dirname .sf)/.sf.lock" ### Session lock stolen by `/sf` in another terminal -**Symptoms:** Running `/sf` (step mode) in a second terminal causes a running autonomous mode session to lose its lock. +**Symptoms:** Running `/sf` (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. diff --git a/packages/daemon/src/orchestrator.ts b/packages/daemon/src/orchestrator.ts index 157189659..fb8a770cc 100644 --- a/packages/daemon/src/orchestrator.ts +++ b/packages/daemon/src/orchestrator.ts @@ -95,7 +95,7 @@ const TOOLS: Tool[] = [ { name: "start_session", description: - 'Start a new SF autonomous-mode session for a project. Provide the absolute project path. Optionally provide a command to run instead of the default "/sf autonomous".', + 'Start a new SF autonomous mode session for a project. Provide the absolute project path. Optionally provide a command to run instead of the default "/sf autonomous".', input_schema: { type: "object" as const, properties: { diff --git a/packages/daemon/src/session-manager.test.ts b/packages/daemon/src/session-manager.test.ts index da6af8774..c4def2f3e 100644 --- a/packages/daemon/src/session-manager.test.ts +++ b/packages/daemon/src/session-manager.test.ts @@ -75,7 +75,7 @@ class MockRpcClient { type: "extension_ui_request", id: "pause-notice", method: "notify", - message: "Auto-mode paused: daemon reload requested", + message: "Autonomous mode paused: daemon reload requested", }); }); } @@ -363,7 +363,7 @@ describe("SessionManager", () => { type: "extension_ui_request", id: "n1", method: "notify", - message: "Auto-mode stopped: completed all tasks", + message: "Autonomous mode stopped: completed all tasks", }); assert.equal(session.status, "completed"); @@ -451,7 +451,7 @@ describe("SessionManager", () => { type: "extension_ui_request", id: "n2", method: "notify", - message: "Auto-mode stopped: all done", + message: "Autonomous mode stopped: all done", }); assert.equal(session.status, "completed"); @@ -644,9 +644,9 @@ describe("SessionManager", () => { assert.equal(session.pendingBlocker, null); }); - // ---- Terminal detection (auto-mode stopped notification) ---- + // ---- Terminal detection (autonomous mode stopped notification) ---- - it("detects terminal from auto-mode stopped notification", async () => { + it("detects terminal from autonomous mode stopped notification", async () => { const { manager } = createManager(); const sessionId = await manager.startSession({ @@ -658,7 +658,7 @@ describe("SessionManager", () => { type: "extension_ui_request", id: "n1", method: "notify", - message: "Step-mode stopped: user requested", + message: "Assisted mode stopped: user requested", }); assert.equal(session.status, "completed"); @@ -763,7 +763,7 @@ describe("SessionManager", () => { type: "extension_ui_request", id: "n1", method: "notify", - message: "Auto-mode stopped: success", + message: "Autonomous mode stopped: success", }); assert.ok(emittedData); @@ -935,7 +935,7 @@ describe("SessionManager", () => { type: "extension_ui_request", id: "bn-1", method: "notify", - message: "Auto-mode stopped: Blocked: waiting for approval", + message: "Autonomous mode stopped: Blocked: waiting for approval", }); assert.equal(session.status, "blocked"); diff --git a/packages/daemon/src/session-manager.ts b/packages/daemon/src/session-manager.ts index 0ef86af49..8952b9795 100644 --- a/packages/daemon/src/session-manager.ts +++ b/packages/daemon/src/session-manager.ts @@ -43,10 +43,9 @@ const FIRE_AND_FORGET_METHODS = new Set([ const TERMINAL_PREFIXES = [ "autonomous mode stopped", - "auto-mode stopped", "autonomous mode paused", - "auto-mode paused", - "step-mode stopped", + "assisted mode stopped", + "assisted mode paused", ]; const RELOAD_PAUSE_TIMEOUT_MS = 5_000; @@ -62,7 +61,9 @@ function isBlockedNotification(event: Record): boolean { return false; const message = String(event.message ?? "").toLowerCase(); return ( - message.includes("blocked:") || message.startsWith("autonomous mode paused") + message.includes("blocked:") || + message.startsWith("autonomous mode paused") || + message.startsWith("assisted mode paused") ); } @@ -434,7 +435,7 @@ export class SessionManager extends EventEmitter { } } - // Terminal detection — autonomous mode/step-mode stopped + // Terminal detection — autonomous mode/assisted mode stopped if (isTerminalNotification(event as Record)) { if (isBlockedNotification(event as Record)) { session.status = "blocked"; diff --git a/packages/pi-coding-agent/src/core/agent-session.ts b/packages/pi-coding-agent/src/core/agent-session.ts index 19424e35c..63300ca9c 100644 --- a/packages/pi-coding-agent/src/core/agent-session.ts +++ b/packages/pi-coding-agent/src/core/agent-session.ts @@ -1795,7 +1795,7 @@ export class AgentSession { } finally { this._sessionSwitchPending = false; } - // Update cwd to current process directory — auto-mode may have chdir'd + // Update cwd to current process directory — autonomous mode may have chdir'd // into a worktree since the original session was created. const previousCwd = this._cwd; this._cwd = process.cwd(); @@ -1807,7 +1807,7 @@ export class AgentSession { this.sessionManager.appendThinkingLevelChange(this.thinkingLevel); - // Rebuild tools when cwd changed (e.g., auto-mode entered a worktree). + // Rebuild tools when cwd changed (e.g., autonomous mode entered a worktree). // Tools capture cwd at creation time for path resolution — without // rebuilding, write/read/edit/bash resolve relative paths against // the original project root instead of the worktree (#633). @@ -1821,7 +1821,7 @@ export class AgentSession { // Extensions (e.g., discuss flows) may narrow the active tool list // via setActiveTools() during a session. Without this refresh, the // narrowed set persists into the next session — causing tools like - // sf_plan_slice to be missing from auto-mode subagent sessions. + // sf_plan_slice to be missing from autonomous mode subagent sessions. this._refreshToolRegistry({ activeToolNames: this.getActiveToolNames(), includeAllExtensionTools: true, diff --git a/packages/pi-coding-agent/src/core/lsp/client.ts b/packages/pi-coding-agent/src/core/lsp/client.ts index d6b710e81..b4cdfb6b8 100644 --- a/packages/pi-coding-agent/src/core/lsp/client.ts +++ b/packages/pi-coding-agent/src/core/lsp/client.ts @@ -564,7 +564,7 @@ async function getOrCreateClientOnce( }); // Handle spawn failure (e.g., ENOENT when the command doesn't exist). - // Without this, the error bubbles up and can crash auto-mode (#901). + // Without this, the error bubbles up and can crash autonomous mode (#901). proc.on("error", (err: NodeJS.ErrnoException) => { if (err.code === "ENOENT") { proc.emit("exit", 1); 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 90e59c73c..70698cd75 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 @@ -137,6 +137,7 @@ describe("ModelRegistry — public discovery providers", () => { const registry = new ModelRegistry( AuthStorage.inMemory({}), undefined, + undefined, new ModelDiscoveryCache(join(testDir, "ollama-cloud-cache.json")), ); const results = await registry.discoverModels(["ollama-cloud"]); @@ -173,6 +174,7 @@ describe("ModelRegistry — public discovery providers", () => { "ollama-cloud": { type: "api_key", key: "ollama-test" }, }), undefined, + undefined, new ModelDiscoveryCache(join(testDir, "ollama-cloud-auth-cache.json")), ); await registry.discoverModels(["ollama-cloud"]); @@ -213,6 +215,7 @@ describe("ModelRegistry — public discovery providers", () => { zai: { type: "api_key", key: "zai-test" }, }), undefined, + undefined, new ModelDiscoveryCache(join(testDir, "zai-cache.json")), ); const results = await registry.discoverModels(["zai"]); @@ -269,6 +272,7 @@ describe("ModelRegistry — public discovery providers", () => { const registry = new ModelRegistry( AuthStorage.inMemory({}), undefined, + undefined, new ModelDiscoveryCache(join(testDir, "memory-cache.json")), ); const results = await registry.discoverModelCatalogs([ diff --git a/packages/pi-coding-agent/src/core/retry-handler.ts b/packages/pi-coding-agent/src/core/retry-handler.ts index 7c19242e4..e61b58941 100644 --- a/packages/pi-coding-agent/src/core/retry-handler.ts +++ b/packages/pi-coding-agent/src/core/retry-handler.ts @@ -212,7 +212,7 @@ export class RetryHandler { if (isRateLimit || isQuotaError || isAuthError) { // For quota errors with a retry-after hint, wait before switching providers. // Only wait if the reset is very short (< 5s); otherwise falling back to - // another provider is faster and keeps auto-mode throughput up. + // another provider is faster and keeps autonomous mode throughput up. const QUOTA_WAIT_THRESHOLD_MS = 5_000; if ( isQuotaError && diff --git a/scripts/check-sf-extension-inventory.mjs b/scripts/check-sf-extension-inventory.mjs index 82584714c..2d3266541 100644 --- a/scripts/check-sf-extension-inventory.mjs +++ b/scripts/check-sf-extension-inventory.mjs @@ -148,7 +148,7 @@ function parseTopLevelCatalogCommands() { function parseHandledTopLevelCommands() { const handlerFiles = [ "core.js", - "auto.js", + "autonomous.js", "parallel.js", "workflow.js", "ops.js", diff --git a/scripts/recover-sf-1364.sh b/scripts/recover-sf-1364.sh index 43bf5e23d..57719381d 100755 --- a/scripts/recover-sf-1364.sh +++ b/scripts/recover-sf-1364.sh @@ -1,9 +1,6 @@ #!/usr/bin/env bash # recover-sf-1364.sh — Recovery script for issue #1364 (Linux / macOS) # -# For Windows use the PowerShell equivalent: -# powershell -ExecutionPolicy Bypass -File scripts\recover-sf-1364.ps1 [-DryRun] -# # CRITICAL DATA-LOSS BUG: SF versions 2.30.0–2.35.x unconditionally added # ".sf" to .gitignore via ensureGitignore(), causing git to report all # tracked .sf/ files as deleted. Fixed in v2.36.0 (PR #1367). diff --git a/scripts/recover-sf-1668.sh b/scripts/recover-sf-1668.sh deleted file mode 100755 index 59cdb3e32..000000000 --- a/scripts/recover-sf-1668.sh +++ /dev/null @@ -1,446 +0,0 @@ -#!/usr/bin/env bash -# recover-sf-1668.sh — Recovery script for issue #1668 (Linux / macOS) -# -# SF v2.39.x deleted the milestone branch and worktree directory when a -# merge failed due to the repo using `master` as its default branch (not -# `main`). The commits were never merged — they are orphaned in the git -# object store and can be recovered via git reflog or git fsck. -# -# This script: -# 1. Searches git reflog for the deleted milestone branch (fastest path) -# 2. Falls back to git fsck --unreachable to find orphaned commits -# 3. Ranks candidates by recency and SF commit message patterns -# 4. Creates a recovery branch at the identified commit -# 5. Reports what was found and how to complete the merge manually -# -# Usage: -# bash scripts/recover-sf-1668.sh [--milestone ] [--dry-run] [--auto] -# -# Options: -# --milestone SF milestone ID (e.g. M001-g2nalq). -# When omitted the script scans all recent orphans. -# --dry-run Show what would be done without making any changes. -# --auto Pick the best candidate automatically (no prompts). -# -# Requirements: git >= 2.23, bash >= 4.x -# -# Affected versions: SF.39.x -# Fixed in: SF.40.1 (PR #1669) - -set -euo pipefail - -# ─── Colours ────────────────────────────────────────────────────────────────── - -RED='\033[0;31m' -YELLOW='\033[1;33m' -GREEN='\033[0;32m' -CYAN='\033[0;36m' -BOLD='\033[1m' -DIM='\033[2m' -RESET='\033[0m' - -# ─── Args ───────────────────────────────────────────────────────────────────── - -DRY_RUN=false -AUTO=false -MILESTONE_ID="" - -while [[ $# -gt 0 ]]; do - case "$1" in - --dry-run) DRY_RUN=true; shift ;; - --auto) AUTO=true; shift ;; - --milestone) - [[ $# -lt 2 ]] && { echo "Error: --milestone requires an argument" >&2; exit 1; } - MILESTONE_ID="$2"; shift 2 ;; - --milestone=*) - MILESTONE_ID="${1#--milestone=}"; shift ;; - -h|--help) - sed -n '2,/^set -/p' "$0" | grep '^#' | sed 's/^# \{0,1\}//' - exit 0 ;; - *) - echo "Unknown argument: $1" >&2 - echo "Usage: $0 [--milestone ] [--dry-run] [--auto]" >&2 - exit 1 ;; - esac -done - -# ─── Helpers ────────────────────────────────────────────────────────────────── - -info() { echo -e "${CYAN}[info]${RESET} $*"; } -ok() { echo -e "${GREEN}[ok]${RESET} $*"; } -warn() { echo -e "${YELLOW}[warn]${RESET} $*"; } -error() { echo -e "${RED}[error]${RESET} $*" >&2; } -section() { echo -e "\n${BOLD}$*${RESET}"; } -dim() { echo -e "${DIM}$*${RESET}"; } - -die() { - error "$*" - exit 1 -} - -run() { - if $DRY_RUN; then - echo -e " ${YELLOW}(dry-run)${RESET} $*" - else - eval "$*" - fi -} - -# ─── Preflight ──────────────────────────────────────────────────────────────── - -section "── Preflight ───────────────────────────────────────────────────────────" - -if ! git rev-parse --git-dir > /dev/null 2>&1; then - die "Not inside a git repository. Run this from your project root." -fi - -REPO_ROOT="$(git rev-parse --show-toplevel)" -cd "$REPO_ROOT" -info "Repo root: $REPO_ROOT" - -$DRY_RUN && warn "DRY-RUN mode — no changes will be made." - -# ─── Step 1: Confirm the milestone branch is gone ───────────────────────────── - -section "── Step 1: Verify milestone branch is missing ───────────────────────────" - -BRANCH_PATTERN="milestone/" -if [[ -n "$MILESTONE_ID" ]]; then - BRANCH_PATTERN="milestone/${MILESTONE_ID}" -fi - -LIVE_BRANCHES="$(git branch | grep "$BRANCH_PATTERN" 2>/dev/null | tr -d '* ' || true)" - -if [[ -n "$LIVE_BRANCHES" ]]; then - ok "Found live milestone branch(es):" - echo "$LIVE_BRANCHES" | while IFS= read -r b; do echo " $b"; done - echo "" - warn "The branch still exists — are you sure it was lost?" - echo " If you want to check out existing work: git checkout ${LIVE_BRANCHES%%$'\n'*}" - echo " To merge it manually: git checkout master && git merge --squash ${LIVE_BRANCHES%%$'\n'*}" - echo "" - echo "Re-run with --milestone to force scanning for a specific orphaned commit." - if [[ -z "$MILESTONE_ID" ]]; then - exit 0 - fi -fi - -if [[ -n "$MILESTONE_ID" && -n "$LIVE_BRANCHES" ]]; then - warn "Milestone branch milestone/${MILESTONE_ID} is still live — continuing scan anyway." -elif [[ -n "$MILESTONE_ID" ]]; then - info "Confirmed: milestone/${MILESTONE_ID} branch is gone." -else - info "No live milestone/ branches found — scanning for orphaned commits." -fi - -# ─── Step 2: Search git reflog (fastest, most reliable) ─────────────────────── - -section "── Step 2: Search git reflog for deleted branch ────────────────────────" - -# git reflog stores branch moves and deletions in .git/logs/refs/heads/ -# It is retained for 90 days by default (gc.reflogExpire). -REFLOG_FOUND_SHA="" -REFLOG_FOUND_BRANCH="" - -if [[ -n "$MILESTONE_ID" ]]; then - REFLOG_PATH="${REPO_ROOT}/.git/logs/refs/heads/milestone/${MILESTONE_ID}" - if [[ -f "$REFLOG_PATH" ]]; then - # Last line of the reflog for this branch is the most recent tip - REFLOG_FOUND_SHA="$(tail -1 "$REFLOG_PATH" | awk '{print $2}')" - REFLOG_FOUND_BRANCH="milestone/${MILESTONE_ID}" - ok "Reflog entry found for milestone/${MILESTONE_ID} — commit: ${REFLOG_FOUND_SHA:0:12}" - else - info "No reflog file at .git/logs/refs/heads/milestone/${MILESTONE_ID}" - fi -fi - -# Also try git reflog (in-memory index, works without the raw file) -if [[ -z "$REFLOG_FOUND_SHA" ]]; then - info "Scanning git reflog for milestone/ commits..." - REFLOG_MILESTONES="$(git reflog --all --format="%H %gs" 2>/dev/null \ - | grep -E "(checkout|commit|merge).*milestone/" \ - | head -20 || true)" - - if [[ -n "$REFLOG_MILESTONES" ]]; then - info "Found milestone-related reflog entries:" - echo "$REFLOG_MILESTONES" | while IFS= read -r line; do - dim " $line" - done - # Extract the most recent SHA from the most relevant entry - if [[ -n "$MILESTONE_ID" ]]; then - MATCH="$(echo "$REFLOG_MILESTONES" | grep "milestone/${MILESTONE_ID}" | head -1 || true)" - else - MATCH="$(echo "$REFLOG_MILESTONES" | head -1 || true)" - fi - if [[ -n "$MATCH" ]]; then - REFLOG_FOUND_SHA="$(echo "$MATCH" | awk '{print $1}')" - REFLOG_FOUND_BRANCH="$(echo "$MATCH" | grep -oE 'milestone/[^ ]+' | head -1 || echo "milestone/unknown")" - fi - else - info "No milestone/ entries in reflog." - fi -fi - -# ─── Step 3: Fall back to git fsck if reflog didn't find it ─────────────────── - -section "── Step 3: Scan for orphaned (unreachable) commits ───────────────────" - -FSCK_CANDIDATES=() -FSCK_CANDIDATE_MSGS=() -FSCK_CANDIDATE_DATES=() -FSCK_CANDIDATE_FILES=() - -if [[ -z "$REFLOG_FOUND_SHA" ]]; then - info "Running git fsck --unreachable (this may take a moment)..." - - # Collect all unreachable commit hashes - UNREACHABLE_COMMITS="$(git fsck --unreachable --no-reflogs 2>/dev/null \ - | grep '^unreachable commit' \ - | awk '{print $3}' || true)" - - if [[ -z "$UNREACHABLE_COMMITS" ]]; then - # Try without --no-reflogs as a fallback (less conservative) - UNREACHABLE_COMMITS="$(git fsck --unreachable 2>/dev/null \ - | grep '^unreachable commit' \ - | awk '{print $3}' || true)" - fi - - TOTAL="$(echo "$UNREACHABLE_COMMITS" | grep -c . || true)" - info "Found ${TOTAL} unreachable commit object(s)." - - if [[ -z "$UNREACHABLE_COMMITS" || "$TOTAL" -eq 0 ]]; then - error "No unreachable commits found." - echo "" - echo "This means one of:" - echo " 1. git gc has already been run and the objects were pruned" - echo " (objects are pruned after 14 days by default)" - echo " 2. The commits were never written to the object store" - echo " 3. The wrong repository is being scanned" - echo "" - echo "If git gc ran, the objects may be unrecoverable without a backup." - echo "Try: git reflog --all | grep milestone" - exit 1 - fi - - # Score each unreachable commit — rank by recency and SF message patterns. - # SF milestone commits look like: "feat(M001-g2nalq): " - # Slice merges look like: "feat(M001-g2nalq/S01): <slice>" - # - # Performance: use a single `git log --no-walk=unsorted --stdin` call to - # read all commit metadata in one pass instead of one `git show` per commit. - CUTOFF="$(date -d '30 days ago' '+%s' 2>/dev/null || date -v-30d '+%s' 2>/dev/null || echo 0)" - WEEK_AGO="$(date -d '7 days ago' '+%s' 2>/dev/null || date -v-7d '+%s' 2>/dev/null || echo 0)" - - # Batch-read all commits: output format per commit is: - # HASH<TAB>UNIX_TIMESTAMP<TAB>ISO_DATE<TAB>SUBJECT - # separated by NUL so multi-line subjects don't break parsing. - BATCH_LOG="$(echo "$UNREACHABLE_COMMITS" \ - | git log --no-walk=unsorted --stdin --format=$'%H\t%ct\t%ci\t%s' 2>/dev/null || true)" - - while IFS=$'\t' read -r sha commit_ts commit_date_hr commit_msg; do - [[ -z "$sha" ]] && continue - [[ -z "$commit_ts" || "$commit_ts" -lt "$CUTOFF" ]] && continue - - # Score: milestone pattern in subject is highest signal - SCORE=0 - if [[ -n "$MILESTONE_ID" ]] && echo "$commit_msg" | grep -qiE "(milestone[/ ])?${MILESTONE_ID}"; then - SCORE=$((SCORE + 100)) - fi - if echo "$commit_msg" | grep -qE '^feat\([A-Z][0-9]+'; then - SCORE=$((SCORE + 50)) - fi - if echo "$commit_msg" | grep -qiE 'milestone/|complete-milestone|SF|slice'; then - SCORE=$((SCORE + 20)) - fi - if [[ "$commit_ts" -gt "$WEEK_AGO" ]]; then - SCORE=$((SCORE + 10)) - fi - - FSCK_CANDIDATES+=("$sha|$SCORE") - FSCK_CANDIDATE_MSGS+=("$commit_msg") - FSCK_CANDIDATE_DATES+=("$commit_date_hr") - FSCK_CANDIDATE_FILES+=("?") - done <<< "$BATCH_LOG" - - if [[ ${#FSCK_CANDIDATES[@]} -eq 0 ]]; then - error "No recent unreachable commits found within the last 30 days." - echo "" - echo "Objects may have been pruned by git gc, or the issue occurred more than 30 days ago." - echo "Try: git fsck --unreachable --no-reflogs 2>/dev/null | grep commit" - exit 1 - fi - - # Sort by score descending, keep top 10 - IFS=$'\n' SORTED_CANDIDATES=($( - for i in "${!FSCK_CANDIDATES[@]}"; do - echo "${FSCK_CANDIDATES[$i]}|$i" - done | sort -t'|' -k2 -rn | head -10 - )) - unset IFS - - info "Top candidates (scored by recency and SF message patterns):" - echo "" - NUM=1 - SORTED_IDXS=() - for entry in "${SORTED_CANDIDATES[@]}"; do - SHA="${entry%%|*}" - IDX="${entry##*|}" - SORTED_IDXS+=("$IDX") - MSG="${FSCK_CANDIDATE_MSGS[$IDX]}" - DATE="${FSCK_CANDIDATE_DATES[$IDX]}" - FILES="${FSCK_CANDIDATE_FILES[$IDX]}" - echo -e " ${BOLD}${NUM})${RESET} ${sha:0:12} ${GREEN}${MSG}${RESET}" - echo -e " ${DIM}${DATE} — ${FILES}${RESET}" - NUM=$((NUM + 1)) - done - echo "" -fi - -# ─── Step 4: Select the recovery commit ─────────────────────────────────────── - -section "── Step 4: Select recovery commit ──────────────────────────────────────" - -RECOVERY_SHA="" -RECOVERY_SOURCE="" - -if [[ -n "$REFLOG_FOUND_SHA" ]]; then - RECOVERY_SHA="$REFLOG_FOUND_SHA" - RECOVERY_SOURCE="reflog (${REFLOG_FOUND_BRANCH})" - info "Using reflog candidate: ${RECOVERY_SHA:0:12}" - MSG="$(git show -s --format="%s %ci" "$RECOVERY_SHA" 2>/dev/null || echo "unknown")" - dim " $MSG" - -elif [[ ${#SORTED_IDXS[@]} -eq 1 ]] || $AUTO; then - # Auto-select first (highest scored) candidate - FIRST_ENTRY="${SORTED_CANDIDATES[0]}" - FIRST_SHA="${FIRST_ENTRY%%|*}" - FIRST_IDX="${FIRST_ENTRY##*|}" - RECOVERY_SHA="$FIRST_SHA" - RECOVERY_SOURCE="fsck (auto-selected)" - info "Auto-selecting best candidate: ${RECOVERY_SHA:0:12}" - -else - # Prompt user to select - echo -n "Select a candidate to recover [1-${#SORTED_CANDIDATES[@]}, or q to quit]: " - read -r SELECTION - - if [[ "$SELECTION" == "q" ]]; then - info "Aborted." - exit 0 - fi - - if ! [[ "$SELECTION" =~ ^[0-9]+$ ]] || \ - [[ "$SELECTION" -lt 1 ]] || \ - [[ "$SELECTION" -gt ${#SORTED_CANDIDATES[@]} ]]; then - die "Invalid selection: $SELECTION" - fi - - SEL_IDX=$((SELECTION - 1)) - SEL_ENTRY="${SORTED_CANDIDATES[$SEL_IDX]}" - RECOVERY_SHA="${SEL_ENTRY%%|*}" - RECOVERY_SOURCE="fsck (user-selected #${SELECTION})" -fi - -if [[ -z "$RECOVERY_SHA" ]]; then - die "Could not determine a recovery commit. See output above." -fi - -ok "Recovery commit: ${RECOVERY_SHA:0:16} (source: ${RECOVERY_SOURCE})" - -# Show what's in this commit -echo "" -info "Commit details:" -git show -s --format=" Message: %s%n Author: %an <%ae>%n Date: %ci%n Full SHA: %H" "$RECOVERY_SHA" -echo "" -info "Files at this commit (first 30):" -git show --stat --format="" "$RECOVERY_SHA" 2>/dev/null | head -30 -echo "" - -# ─── Step 5: Create recovery branch ─────────────────────────────────────────── - -section "── Step 5: Create recovery branch ──────────────────────────────────────" - -# Determine recovery branch name -if [[ -n "$MILESTONE_ID" ]]; then - RECOVERY_BRANCH="recovery/1668/${MILESTONE_ID}" -elif [[ -n "$REFLOG_FOUND_BRANCH" ]]; then - CLEAN_NAME="${REFLOG_FOUND_BRANCH//\//-}" - RECOVERY_BRANCH="recovery/1668/${CLEAN_NAME}" -else - SHORT_SHA="${RECOVERY_SHA:0:8}" - RECOVERY_BRANCH="recovery/1668/commit-${SHORT_SHA}" -fi - -# Check if it already exists -if git show-ref --verify --quiet "refs/heads/${RECOVERY_BRANCH}" 2>/dev/null; then - warn "Branch ${RECOVERY_BRANCH} already exists." - if ! $AUTO; then - echo -n "Overwrite it? [y/N]: " - read -r ANSWER - if [[ "$ANSWER" != "y" && "$ANSWER" != "Y" ]]; then - info "Aborted. Existing branch preserved." - exit 0 - fi - fi - run "git branch -D \"${RECOVERY_BRANCH}\"" -fi - -run "git branch \"${RECOVERY_BRANCH}\" \"${RECOVERY_SHA}\"" - -if ! $DRY_RUN; then - ok "Recovery branch created: ${RECOVERY_BRANCH}" -else - ok "(dry-run) Would create branch: ${RECOVERY_BRANCH} → ${RECOVERY_SHA:0:12}" -fi - -# ─── Step 6: Verify the recovery branch ─────────────────────────────────────── - -if ! $DRY_RUN; then - section "── Step 6: Verify recovery branch ──────────────────────────────────────" - - FILE_LIST="$(git ls-tree -r --name-only "${RECOVERY_BRANCH}" 2>/dev/null | grep -v '^\.sf/' || true)" - FILE_COUNT="$(echo "$FILE_LIST" | grep -c . || true)" - - info "Files recoverable (excluding .sf/ state files): ${FILE_COUNT}" - echo "$FILE_LIST" | head -30 | while IFS= read -r f; do echo " $f"; done - if [[ "$FILE_COUNT" -gt 30 ]]; then - dim " ... and $((FILE_COUNT - 30)) more" - fi -fi - -# ─── Summary ────────────────────────────────────────────────────────────────── - -section "── Recovery Summary ─────────────────────────────────────────────────────" - -if $DRY_RUN; then - echo -e "${YELLOW}Dry-run complete. Re-run without --dry-run to apply.${RESET}" - exit 0 -fi - -DEFAULT_BRANCH="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||' \ - || git for-each-ref --format='%(refname:short)' 'refs/heads/main' 'refs/heads/master' 2>/dev/null | head -1 \ - || git branch --show-current)" - -echo -e "${GREEN}Recovery branch ready: ${BOLD}${RECOVERY_BRANCH}${RESET}" -echo "" -echo "Next steps:" -echo "" -echo -e " ${BOLD}1. Inspect the recovered files:${RESET}" -echo " git checkout ${RECOVERY_BRANCH}" -echo " ls -la" -echo "" -echo -e " ${BOLD}2. Verify your code is intact:${RESET}" -echo " git log --oneline ${RECOVERY_BRANCH} | head -20" -echo " git show --stat ${RECOVERY_BRANCH}" -echo "" -echo -e " ${BOLD}3. Merge to your default branch (${DEFAULT_BRANCH}):${RESET}" -echo " git checkout ${DEFAULT_BRANCH}" -echo " git merge --squash ${RECOVERY_BRANCH}" -echo " git commit -m \"feat: recover milestone from #1668\"" -echo "" -echo -e " ${BOLD}4. Clean up after verifying:${RESET}" -echo " git branch -D ${RECOVERY_BRANCH}" -echo "" -echo -e "${DIM}Note: update SF to v2.40.1+ to prevent this from recurring.${RESET}" -echo " PR: https://github.com/singularity-forge/sf-run/pull/1669" -echo "" diff --git a/src/cli.ts b/src/cli.ts index 56daf1279..4bfb08d34 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -711,7 +711,7 @@ if (cliFlags.listModels !== undefined) { const searchPattern = typeof cliFlags.listModels === "string" ? cliFlags.listModels : undefined; // Apply allowed_providers / blocked_providers from SF preferences so the - // listing matches what auto-mode would actually be willing to dispatch. + // listing matches what autonomous mode would actually be willing to dispatch. const sfPrefs = loadEffectiveSFPreferences()?.preferences as | { allowed_providers?: string[]; diff --git a/src/headless-events.ts b/src/headless-events.ts index 6f0548c48..b36b03afd 100644 --- a/src/headless-events.ts +++ b/src/headless-events.ts @@ -95,13 +95,13 @@ export function shouldRestartHeadlessRun( // --------------------------------------------------------------------------- /** - * Detect genuine auto-mode termination notifications. + * Detect genuine autonomous mode termination notifications. * * Only matches the actual stop/pause signals emitted by stopAuto()/pauseAuto(): - * "Auto-mode stopped..." - * "Step-mode stopped..." - * "Auto-mode paused..." - * "Step-mode paused..." + * "Autonomous mode stopped..." + * "Assisted mode stopped..." + * "Autonomous mode paused..." + * "Assisted mode paused..." * * Does NOT match progress notifications that happen to contain words like * "complete" or "stopped" (e.g., "Override resolved — rewrite-docs completed", @@ -110,10 +110,10 @@ export function shouldRestartHeadlessRun( * Blocked detection is separate — checked via isBlockedNotification. */ export const TERMINAL_PREFIXES = [ - "auto-mode stopped", - "step-mode stopped", - "auto-mode paused", - "step-mode paused", + "autonomous mode stopped", + "assisted mode stopped", + "autonomous mode paused", + "assisted mode paused", ]; /** @@ -132,8 +132,8 @@ export const NEW_MILESTONE_IDLE_TIMEOUT_MS = 120_000; /** * Deadlock backstop for long-running multi-turn commands (auto, next, * discuss, plan). The role here is NOT idle-detection ("are we done?") — - * those commands signal completion explicitly via "auto-mode stopped" / - * "step-mode stopped" terminal notifications, and the agent's child-process + * those commands signal completion explicitly via "autonomous mode stopped" / + * "assisted mode stopped" terminal notifications, and the agent's child-process * exit catches crashes. The only remaining failure mode is a truly hung * process (deadlock, network stuck without retry, infinite reasoning loop * outside the LLM's awareness). 30 minutes is long enough to never misfire @@ -171,7 +171,7 @@ function getEventMetadata( } /** - * Detect genuine auto-mode or step-mode termination signals. Checks structured + * Detect genuine autonomous mode or assisted mode termination signals. Checks structured * metadata first, then falls back to legacy text-matching heuristics. */ export function isTerminalNotification( @@ -196,8 +196,8 @@ export function isPauseNotification(event: Record<string, unknown>): boolean { // Fallback: legacy text heuristics. const message = String(event.message ?? "").toLowerCase(); return ( - message.startsWith("auto-mode paused") || - message.startsWith("step-mode paused") + message.startsWith("autonomous mode paused") || + message.startsWith("assisted mode paused") ); } diff --git a/src/headless-query.ts b/src/headless-query.ts index 4a09af699..1ae495dac 100644 --- a/src/headless-query.ts +++ b/src/headless-query.ts @@ -7,7 +7,7 @@ * Output: { schemaVersion, state, next, cost } * schemaVersion — output contract version * state — deriveState() output (phase, milestones, progress, blockers) - * next — dry-run dispatch preview (what auto-mode would do next) + * next — dry-run dispatch preview (what autonomous mode would do next) * cost — aggregated parallel worker costs * * Note: Extension modules are .ts files loaded via jiti (not compiled to .js). @@ -27,7 +27,7 @@ const jiti = createJiti(import.meta.filename, { debug: false, }); // Resolve extensions from the synced agent directory so headless-query -// loads the same extension copy as interactive/auto modes (#3471). +// loads the same extension copy as interactive/autonomous modes (#3471). // The synced runtime is compiled .js; source-tree fallback is .ts. const agentExtensionsDir = join(getSfEnv().agentDir, "extensions", "sf"); const useAgentDir = existsSync(join(agentExtensionsDir, "state.js")); diff --git a/src/headless-ui.ts b/src/headless-ui.ts index cb83bfc07..0539fb0f2 100644 --- a/src/headless-ui.ts +++ b/src/headless-ui.ts @@ -339,7 +339,7 @@ export function handleExtensionUIRequest( // to proceed. Detect by title and pick the force option. const title = String(event.title ?? ""); let selected = event.options?.[0] ?? ""; - if (title.includes("Auto-mode is running") && event.options) { + if (title.includes("Autonomous mode is running") && event.options) { const forceOption = event.options.find((o) => o.toLowerCase().includes("force start"), ); @@ -556,7 +556,7 @@ export function formatCostLine( /** * Format a periodic liveness line for headless runs. * - * Purpose: make long model calls and quiet auto-mode phases observable without + * Purpose: make long model calls and quiet autonomous mode phases observable without * changing machine-readable JSON output. */ export function formatHeadlessHeartbeat(ctx: HeadlessHeartbeatContext): string { diff --git a/src/headless.ts b/src/headless.ts index 83b142ed6..34bc5422f 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -76,6 +76,10 @@ import { summarizeToolArgs, } from "./headless-ui.js"; import { getProjectSessionsDir } from "./project-sessions.js"; +import { + findUnsupportedAutonomousArgs, + formatUnsupportedAutonomousArgs, +} from "./resources/extensions/sf/autonomous-command-args.js"; import { ensureSfSymlink, externalSfRoot, @@ -187,7 +191,7 @@ export interface HeadlessOptions { commandArgs: string[]; context?: string; // file path or '-' for stdin contextText?: string; // inline text - auto?: boolean; // chain into autonomous mode after milestone creation + 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) supervised?: boolean; // supervised mode: forward interactive requests to orchestrator @@ -408,8 +412,13 @@ export function parseHeadlessArgs(argv: string[]): HeadlessOptions { options.context = args[++i]; } else if (arg === "--context-text" && i + 1 < args.length) { options.contextText = args[++i]; + } else if (arg === "--autonomous") { + options.chainAutonomous = true; } else if (arg === "--auto") { - options.auto = true; + process.stderr.write( + "[headless] Error: --auto was removed. Use --autonomous to chain into autonomous mode.\n", + ); + process.exit(1); } else if (arg === "--verbose") { options.verbose = true; } else if (arg === "--max-restarts" && i + 1 < args.length) { @@ -457,7 +466,7 @@ export function parseHeadlessArgs(argv: string[]): HeadlessOptions { } else if (!commandSeen) { if (arg === "autonomous") { options.command = "autonomous"; - options.auto = true; // autonomous subcommand implies --auto + options.chainAutonomous = true; } else { options.command = arg; } @@ -565,6 +574,37 @@ async function runHeadlessOnce( const headlessRunId = `headless-${new Date(startTime).toISOString().replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`; const requestedCommand = options.command; const requestedCommandArgs = [...options.commandArgs]; + if (options.command === "autonomous") { + const unsupportedArgs = findUnsupportedAutonomousArgs(options.commandArgs); + if (unsupportedArgs.length > 0) { + process.stderr.write( + `[headless] ${formatUnsupportedAutonomousArgs(unsupportedArgs)}\n`, + ); + if (options.outputFormat === "json") { + const result: HeadlessJsonResult = { + schemaVersion: 1, + status: "error", + exitCode: EXIT_ERROR, + duration: Date.now() - startTime, + cost: { + total: 0, + input_tokens: 0, + output_tokens: 0, + cache_read_tokens: 0, + cache_write_tokens: 0, + }, + toolCalls: 0, + events: 0, + }; + process.stdout.write(`${JSON.stringify(result)}\n`); + } + return { + exitCode: EXIT_ERROR, + interrupted: false, + timedOut: false, + }; + } + } if (options.command === "help") { const { printSubcommandHelp } = await import("./help-text.js"); printSubcommandHelp("headless", process.env.SF_VERSION || "0.0.0"); @@ -579,7 +619,7 @@ async function runHeadlessOnce( ); } options.command = "new-milestone"; - options.auto = true; + options.chainAutonomous = true; options.contextText = buildAutoBootstrapContext(process.cwd()); } } @@ -591,11 +631,11 @@ async function runHeadlessOnce( options.timeout = 600_000; // 10 minutes } - // auto-mode sessions are long-running (minutes to hours) with their own internal + // autonomous mode sessions are long-running (minutes to hours) with their own internal // per-unit timeout via auto-supervisor. Disable the overall timeout unless the // user explicitly set --timeout. - const isAutoMode = options.command === "autonomous"; - const wasRequestedAutoMode = requestedCommand === "autonomous"; + const isAutonomousCommand = options.command === "autonomous"; + const wasRequestedAutonomousCommand = requestedCommand === "autonomous"; // discuss and plan are multi-turn: they involve multiple question rounds, // codebase scanning, and artifact writing before the workflow completes (#3547). const isMultiTurnCommand = @@ -612,7 +652,7 @@ async function runHeadlessOnce( options.supervised = false; } - if (isAutoMode && options.timeout === 300_000) { + if (isAutonomousCommand && options.timeout === 300_000) { options.timeout = 0; } @@ -834,7 +874,7 @@ async function runHeadlessOnce( let blocked = false; let completed = false; let exitCode = 0; - let milestoneReady = false; // tracks "Milestone X ready." for auto-chaining + let milestoneReady = false; // tracks "Milestone X ready." for autonomous chaining let timedOut = false; // true only when the overall timeout timer fires // Rolling buffer for milestone-ready detection across split streaming deltas. // Capped at 200 chars — long enough to bridge any realistic delta boundary. @@ -878,7 +918,7 @@ async function runHeadlessOnce( let inThinkingBlock = false; // ─── Structured trace state ─────────────────────────────────────────────── - // Lazy-init: traces only created for auto-mode and new-milestone+auto. + // Lazy-init: traces only created for autonomous mode and new-milestone+autonomous. // Uses maybeStartTrace() — not called upfront so we pay zero cost when disabled. const cwd = process.cwd(); @@ -890,7 +930,7 @@ async function runHeadlessOnce( // Tracks pending tool_execution_start for which we haven't seen toolName yet const pendingToolSpans = new Map<string, ReturnType<typeof startToolSpan>>(); - /** Lazily initialize trace when entering auto-mode. Idempotent. */ + /** Lazily initialize trace when entering autonomous mode. Idempotent. */ function maybeStartTrace(sessionId?: string): void { if (traceActive) return; if (!isTraceEnabled()) return; @@ -1194,7 +1234,7 @@ async function runHeadlessOnce( // - new-milestone: bounded creative task; 120s buffer for LLM thinking // between bootstrap steps. (NEW_MILESTONE_IDLE_TIMEOUT_MS) // - Multi-turn (auto, next, discuss, plan): NOT a completion detector — - // those signal done via "auto-mode stopped" terminal notifications, + // those signal done via "autonomous mode stopped" terminal notifications, // and child-process exit catches crashes. The idle timer here is a // deadlock BACKSTOP only: 30 minutes, long enough to never misfire on // legitimate LLM reasoning, short enough to recover from a real hang. @@ -1230,7 +1270,7 @@ async function runHeadlessOnce( // Precompute supervised response timeout const responseTimeout = options.responseTimeout ?? 30_000; - // Overall timeout (disabled when options.timeout === 0, e.g. auto-mode) + // Overall timeout (disabled when options.timeout === 0, e.g. autonomous mode) const timeoutTimer = options.timeout > 0 ? setTimeout(() => { @@ -1292,8 +1332,8 @@ async function runHeadlessOnce( if (toolCallId && isInteractiveHeadlessTool(toolName)) { interactiveToolCallIds.add(toolCallId); } - // Lazy-start trace on first real tool call in auto-mode - if (!traceActive && isAutoMode) { + // Lazy-start trace on first real tool call in autonomous mode + if (!traceActive && isAutonomousCommand) { maybeStartTrace(lastSessionId); } // Start a tool span if tracing is active @@ -1423,7 +1463,11 @@ async function runHeadlessOnce( // output and verbose log lines that could spuriously match the pattern). // Accumulate a rolling 200-char buffer so patterns split across two // consecutive deltas are still detected. - if (isNewMilestone && options.auto && ame?.type === "text_delta") { + if ( + isNewMilestone && + options.chainAutonomous && + ame?.type === "text_delta" + ) { const deltaText = String(ame?.delta ?? ame?.text ?? ""); if (deltaText) { milestoneDetectionBuffer = ( @@ -1565,7 +1609,7 @@ async function runHeadlessOnce( // Handle execution_complete (v2 structured completion) // Skip for multi-turn commands (auto, next) — their completion is detected via - // isTerminalNotification("Auto-mode stopped..."/"Step-mode stopped..."), not per-turn events + // isTerminalNotification("Autonomous mode stopped..."/"Assisted mode stopped..."), not per-turn events if ( eventObj.type === "execution_complete" && !completed && @@ -1593,7 +1637,7 @@ async function runHeadlessOnce( blocked = true; } - // Detect "Milestone X ready." for auto-mode chaining + // Detect "Milestone X ready." for autonomous mode chaining if (isMilestoneReadyNotification(eventObj)) { milestoneReady = true; } @@ -1607,8 +1651,8 @@ async function runHeadlessOnce( const message = String(eventObj.message ?? ""); observeHeadlessNotification(message); if ( - message.includes("Auto-mode resumed") || - message.includes("Step-mode resumed") || + message.includes("Autonomous mode resumed") || + message.includes("Assisted mode resumed") || (message.includes("[unit]") && message.includes("starting")) ) { providerAutoResumePending = false; @@ -1868,11 +1912,11 @@ async function runHeadlessOnce( await completionPromise; } - // Autonomous-mode chaining: if --auto and milestone creation succeeded, + // Autonomous mode chaining: if --autonomous and milestone creation succeeded, // send the canonical autonomous command. if ( isNewMilestone && - options.auto && + options.chainAutonomous && milestoneReady && !blocked && exitCode === EXIT_SUCCESS @@ -1883,13 +1927,13 @@ async function runHeadlessOnce( ); } - // Reset completion state for the auto-mode phase. - // Disable the overall timeout — auto-mode has its own internal supervisor. + // Reset completion state for the autonomous mode phase. + // Disable the overall timeout — autonomous mode has its own internal supervisor. if (timeoutTimer) clearTimeout(timeoutTimer); completed = false; milestoneReady = false; blocked = false; - const autoCompletionPromise = new Promise<void>((resolve) => { + const autonomousCompletionPromise = new Promise<void>((resolve) => { resolveCompletion = resolve; }); @@ -1904,7 +1948,7 @@ async function runHeadlessOnce( } if (exitCode === EXIT_SUCCESS || exitCode === EXIT_BLOCKED) { - await autoCompletionPromise; + await autonomousCompletionPromise; } } @@ -1925,7 +1969,7 @@ async function runHeadlessOnce( await client.stop(); const solverEvalRecord = - (isAutoMode || wasRequestedAutoMode) && timedOut + (isAutonomousCommand || wasRequestedAutonomousCommand) && timedOut ? await runHeadlessTimeoutSolverEval(process.cwd()) : null; diff --git a/src/help-text.ts b/src/help-text.ts index e9caf7bc4..f19af3227 100644 --- a/src/help-text.ts +++ b/src/help-text.ts @@ -227,7 +227,7 @@ const SUBCOMMAND_HELP: Record<string, string> = { "new-milestone flags:", " --context <path> Path to spec/PRD file (use '-' for stdin)", " --context-text <txt> Inline specification text", - " --auto Start autonomous mode after milestone creation", + " --autonomous Start autonomous mode after milestone creation", " --verbose Show tool calls in progress output", "", "Output formats:", @@ -246,7 +246,7 @@ const SUBCOMMAND_HELP: Record<string, string> = { " sf headless --resume abc123 autonomous Resume a prior session", " sf headless new-milestone --context spec.md Create milestone from file", " cat spec.md | sf headless new-milestone --context - From stdin", - " sf headless new-milestone --context spec.md --auto Create + auto-execute", + " sf headless new-milestone --context spec.md --autonomous Create + run autonomously", " sf headless --supervised autonomous Supervised orchestrator mode", " sf headless --answers answers.json autonomous With pre-supplied answers", " sf headless --events agent_end,extension_ui_request autonomous Filtered event stream", diff --git a/src/loader.ts b/src/loader.ts index 894af0c94..e74f02cfb 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -113,7 +113,7 @@ if ( try { const now = Date.now(); let passiveDueCount = 0; - let projectAutoDispatchDueCount = 0; + let projectAutonomousDispatchDueCount = 0; const schedulePaths = [ { path: join(process.cwd(), ".sf", "schedule.jsonl"), scope: "project" }, { @@ -149,10 +149,10 @@ if ( ) { if ( scope === "project" && - entry.auto_dispatch === true && + entry.autonomous_dispatch === true && (entry.kind === "command" || entry.kind === "prompt") ) { - projectAutoDispatchDueCount++; + projectAutonomousDispatchDueCount++; } else { passiveDueCount++; } @@ -164,9 +164,9 @@ if ( `[forge] ${passiveDueCount} passive scheduled item${passiveDueCount === 1 ? "" : "s"} due now. Manage: /sf schedule list\n`, ); } - if (projectAutoDispatchDueCount > 0) { + if (projectAutonomousDispatchDueCount > 0) { process.stderr.write( - `[forge] ${projectAutoDispatchDueCount} scheduled auto-dispatch item${projectAutoDispatchDueCount === 1 ? "" : "s"} due now; autonomous mode will consume project entries.\n`, + `[forge] ${projectAutonomousDispatchDueCount} scheduled autonomous dispatch item${projectAutonomousDispatchDueCount === 1 ? "" : "s"} due now; autonomous mode will consume project entries.\n`, ); } } catch { diff --git a/src/resources/extensions/bg-shell/process-manager.js b/src/resources/extensions/bg-shell/process-manager.js index 1b3799e5a..1b9c909ee 100644 --- a/src/resources/extensions/bg-shell/process-manager.js +++ b/src/resources/extensions/bg-shell/process-manager.js @@ -355,7 +355,7 @@ export function cleanupAll() { } /** * Kill all alive, non-persistent bg processes. - * Called between auto-mode units to prevent orphaned servers from + * Called between autonomous mode units to prevent orphaned servers from * keeping ports bound across task boundaries (#1209). */ export function killSessionProcesses() { diff --git a/src/resources/extensions/ollama/index.js b/src/resources/extensions/ollama/index.js index 775ceefa4..62e286ef2 100644 --- a/src/resources/extensions/ollama/index.js +++ b/src/resources/extensions/ollama/index.js @@ -123,7 +123,7 @@ export default function ollama(pi) { } else { await registerOllamaTools(pi); } - // In headless/auto mode, await the probe so the fallback resolver can + // In headless/autonomous mode, await the probe so the fallback resolver can // see Ollama before the first LLM call (#3531 race condition). // In interactive mode, keep it async for fast startup. if (!ctx.hasUI) { diff --git a/src/resources/extensions/remote-questions/notify.js b/src/resources/extensions/remote-questions/notify.js index 0730e0b8c..fabd86ecb 100644 --- a/src/resources/extensions/remote-questions/notify.js +++ b/src/resources/extensions/remote-questions/notify.js @@ -2,7 +2,7 @@ * Remote Notifications — one-way alert delivery to configured channels. * * Sends informational messages to Slack/Discord/Telegram without expecting - * a reply. Used for auto-mode events like secrets-required pauses where + * a reply. Used for autonomous mode events like secrets-required pauses where * the user needs to be notified but should NOT send sensitive data back * through the channel. */ diff --git a/src/resources/extensions/sf/activity-log.js b/src/resources/extensions/sf/activity-log.js index 4117ee5e2..8126712fa 100644 --- a/src/resources/extensions/sf/activity-log.js +++ b/src/resources/extensions/sf/activity-log.js @@ -1,7 +1,7 @@ /** * SF Activity Log — Save raw chat sessions to .sf/activity/ * - * Before each context wipe in auto-mode, dumps the full session + * Before each context wipe in autonomous mode, dumps the full session * as JSONL. No formatting, no truncation, no information loss. * These are debug artifacts — only read when summaries aren't enough. * @@ -30,7 +30,7 @@ import { isAuditEnvelopeEnabled } from "./uok/audit-toggle.js"; const activityLogState = new Map(); /** * Clear accumulated activity log state (#611). - * Call when auto-mode stops to prevent unbounded memory growth + * Call when autonomous mode stops to prevent unbounded memory growth * from lastSnapshotKeyByUnit maps accumulating across units. */ export function clearActivityLogState() { @@ -154,7 +154,7 @@ export function saveActivityLog(ctx, basePath, unitType, unitId) { } return filePath; } catch (e) { - // Don't let logging failures break auto-mode + // Don't let logging failures break autonomous mode void e; return null; } diff --git a/src/resources/extensions/sf/auto-artifact-paths.js b/src/resources/extensions/sf/auto-artifact-paths.js index 75ef1fd60..a677818ad 100644 --- a/src/resources/extensions/sf/auto-artifact-paths.js +++ b/src/resources/extensions/sf/auto-artifact-paths.js @@ -1,4 +1,4 @@ -// SF Auto-mode — Artifact Path Resolution +// SF Autonomous mode — Artifact Path Resolution // // resolveExpectedArtifactPath and diagnoseExpectedArtifact moved here from // auto-recovery.ts (Phase 5 dead-code cleanup). The artifact verification diff --git a/src/resources/extensions/sf/auto-budget.js b/src/resources/extensions/sf/auto-budget.js index 5c3713953..d7414cb31 100644 --- a/src/resources/extensions/sf/auto-budget.js +++ b/src/resources/extensions/sf/auto-budget.js @@ -1,5 +1,5 @@ /** - * Budget alert level tracking and enforcement for auto-mode. + * Budget alert level tracking and enforcement for autonomous mode. * Pure functions — no module state or side effects. */ export function getBudgetAlertLevel(budgetPct) { diff --git a/src/resources/extensions/sf/auto-dashboard.js b/src/resources/extensions/sf/auto-dashboard.js index f61d36e41..277737a96 100644 --- a/src/resources/extensions/sf/auto-dashboard.js +++ b/src/resources/extensions/sf/auto-dashboard.js @@ -1,5 +1,5 @@ /** - * Auto-mode Dashboard — progress widget rendering, elapsed time formatting, + * Autonomous mode Dashboard — progress widget rendering, elapsed time formatting, * unit description helpers, and slice progress caching. * * Pure functions that accept specific parameters — no module-level globals @@ -243,7 +243,7 @@ export function describeNextUnit(state) { } } // ─── Elapsed Time Formatting ────────────────────────────────────────────────── -/** Format elapsed time since auto-mode started */ +/** Format elapsed time since autonomous mode started */ export function formatAutoElapsed(autoStartTime) { if (!autoStartTime || autoStartTime <= 0 || !Number.isFinite(autoStartTime)) return ""; @@ -401,7 +401,7 @@ function getLastCommit(basePath) { } // ─── Footer Factory ─────────────────────────────────────────────────────────── /** - * Footer factory used by auto-mode. + * Footer factory used by autonomous mode. * Keep footer minimal but preserve extension status context from setStatus(). */ function sanitizeFooterStatus(text) { diff --git a/src/resources/extensions/sf/auto-direct-dispatch.js b/src/resources/extensions/sf/auto-direct-dispatch.js index 4657fd418..2051a862d 100644 --- a/src/resources/extensions/sf/auto-direct-dispatch.js +++ b/src/resources/extensions/sf/auto-direct-dispatch.js @@ -54,7 +54,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) { ); return; } - // When require_slice_discussion is enabled, pause auto-mode before + // When require_slice_discussion is enabled, pause autonomous mode before // each new slice so the user can discuss requirements first (#789). const sliceContextFile = resolveSliceFile(base, mid, sid, "CONTEXT"); const requireDiscussion = diff --git a/src/resources/extensions/sf/auto-dispatch.js b/src/resources/extensions/sf/auto-dispatch.js index 5492ae409..cb32db55d 100644 --- a/src/resources/extensions/sf/auto-dispatch.js +++ b/src/resources/extensions/sf/auto-dispatch.js @@ -67,9 +67,9 @@ import { resolveModelWithFallbacksForUnit } from "./preferences-models.js"; import { buildScheduledPrompt, executeProjectScheduleCommand, - isAutoDispatchScheduleEntry, + isAutonomousDispatchScheduleEntry, markProjectScheduleDone, -} from "./schedule/schedule-auto-dispatch.js"; +} from "./schedule/schedule-autonomous-dispatch.js"; import { createScheduleStore } from "./schedule/schedule-store.js"; import { getMilestone, @@ -259,7 +259,7 @@ function findMissingSummaries(basePath, mid) { const MAX_REWRITE_ATTEMPTS = 3; // ─── Disk-persisted rewrite attempt counter ────────────────────────────────── // The counter must survive session restarts (crash recovery, pause/resume, -// step-mode). Storing it on the in-memory session object caused the circuit +// assisted mode). Storing it on the in-memory session object caused the circuit // breaker to never trip — see https://github.com/singularity-forge/sf-run/issues/2203 function rewriteCountPath(basePath) { return join(sfRoot(basePath), "runtime", "rewrite-count.json"); @@ -518,15 +518,17 @@ export async function enhanceUnitRankingWithMemory(units, baseScores = {}) { // ─── Rules ──────────────────────────────────────────────────────────────── export const DISPATCH_RULES = [ { - name: "schedule auto-dispatch", + name: "schedule autonomous dispatch", match: async ({ basePath }) => { try { const store = createScheduleStore(basePath); const due = store.findDue("project", new Date()); - const autoDispatch = due.filter(isAutoDispatchScheduleEntry); - if (autoDispatch.length === 0) return null; + const autonomousDispatch = due.filter( + isAutonomousDispatchScheduleEntry, + ); + if (autonomousDispatch.length === 0) return null; - const entry = autoDispatch[0]; + const entry = autonomousDispatch[0]; if (entry.kind === "command") { const result = executeProjectScheduleCommand(basePath, entry); if (result.ok) { @@ -1745,6 +1747,8 @@ function emitDispatchEnvelope(ctx, action) { action: envelopeAction, unitType, unitId, + runControl: ctx.runControl, + permissionProfile: ctx.permissionProfile, reasonCode, summary, evidence: { diff --git a/src/resources/extensions/sf/auto-model-selection.js b/src/resources/extensions/sf/auto-model-selection.js index ea5de0b69..3ca6a3dd4 100644 --- a/src/resources/extensions/sf/auto-model-selection.js +++ b/src/resources/extensions/sf/auto-model-selection.js @@ -1,5 +1,5 @@ /** - * Model selection and dynamic routing for auto-mode unit dispatch. + * Model selection and dynamic routing for autonomous mode unit dispatch. * Handles complexity-based routing, model resolution across providers, * and fallback chains. */ @@ -91,7 +91,7 @@ function restoreToolBaseline(pi) { const key = pi; const baseline = TOOL_BASELINE.get(key); if (baseline === undefined) { - // First call: capture the canonical pre-dispatch tool set. At auto-mode + // First call: capture the canonical pre-dispatch tool set. At autonomous mode // start the active set has not yet been narrowed for any provider. // Guarded against test fakes that omit getActiveTools — record an empty // baseline so subsequent calls don't keep re-probing. @@ -201,7 +201,7 @@ function matchesBareModelId(candidateId, requestedId) { } /** * Resolve preferred model configuration for a unit type from preferences or dynamic routing. - * Returns undefined if no explicit config and auto-mode is disabled or flat-rate provider detected. + * Returns undefined if no explicit config and autonomous mode is disabled or flat-rate provider detected. */ export function resolvePreferredModelConfig( unitType, @@ -265,11 +265,11 @@ export async function selectAndApplyModel( autoModeStartModel, retryContext, /** When false (interactive/guided-flow), skip dynamic routing and use the session model. - * Dynamic routing only applies in auto-mode where cost optimization is expected. (#3962) */ + * 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. */ sessionModelOverride, - /** Thinking level captured at auto-mode start and re-applied after model swaps. */ + /** Thinking level captured at autonomous mode start and re-applied after model swaps. */ autoModeStartThinkingLevel, ) { // ── Restore active-tool baseline before policy evaluation (#4959, #4681, #4850) ── @@ -347,7 +347,7 @@ export async function selectAndApplyModel( const modelPolicyTurnId = `${unitType}:${unitId}`; let policyAllowedModelKeys = null; // ─── Dynamic Model Routing ───────────────────────────────────────── - // Dynamic routing (complexity-based downgrading) only applies in auto-mode. + // Dynamic routing (complexity-based downgrading) only applies in autonomous mode. // Interactive/guided-flow dispatches use the user's session model directly, // respecting their /model selection without silent downgrades (#3962). const routingConfig = resolveDynamicRoutingConfig(); @@ -756,7 +756,7 @@ export async function selectAndApplyModel( } } else if (autoModeStartModel) { // No model preference for this unit type — re-apply the model captured - // at auto-mode start to prevent bleed from shared global settings.json (#650). + // at autonomous mode start to prevent bleed from shared global settings.json (#650). const availableModels = filterModelsByProviderModelAllow( ctx.modelRegistry .getAvailable() diff --git a/src/resources/extensions/sf/auto-post-unit.js b/src/resources/extensions/sf/auto-post-unit.js index 0f4ca46b6..abc62d6a9 100644 --- a/src/resources/extensions/sf/auto-post-unit.js +++ b/src/resources/extensions/sf/auto-post-unit.js @@ -842,7 +842,7 @@ export async function postUnitPreVerification(pctx, opts) { error: err instanceof Error ? err.message : String(err), }); // Non-conflict failures (dirty main, rev-walk error, etc.) can - // leave the checkout in an unexpected state. Stop auto-mode so + // leave the checkout in an unexpected state. Stop autonomous mode so // the next slice doesn't dispatch on top of it. const { stopAuto } = await import("./auto.js"); await stopAuto(ctx, undefined, `slice-merge-error on ${sid}`); @@ -1144,7 +1144,7 @@ export async function postUnitPreVerification(pctx, opts) { unitId: s.currentUnit.id, }); ctx.ui.notify( - `${s.currentUnit.type} ${s.currentUnit.id} is waiting for your input — pausing auto-mode instead of retrying the missing artifact.`, + `${s.currentUnit.type} ${s.currentUnit.id} is waiting for your input — pausing autonomous mode instead of retrying the missing artifact.`, "info", ); s.lastToolInvocationError = null; @@ -1218,14 +1218,14 @@ export async function postUnitPreVerification(pctx, opts) { } // #2883/#3595: If the artifact is missing because the tool invocation // failed (malformed JSON) or was skipped (queued user message), retrying - // will produce the same failure. Pause auto-mode instead of looping. + // will produce the same failure. Pause autonomous mode instead of looping. if (s.lastToolInvocationError) { const isUserSkip = /queued user message/i.test( s.lastToolInvocationError, ); const errMsg = isUserSkip - ? `Tool skipped for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Queued user message interrupted the turn — pausing auto-mode.` - : `Tool invocation failed for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Structured argument generation failed — pausing auto-mode.`; + ? `Tool skipped for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Queued user message interrupted the turn — pausing autonomous mode.` + : `Tool invocation failed for ${s.currentUnit.type}: ${s.lastToolInvocationError}. Structured argument generation failed — pausing autonomous mode.`; debugLog("postUnit", { phase: "tool-invocation-error-pause", unitType: s.currentUnit.type, @@ -1252,7 +1252,7 @@ export async function postUnitPreVerification(pctx, opts) { // the stub SUMMARY has no recovery value (milestone is terminal), // it does not update DB status (so deriveState never advances), // and it fools stopAuto's presence check into merging a milestone - // that was never legitimately completed. Pause auto-mode with a + // that was never legitimately completed. Pause autonomous mode with a // clear single failure signal and preserve the worktree branch. if (s.currentUnit.type === "complete-milestone") { debugLog("postUnit", { @@ -1331,7 +1331,7 @@ export async function postUnitPreVerification(pctx, opts) { * * Returns: * - "continue" — proceed to sidecar drain / normal dispatch - * - "step-wizard" — step mode, show wizard instead + * - "step-wizard" — assisted mode, show wizard instead * - "stopped" — stopAuto was called */ export async function postUnitPostVerification(pctx) { @@ -1587,7 +1587,7 @@ export async function postUnitPostVerification(pctx) { const stopCapture = pending.find((c) => STOP_PATTERN.test(c.text.trim())); if (stopCapture) { ctx.ui.notify( - `Stop directive detected in pending capture ${stopCapture.id}: "${stopCapture.text}" — pausing auto-mode.`, + `Stop directive detected in pending capture ${stopCapture.id}: "${stopCapture.text}" — pausing autonomous mode.`, "warning", ); debugLog("postUnit", { phase: "fast-stop", captureId: stopCapture.id }); @@ -1749,7 +1749,7 @@ export async function postUnitPostVerification(pctx) { durationMs: result.durationMs, }); } catch (preExecError) { - // Fail-closed: if runPreExecutionChecks throws, pause auto-mode instead of silently continuing + // Fail-closed: if runPreExecutionChecks throws, pause autonomous mode instead of silently continuing const errorMessage = preExecError instanceof Error ? preExecError.message @@ -1913,8 +1913,8 @@ export async function postUnitPostVerification(pctx) { debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) }); } } - // Step mode → show wizard instead of dispatch. - // Without this notify(), /sf in step mode finishes a unit and silently + // 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. if (s.stepMode) { try { diff --git a/src/resources/extensions/sf/auto-prompts.js b/src/resources/extensions/sf/auto-prompts.js index 712e72df5..5575c6345 100644 --- a/src/resources/extensions/sf/auto-prompts.js +++ b/src/resources/extensions/sf/auto-prompts.js @@ -1,5 +1,5 @@ /** - * Auto-mode Prompt Builders — construct dispatch prompts for each unit type. + * Autonomous mode Prompt Builders — construct dispatch prompts for each unit type. * * Pure async functions that load templates and inline file content. No module-level * state, no globals — every dependency is passed as a parameter or imported as a @@ -1751,7 +1751,7 @@ export async function buildExecuteTaskPrompt( })(); // SF ADR-011 P2: when the feature is enabled, teach the executor that it can // surface non-obvious choices via the `escalation` field on sf_task_complete - // rather than silently picking. Auto-mode auto-accepts the recommendation + // rather than silently picking. Autonomous mode auto-accepts the recommendation // (see phases.escalation_auto_accept), so this is low-cost overhead — but // it produces an audit trail and a hard constraint for downstream tasks. // When the feature is off, the field is silently dropped, so we omit the @@ -1774,7 +1774,7 @@ export async function buildExecuteTaskPrompt( "}", "```", "", - "Provide 2–4 options with concrete tradeoffs. The recommendation must reference one of the option ids. Auto-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 auto-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 `/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).", ].join("\n") : ""; // Apply knowledge injection for this task context diff --git a/src/resources/extensions/sf/auto-recovery.js b/src/resources/extensions/sf/auto-recovery.js index db8581557..0cc305f56 100644 --- a/src/resources/extensions/sf/auto-recovery.js +++ b/src/resources/extensions/sf/auto-recovery.js @@ -1,5 +1,5 @@ /** - * Auto-mode Recovery — artifact resolution, verification, blocker placeholders, + * Autonomous mode Recovery — artifact resolution, verification, blocker placeholders, * skip artifacts, merge state reconciliation, * self-heal runtime records, and loop remediation steps. * @@ -468,13 +468,13 @@ export function writeBlockerPlaceholder(unitType, unitId, base, reason) { const dir = dirname(absPath); if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); const content = [ - `# BLOCKER — auto-mode recovery failed`, + `# BLOCKER — autonomous mode recovery failed`, ``, `Unit \`${unitType}\` for \`${unitId}\` failed to produce this artifact after idle recovery exhausted all retries.`, ``, `**Reason**: ${reason}`, ``, - `This placeholder was written by auto-mode so the pipeline can advance.`, + `This placeholder was written by autonomous mode so the pipeline can advance.`, `Review and replace this file before relying on downstream artifacts.`, ].join("\n"); writeFileSync(absPath, content, "utf-8"); @@ -662,7 +662,7 @@ export function reconcileMergeState(basePath, ctx) { // Code conflicts present — fail safe and preserve any manual resolution // work instead of discarding it with merge --abort/reset --hard. ctx.ui.notify( - "Detected leftover merge state with unresolved code conflicts. Auto-mode will pause without modifying the worktree so manual conflict resolution is preserved.", + "Detected leftover merge state with unresolved code conflicts. Autonomous mode will pause without modifying the worktree so manual conflict resolution is preserved.", "error", ); return "blocked"; @@ -682,7 +682,7 @@ export function buildLoopRemediationSteps(unitType, unitId, base) { if (!mid || !sid || !tid) break; return [ ` 1. Run \`sf undo-task ${tid}\` to reset the task state`, - ` 2. Resume auto-mode — it will re-execute the task`, + ` 2. Resume autonomous mode — it will re-execute the task`, ` 3. If the task keeps failing, run \`sf recover\` to rebuild DB state from disk`, ].join("\n"); } @@ -696,14 +696,14 @@ export function buildLoopRemediationSteps(unitType, unitId, base) { return [ ` 1. Write ${artifactRel} manually (or with the LLM in interactive mode)`, ` 2. Run \`sf recover\` to rebuild DB state from disk`, - ` 3. Resume auto-mode`, + ` 3. Resume autonomous mode`, ].join("\n"); } case "complete-slice": { if (!mid || !sid) break; return [ ` 1. Run \`sf reset-slice ${sid}\` to reset the slice and all its tasks`, - ` 2. Resume auto-mode — it will re-execute incomplete tasks and re-complete the slice`, + ` 2. Resume autonomous mode — it will re-execute incomplete tasks and re-complete the slice`, ` 3. If the slice keeps failing, run \`sf recover\` to rebuild DB state from disk`, ].join("\n"); } @@ -713,7 +713,7 @@ export function buildLoopRemediationSteps(unitType, unitId, base) { return [ ` 1. Write ${artifactRel} with verdict: pass`, ` 2. Run \`sf recover\` to rebuild DB state from disk`, - ` 3. Resume auto-mode`, + ` 3. Resume autonomous mode`, ].join("\n"); } default: diff --git a/src/resources/extensions/sf/auto-runaway-guard.js b/src/resources/extensions/sf/auto-runaway-guard.js index ce70e4624..27078c462 100644 --- a/src/resources/extensions/sf/auto-runaway-guard.js +++ b/src/resources/extensions/sf/auto-runaway-guard.js @@ -1,5 +1,5 @@ /** - * Diagnostic budget guard for unusually long auto-mode units. + * Diagnostic budget guard for unusually long autonomous mode units. * * This is intentionally not a blind tool-count kill switch. It gives the agent * explicit turns to explain whether the unit is legitimately large, stuck, or diff --git a/src/resources/extensions/sf/auto-runtime-state.js b/src/resources/extensions/sf/auto-runtime-state.js index 8513bb4fa..6283129ac 100644 --- a/src/resources/extensions/sf/auto-runtime-state.js +++ b/src/resources/extensions/sf/auto-runtime-state.js @@ -1,4 +1,4 @@ -// SF auto-mode runtime state +// SF autonomous mode runtime state import { AutoSession } from "./auto/session.js"; import { isDeterministicPolicyError, diff --git a/src/resources/extensions/sf/auto-start.js b/src/resources/extensions/sf/auto-start.js index 078f36cfa..e0a342381 100644 --- a/src/resources/extensions/sf/auto-start.js +++ b/src/resources/extensions/sf/auto-start.js @@ -1,5 +1,5 @@ /** - * Auto-mode bootstrap — fresh-start initialization path. + * Autonomous mode bootstrap — fresh-start initialization path. * * Git/state bootstrap, crash lock detection, debug init, worktree recovery, * guided flow gate, session init, worktree lifecycle, DB lifecycle, @@ -109,7 +109,7 @@ import { import { emitWorktreeOrphaned } from "./worktree-telemetry.js"; /** - * Bootstrap a fresh auto-mode session. Handles everything from git init + * Bootstrap a fresh autonomous mode session. Handles everything from git init * through secrets collection, returning when ready for the first * dispatchNextUnit call. * @@ -148,7 +148,7 @@ export async function openProjectDbIfPresent(basePath) { * After a milestone completes, the teardown step (merge branch → main, * delete branch, remove worktree) runs as a post-completion engine step. * If the session ends between completion and teardown, the branch and - * worktree are orphaned — the DB says "complete" so auto-mode won't + * worktree are orphaned — the DB says "complete" so autonomous mode won't * re-enter the milestone, and the teardown is never retried. * * This audit runs on every fresh bootstrap to catch that gap: @@ -197,7 +197,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode) { const milestone = getMilestone(milestoneId); if (!milestone) continue; // #4762 — in-progress milestone branch with unmerged commits ahead of - // main. This is the pre-completion orphan case: auto-mode exited without + // main. This is the pre-completion orphan case: autonomous mode exited without // completing the milestone (pause, stop, crash, merge error, blocker) and // work is stranded on the branch or in the worktree. Data safety first: // we never delete or touch; we just surface a warning so the user knows @@ -364,7 +364,7 @@ export async function bootstrapAutoSession( // Exception (#4122): when the session provider is a custom provider declared // in ~/.sf/agent/models.json (Ollama, vLLM, OpenAI-compatible proxy, etc.), // PREFERENCES.md is skipped entirely. PREFERENCES.md cannot reference custom - // providers, so honoring it would silently reroute auto-mode to a built-in + // providers, so honoring it would silently reroute autonomous mode to a built-in // provider the user is not logged into and surface as "Not logged in · Please // run /login" before pausing and resetting to claude-code/claude-sonnet-4-6. const manualSessionOverride = getSessionModelOverride( @@ -606,7 +606,7 @@ export async function bootstrapAutoSession( // Survivor branch exists but milestone still needs discussion (#1726): // The worktree/branch was created but the milestone only has CONTEXT-DRAFT.md. // Route to the interactive discussion handler instead of falling through to - // auto-mode, which would immediately stop with "needs discussion". + // autonomous mode, which would immediately stop with "needs discussion". if (decideSurvivorAction(hasSurvivorBranch, state.phase) === "discuss") { const { showWorkflowEntry } = await import("./guided-flow.js"); await showWorkflowEntry(ctx, pi, base, { step: requestedStepMode }); @@ -661,7 +661,7 @@ export async function bootstrapAutoSession( ); return releaseLockAndReturn(); } - // Auto mode: autonomously map the codebase and create milestones + // Autonomous mode: map the codebase and create milestones // without waiting for user answers. Uses discuss-headless prompt. ctx.ui.notify( "No milestones found. Bootstrapping from repo docs and source inventory.", @@ -678,7 +678,7 @@ export async function bootstrapAutoSession( const bootstrapContext = buildAutoBootstrapContext(base); const nextId = bootstrapNewMilestone(base); await dispatchNewMilestoneDiscuss(ctx, pi, base, nextId, { - auto: true, + autonomousBootstrap: true, preamble: injectTodoContext(base, bootstrapContext), }); invalidateAllCaches(); @@ -689,7 +689,7 @@ export async function bootstrapAutoSession( "warning", ); await dispatchNewMilestoneDiscuss(ctx, pi, base, nextId, { - auto: true, + autonomousBootstrap: true, preamble: injectTodoContext( base, [ @@ -699,7 +699,7 @@ export async function bootstrapAutoSession( bootstrapContext, "Start the roadmap planning session now: build project knowledge, run the planning meeting, and persist artifacts.", "Do not stop after reflection. At minimum write CONTEXT-DRAFT with evidence and open questions.", - "If confidence is high enough, write CONTEXT and call sf_plan_milestone so auto-mode can continue.", + "If confidence is high enough, write CONTEXT and call sf_plan_milestone so autonomous mode can continue.", ].join("\n"), ), }); @@ -732,7 +732,7 @@ export async function bootstrapAutoSession( "warning", ); await dispatchNewMilestoneDiscuss(ctx, pi, base, repairId, { - auto: true, + autonomousBootstrap: true, preamble: injectTodoContext( base, [ @@ -742,7 +742,7 @@ export async function bootstrapAutoSession( bootstrapContext, "Reuse this milestone ID. Do not create a new milestone for the same bootstrap work.", "Run the roadmap planning session now and persist CONTEXT or CONTEXT-DRAFT at minimum.", - "If confidence is high enough, write CONTEXT and call sf_plan_milestone so auto-mode can continue.", + "If confidence is high enough, write CONTEXT and call sf_plan_milestone so autonomous mode can continue.", ].join("\n"), ), }); @@ -816,7 +816,7 @@ export async function bootstrapAutoSession( await import("./guided-flow.js"); const bootstrapContext = buildAutoBootstrapContext(base); await dispatchNewMilestoneDiscuss(ctx, pi, base, mid, { - auto: true, + autonomousBootstrap: true, preamble: injectTodoContext( base, [ @@ -1008,12 +1008,12 @@ export async function bootstrapAutoSession( } // Gate: abort bootstrap if the DB file exists but the provider is // still unavailable after both open attempts above. Without this, - // auto-mode starts but every sf_task_complete / sf_slice_complete + // autonomous mode starts but every sf_task_complete / sf_slice_complete // call returns "db_unavailable", triggering artifact-retry which // re-dispatches the same task — producing an infinite loop (#2419). if (existsSync(sfDbPath) && !isDbAvailable()) { ctx.ui.notify( - "SQLite database exists but failed to open. Auto-mode cannot proceed without a working database provider. " + + "SQLite database exists but failed to open. Autonomous mode cannot proceed without a working database provider. " + "Check for corrupt sf.db or missing native SQLite bindings.", "error", ); @@ -1070,7 +1070,7 @@ export async function bootstrapAutoSession( // Hide sf-health during AUTO — sf-progress is the single source of truth // for last-commit / cost / health signal while auto is running. safeSetWidget(ctx, "sf-health", undefined); - const modeLabel = s.stepMode ? "Step-mode" : "Auto-mode"; + const modeLabel = s.stepMode ? "Assisted mode" : "Autonomous mode"; const pendingCount = (state.registry ?? []).filter( (m) => m.status !== "complete" && m.status !== "parked", ).length; diff --git a/src/resources/extensions/sf/auto-supervisor.js b/src/resources/extensions/sf/auto-supervisor.js index 5305e190c..8bb54528a 100644 --- a/src/resources/extensions/sf/auto-supervisor.js +++ b/src/resources/extensions/sf/auto-supervisor.js @@ -1,5 +1,5 @@ /** - * Auto-mode Supervisor — signal handling and working-tree activity detection. + * Autonomous mode Supervisor — signal handling and working-tree activity detection. * * Pure functions — no module-level globals or AutoContext dependency. */ diff --git a/src/resources/extensions/sf/auto-timeout-recovery.js b/src/resources/extensions/sf/auto-timeout-recovery.js index c92e3d5e5..398432ad2 100644 --- a/src/resources/extensions/sf/auto-timeout-recovery.js +++ b/src/resources/extensions/sf/auto-timeout-recovery.js @@ -1,5 +1,5 @@ /** - * Timeout recovery logic for auto-mode units. + * Timeout recovery logic for autonomous mode units. * Handles idle and hard timeout recovery with escalation, steering messages, * and blocker placeholder generation. */ @@ -132,7 +132,7 @@ export async function recoverTimedOutUnit( recovery: status, }); ctx.ui.notify( - `${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed on disk. Continuing auto-mode. (attempt ${attemptNumber})`, + `${reason === "idle" ? "Idle" : "Timeout"} recovery: ${unitType} ${unitId} already completed on disk. Continuing autonomous mode. (attempt ${attemptNumber})`, "info", ); unitRecoveryCount.delete(recoveryKey); @@ -263,7 +263,7 @@ export async function recoverTimedOutUnit( "If you are truly blocked, write the file with a BLOCKER section explaining why.", ] : [ - `**${reason === "idle" ? "IDLE" : "HARD TIMEOUT"} RECOVERY — stay in auto-mode.**`, + `**${reason === "idle" ? "IDLE" : "HARD TIMEOUT"} RECOVERY — stay in autonomous mode.**`, `You are still executing ${unitType} ${unitId}.`, `Recovery attempt ${recoveryAttempts + 1} of ${maxRecoveryAttempts}.`, `Expected durable output: ${status.expected}.`, diff --git a/src/resources/extensions/sf/auto-timers.js b/src/resources/extensions/sf/auto-timers.js index c7c7ed308..a89e35c57 100644 --- a/src/resources/extensions/sf/auto-timers.js +++ b/src/resources/extensions/sf/auto-timers.js @@ -419,7 +419,7 @@ export function startUnitSupervision(sctx) { }, ); ctx.ui.notify( - `Unit ${unitType} ${unitId} made no meaningful progress for ${supervisor.idle_timeout_minutes}min. Pausing auto-mode.`, + `Unit ${unitType} ${unitId} made no meaningful progress for ${supervisor.idle_timeout_minutes}min. Pausing autonomous mode.`, "warning", ); await pauseAuto(ctx, pi); @@ -480,7 +480,7 @@ export function startUnitSupervision(sctx) { ); if (recovery === "recovered") return; ctx.ui.notify( - `Unit ${unitType} ${unitId} exceeded ${supervisor.hard_timeout_minutes}min hard timeout. Pausing auto-mode.`, + `Unit ${unitType} ${unitId} exceeded ${supervisor.hard_timeout_minutes}min hard timeout. Pausing autonomous mode.`, "warning", ); await pauseAuto(ctx, pi); diff --git a/src/resources/extensions/sf/auto-tool-tracking.js b/src/resources/extensions/sf/auto-tool-tracking.js index decb15bfd..43eeef811 100644 --- a/src/resources/extensions/sf/auto-tool-tracking.js +++ b/src/resources/extensions/sf/auto-tool-tracking.js @@ -1,5 +1,5 @@ /** - * In-flight tool call tracking for auto-mode idle detection. + * In-flight tool call tracking for autonomous mode idle detection. * Tracks which tool calls are currently executing so the idle watchdog * can distinguish "waiting for tool completion" from "truly idle". */ @@ -132,7 +132,7 @@ export const DETERMINISTIC_POLICY_ERROR_STRINGS = [ /** * Returns true if the error message indicates a deterministic policy gate * blocked the tool call before execution. Retrying the same unit without - * changing behavior will hit the same gate, so auto-mode should write a + * changing behavior will hit the same gate, so autonomous mode should write a * blocker placeholder instead of re-dispatching (#4973). */ export function isDeterministicPolicyError(errorMsg) { diff --git a/src/resources/extensions/sf/auto-verification.js b/src/resources/extensions/sf/auto-verification.js index 29d02df9d..2712fed4b 100644 --- a/src/resources/extensions/sf/auto-verification.js +++ b/src/resources/extensions/sf/auto-verification.js @@ -1,5 +1,5 @@ /** - * Post-unit verification gate for auto-mode. + * Post-unit verification gate for autonomous mode. * * Runs typecheck/lint/test checks, captures runtime errors, performs * dependency audits, handles auto-fix retry logic, and writes diff --git a/src/resources/extensions/sf/auto-worktree.js b/src/resources/extensions/sf/auto-worktree.js index d007a45dc..275e1c53c 100644 --- a/src/resources/extensions/sf/auto-worktree.js +++ b/src/resources/extensions/sf/auto-worktree.js @@ -1,9 +1,9 @@ /** - * SF Auto-Worktree -- lifecycle management for auto-mode worktrees. + * SF Auto-Worktree -- lifecycle management for autonomous mode worktrees. * - * Auto-mode creates worktrees with `milestone/<MID>` branches (distinct from + * Autonomous mode creates worktrees with `milestone/<MID>` branches (distinct from * manual `/worktree` which uses `worktree/<name>` branches). This module - * manages create, enter, detect, and teardown for auto-mode worktrees. + * manages create, enter, detect, and teardown for autonomous mode worktrees. */ import { execFileSync } from "node:child_process"; import { randomUUID } from "node:crypto"; @@ -972,7 +972,7 @@ export function autoWorktreeBranch(milestoneId) { * Forward-merge plan checkbox state from the project root into a freshly * re-attached worktree (#778). * - * When auto-mode stops via crash (not graceful stop), the milestone branch + * When autonomous mode stops via crash (not graceful stop), the milestone branch * HEAD may be behind the filesystem state at the project root because * syncStateToProjectRoot() runs after every task completion but the final * git commit may not have happened before the crash. On restart the worktree @@ -1088,7 +1088,7 @@ export function createAutoWorktree(basePath, milestoneId) { ); } const branch = autoWorktreeBranch(milestoneId); - // Check if the milestone branch already exists — it survives auto-mode + // Check if the milestone branch already exists — it survives autonomous mode // stop/pause and contains committed work from prior sessions. If it exists, // re-attach the worktree to it WITHOUT resetting. Only create a fresh branch // from the integration branch when no prior work exists. @@ -1120,7 +1120,7 @@ export function createAutoWorktree(basePath, milestoneId) { // Copy .sf/ planning artifacts from the source repo into the new worktree. // Worktrees are fresh git checkouts — untracked files don't carry over. // Planning artifacts may be untracked if the project's .gitignore had a - // blanket .sf/ rule (pre-v2.14.0). Without this copy, auto-mode loops + // blanket .sf/ rule (pre-v2.14.0). Without this copy, autonomous mode loops // on plan-slice because the plan file doesn't exist in the worktree. // // IMPORTANT: Skip when re-attaching to an existing branch (#759). @@ -1134,7 +1134,7 @@ export function createAutoWorktree(basePath, milestoneId) { // Re-attaching to an existing branch: forward-merge any plan checkpoint // state from the project root into the worktree (#778). // - // If auto-mode stopped via crash, the milestone branch HEAD may lag behind + // If autonomous mode stopped via crash, the milestone branch HEAD may lag behind // the project root filesystem because syncStateToProjectRoot() ran after // task completion but the auto-commit never fired. On restart the worktree // is re-created from the branch HEAD (which has [ ] for the crashed task), @@ -1169,7 +1169,7 @@ export function createAutoWorktree(basePath, milestoneId) { * Copies milestones/, DECISIONS.md, REQUIREMENTS.md, PROJECT.md, QUEUE.md, * STATE.md, KNOWLEDGE.md, and OVERRIDES.md. * Skips runtime files (auto.lock, metrics.json, etc.) and the worktrees/ dir. - * Best-effort — failures are non-fatal since auto-mode can recreate artifacts. + * Best-effort — failures are non-fatal since autonomous mode can recreate artifacts. */ function copyPlanningArtifacts(srcBase, wtPath) { const srcSf = join(srcBase, ".sf"); diff --git a/src/resources/extensions/sf/auto.js b/src/resources/extensions/sf/auto.js index 6f1daa365..2d6689d36 100644 --- a/src/resources/extensions/sf/auto.js +++ b/src/resources/extensions/sf/auto.js @@ -198,7 +198,7 @@ export { } from "./auto/session.js"; // ── ENCAPSULATION INVARIANT ───────────────────────────────────────────────── -// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts). +// ALL mutable autonomous mode state lives in the AutoSession class (auto/session.ts). // This file must NOT declare module-level `let` or `var` variables for state. // The single `s` instance below is the only mutable module-level binding. // @@ -309,18 +309,18 @@ export function shouldUseWorktreeIsolation() { /** Track dynamic routing decision for the current unit (for metrics) */ /** Queue of quick-task captures awaiting dispatch after triage resolution */ /** - * Model captured at auto-mode start. Used to prevent model bleed between + * Model captured at autonomous mode start. Used to prevent model bleed between * concurrent SF instances sharing the same global settings.json (#650). * When preferences don't specify a model for a unit type, this ensures * the session's original model is re-applied instead of reading from * the shared global settings (which another instance may have overwritten). */ /** Track current milestone to detect transitions */ -/** Model the user had selected before auto-mode started */ +/** Model the user had selected before autonomous mode started */ /** Progress-aware timeout supervision */ /** Context-pressure continue-here monitor — fires once when context usage >= 70% */ /** Prompt character measurement for token savings analysis (R051). */ -/** SIGTERM handler registered while auto-mode is active — cleared on stop/pause. */ +/** SIGTERM handler registered while autonomous mode is active — cleared on stop/pause. */ /** * Tool calls currently being executed — prevents false idle detection during long-running tools. * Maps toolCallId → start timestamp (ms) so the idle watchdog can detect tools that have been @@ -424,7 +424,7 @@ export function setActiveRunDir(runDir) { s.activeRunDir = runDir; } /** - * Return the model captured at auto-mode start for this session. + * Return the model captured at autonomous mode start for this session. * Used by error-recovery to fall back to the session's own model * instead of reading (potentially stale) preferences from disk (#1065). */ @@ -526,7 +526,7 @@ function normalizeTaskCompleteFailure(errorMsg) { /** * Record a tool invocation error on the current session (#2883). * Called from tool_execution_end when a SF tool fails with isError. - * Malformed/truncated JSON errors still pause auto-mode. sf_task_complete + * Malformed/truncated JSON errors still pause autonomous mode. sf_task_complete * execution errors are tracked separately so the same task can retry in-flow. */ export function recordToolInvocationError(toolName, errorMsg) { @@ -550,7 +550,7 @@ export function getOldestInFlightToolAgeMs() { /** * Return the base path to use for the auto.lock file. * Always uses the original project root (not the worktree) so that - * a second terminal can discover and stop a running auto-mode session. + * a second terminal can discover and stop a running autonomous mode session. * * Delegates to AutoSession.lockBasePath — the single source of truth. */ @@ -558,7 +558,7 @@ function lockBase() { return s.lockBasePath; } /** - * Attempt to stop a running auto-mode session from a different process. + * Attempt to stop a running autonomous mode session from a different process. * Reads the lock file at the project root, checks if the PID is alive, * and sends SIGTERM to gracefully stop it. * @@ -578,7 +578,7 @@ export function stopAutoRemote(projectRoot) { clearLock(projectRoot); return { found: false }; } - // Send SIGTERM — the auto-mode process has a handler that clears the lock and exits + // Send SIGTERM — the autonomous mode process has a handler that clears the lock and exits try { process.kill(lock.pid, "SIGTERM"); return { found: true, pid: lock.pid }; @@ -587,7 +587,7 @@ export function stopAutoRemote(projectRoot) { } } /** - * Check if a remote auto-mode session is running (from a different process). + * 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. @@ -596,7 +596,7 @@ export function checkRemoteAutoSession(projectRoot) { const lock = readCrashLock(projectRoot); if (!lock) return { running: false }; // Our own PID is not a "remote" session — it is a stale lock left by this - // process (e.g. after step-mode exit without full cleanup). (#2730) + // process (e.g. after assisted mode exit without full cleanup). (#2730) if (lock.pid === process.pid) return { running: false }; if (!isLockProcessAlive(lock)) { // Stale lock from a dead process — not a live remote session @@ -720,7 +720,7 @@ function cleanupAfterLoopExit(ctx) { ); } // A transient provider-error pause intentionally leaves the paused badge - // visible so the user still has a resumable auto-mode signal on screen. + // visible so the user still has a resumable autonomous mode signal on screen. if (!s.paused) { ctx.ui.setStatus("sf-auto", undefined); safeSetWidget(ctx, "sf-progress", undefined); @@ -947,12 +947,16 @@ export async function stopAuto(ctx, pi, reason) { if (ledger && ledger.units.length > 0) { const totals = getProjectTotals(ledger.units); ctx?.ui.notify( - `Auto-mode stopped${reasonSuffix}. Session: ${formatCost(totals.cost)} · ${formatTokenCount(totals.tokens.total)} tokens · ${ledger.units.length} units`, + `Autonomous mode stopped${reasonSuffix}. Session: ${formatCost(totals.cost)} · ${formatTokenCount(totals.tokens.total)} tokens · ${ledger.units.length} units`, "info", stopMeta, ); } else { - ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info", stopMeta); + ctx?.ui.notify( + `Autonomous mode stopped${reasonSuffix}.`, + "info", + stopMeta, + ); } } catch (e) { debugLog("stop-cleanup-ledger", { @@ -964,7 +968,7 @@ export async function stopAuto(ctx, pi, reason) { clearCmuxSidebar(loadedPreferences); logCmuxEvent( loadedPreferences, - `Auto-mode stopped${reasonSuffix || ""}.`, + `Autonomous mode stopped${reasonSuffix || ""}.`, reason?.startsWith("Blocked:") ? "warning" : "info", ); } catch (e) { @@ -1115,9 +1119,9 @@ export async function stopAuto(ctx, pi, reason) { } } /** - * Pause auto-mode without destroying state. Context is preserved. + * Pause autonomous mode without destroying state. Context is preserved. * The user can interact with the agent, then `/sf autonomous` resumes - * from disk state. Called when the user presses Escape during auto-mode. + * from disk state. Called when the user presses Escape during autonomous mode. */ export async function pauseAuto(ctx, _pi, _errorContext) { if (!s.active) return; @@ -1417,8 +1421,8 @@ export async function startAuto(ctx, pi, base, verboseMode, options) { const pid = freshStartAssessment.lock?.pid; ctx.ui.notify( pid - ? `Another auto-mode session (PID ${pid}) appears to be running.\nStop it with \`kill ${pid}\` before starting a new session.` - : "Another auto-mode session appears to be running.", + ? `Another autonomous mode session (PID ${pid}) appears to be running.\nStop it with \`kill ${pid}\` before starting a new session.` + : "Another autonomous mode session appears to be running.", "error", ); return; @@ -1612,7 +1616,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) { s.cmdCtx = ctx; s.basePath = base; // Ensure the workflow-logger audit log is pinned to the project root - // even when auto-mode is entered via a path that bypasses the + // even when autonomous mode is entered via a path that bypasses the // bootstrap/dynamic-tools ensureDbOpen() → setLogBasePath() chain // (e.g. /clear resume, hot-reload). setLogBasePath(base); @@ -1643,7 +1647,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) { ctx.ui.setStatus("sf-auto", s.stepMode ? "next" : "auto"); ctx.ui.setFooter(hideFooter); ctx.ui.notify( - s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", + s.stepMode ? "Assisted mode resumed." : "Autonomous mode resumed.", "info", ); restoreHookState(s.basePath); @@ -1716,7 +1720,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) { writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown"); logCmuxEvent( loadEffectiveSFPreferences()?.preferences, - s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", + s.stepMode ? "Assisted mode resumed." : "Autonomous mode resumed.", "progress", ); captureProjectRootEnv(s.originalBasePath || s.basePath); @@ -1755,7 +1759,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) { await deriveState(s.basePath), ); } catch (err) { - // Best-effort only — sidebar sync must never block auto-mode startup + // Best-effort only — sidebar sync must never block autonomous mode startup logWarning( "engine", `cmux sync failed: ${err instanceof Error ? err.message : String(err)}`, @@ -1764,7 +1768,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) { } logCmuxEvent( loadEffectiveSFPreferences()?.preferences, - requestedStepMode ? "Step-mode started." : "Auto-mode started.", + requestedStepMode ? "Assisted mode started." : "Autonomous mode started.", "progress", ); // Dispatch the first unit @@ -1925,7 +1929,7 @@ export async function dispatchHookUnit( s.unitTimeoutHandle = null; if (!s.active) return; ctx.ui.notify( - `Hook ${hookName} exceeded ${supervisor.hard_timeout_minutes ?? 30}min timeout. Pausing auto-mode.`, + `Hook ${hookName} exceeded ${supervisor.hard_timeout_minutes ?? 30}min timeout. Pausing autonomous mode.`, "warning", ); resetHookState(); diff --git a/src/resources/extensions/sf/auto/loop.js b/src/resources/extensions/sf/auto/loop.js index 7922fe934..5d9580429 100644 --- a/src/resources/extensions/sf/auto/loop.js +++ b/src/resources/extensions/sf/auto/loop.js @@ -1,5 +1,5 @@ /** - * auto/loop.ts — Main auto-mode execution loop. + * auto/loop.ts — Main autonomous mode execution loop. * * Iterates: derive → dispatch → guards → runUnit → finalize → repeat. * Exits when s.active becomes false or a terminal condition is reached. @@ -40,7 +40,7 @@ import { MAX_LOOP_ITERATIONS } from "./types.js"; // ── Stuck detection persistence (#3704) ────────────────────────────────── // Persist stuck detection state to disk so it survives session restarts. -// Without this, restarting auto-mode resets all counters, allowing the +// Without this, restarting autonomous mode resets all counters, allowing the // same blocked unit to burn a full retry budget each session. function stuckStatePath(basePath) { return join(sfRoot(basePath), "runtime", "stuck-state.json"); @@ -344,7 +344,7 @@ async function runExitSolverEval(ctx, s, deps, iteration) { } } /** - * Main auto-mode execution loop. Iterates: derive → dispatch → guards → + * Main autonomous mode execution loop. Iterates: derive → dispatch → guards → * runUnit → finalize → repeat. Exits when s.active becomes false or a * terminal condition is reached. * @@ -426,7 +426,7 @@ export async function autoLoop(ctx, pi, s, deps) { if (mem.pressured) { logWarning( "dispatch", - `Memory pressure: ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%) — stopping auto-mode to prevent OOM kill`, + `Memory pressure: ${mem.heapMB}MB / ${mem.limitMB}MB (${Math.round(mem.pct * 100)}%) — stopping autonomous mode to prevent OOM kill`, ); await deps.stopAuto( ctx, @@ -800,7 +800,7 @@ export async function autoLoop(ctx, pi, s, deps) { // ── P4-A: Doctor issues → reassess escalation ───────────────────── // If the health gate detects issues that mention slice IDs (state // inconsistencies that reassessment can fix), queue reassess instead - // of pausing auto-mode. This runs separately from the gate inside + // of pausing autonomous mode. This runs separately from the gate inside // runPreDispatch so we can intercept *before* the break path. try { const healthCheck = await deps.preDispatchHealthGate(s.basePath); @@ -1000,7 +1000,7 @@ export async function autoLoop(ctx, pi, s, deps) { // candidate model is denied (cross-provider disabled + flat-rate // baseline + tool-policy denial), retrying the same unit produces the // same denial — burning the consecutive-error budget toward a 3-strike - // hard stop and corrupting auto-mode state. Pause for user attention + // hard stop and corrupting autonomous mode state. Pause for user attention // instead, with the per-model deny reasons surfaced from the typed error. if (loopErr instanceof ModelPolicyDispatchBlockedError) { debugLog("autoLoop", { @@ -1011,7 +1011,7 @@ export async function autoLoop(ctx, pi, s, deps) { reasons: loopErr.reasons, }); ctx.ui.notify( - `Auto-mode paused: model-policy denied dispatch for ${loopErr.unitType}/${loopErr.unitId}. ${msg}`, + `Autonomous mode paused: model-policy denied dispatch for ${loopErr.unitType}/${loopErr.unitId}. ${msg}`, "error", ); deps.emitJournalEvent({ @@ -1051,7 +1051,7 @@ export async function autoLoop(ctx, pi, s, deps) { error: msg, }); ctx.ui.notify( - `Auto-mode stopped: infrastructure error ${infraCode} — ${msg}`, + `Autonomous mode stopped: infrastructure error ${infraCode} — ${msg}`, "error", ); await deps.stopAuto( @@ -1097,7 +1097,7 @@ export async function autoLoop(ctx, pi, s, deps) { }); if (consecutiveCooldowns > MAX_COOLDOWN_RETRIES) { ctx.ui.notify( - `Auto-mode stopped: ${consecutiveCooldowns} consecutive credential cooldowns — rate limit or quota may be persistently exhausted.`, + `Autonomous mode stopped: ${consecutiveCooldowns} consecutive credential cooldowns — rate limit or quota may be persistently exhausted.`, "error", ); await deps.stopAuto( @@ -1137,7 +1137,7 @@ export async function autoLoop(ctx, pi, s, deps) { .map((m, i) => ` ${i + 1}. ${m}`) .join("\n"); ctx.ui.notify( - `Auto-mode stopped: ${consecutiveErrors} consecutive iteration failures:\n${errorHistory}`, + `Autonomous mode stopped: ${consecutiveErrors} consecutive iteration failures:\n${errorHistory}`, "error", ); await deps.stopAuto( diff --git a/src/resources/extensions/sf/auto/phases.js b/src/resources/extensions/sf/auto/phases.js index 4c94c3d5d..90f2be552 100644 --- a/src/resources/extensions/sf/auto/phases.js +++ b/src/resources/extensions/sf/auto/phases.js @@ -363,7 +363,7 @@ async function generateMilestoneReport(s, ctx, milestoneId) { } // ─── closeoutAndStop ────────────────────────────────────────────────────────── /** - * If a unit is in-flight, close it out, then stop auto-mode. + * If a unit is in-flight, close it out, then stop autonomous mode. * Extracted from ~4 identical if-closeout-then-stop sequences in autoLoop. */ async function closeoutAndStop(ctx, pi, s, deps, reason) { @@ -1053,6 +1053,8 @@ export async function runDispatch(ic, preData, loopState) { state, prefs, session: s, + runControl: deps.uokRunControl, + permissionProfile: deps.uokPermissionProfile, }); if (dispatchResult.action === "stop") { deps.emitJournalEvent({ @@ -1359,7 +1361,7 @@ export async function runGuards(ic, mid, unitType, unitId, sliceId) { "stop-directive", basename(s.originalBasePath || s.basePath), ); - // Pause first — ensures auto-mode stops even if later steps fail + // Pause first — ensures autonomous mode stops even if later steps fail await deps.pauseAuto(ctx, pi); // For backtrack captures, write the backtrack trigger after pausing if (isBacktrack) { @@ -1457,7 +1459,7 @@ export async function runGuards(ic, mid, unitType, unitId, sliceId) { ctx.ui.notify(msg, "error"); deps.sendDesktopNotification( "SF", - "Production mutation guard paused auto-mode", + "Production mutation guard paused autonomous mode", "warning", "safety", basename(s.originalBasePath || s.basePath), @@ -1483,7 +1485,7 @@ export async function runGuards(ic, mid, unitType, unitId, sliceId) { const budgetCeiling = prefs?.budget_ceiling; if (budgetCeiling !== undefined && budgetCeiling > 0) { const currentLedger = deps.getLedger(); - // In parallel worker mode, only count cost from the current auto-mode session + // In parallel worker mode, only count cost from the current autonomous mode session // to avoid hitting the ceiling due to historical project-wide spend (#2184). let costUnits = currentLedger?.units; if ( @@ -2092,7 +2094,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) { getRequiredWorkflowToolsForAutoUnit(unitType), { projectRoot: s.basePath, - surface: "auto-mode", + surface: "autonomous mode", unitType, authMode: s.currentUnitModel?.provider ? ctx.modelRegistry.getProviderAuthMode(s.currentUnitModel.provider) @@ -2462,7 +2464,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) { } // Unit hard timeout (30min+): pause without auto-resume — stuck agent ctx.ui.notify( - `Unit timed out for ${unitType} ${unitId} (supervision may have failed). Pausing auto-mode.`, + `Unit timed out for ${unitType} ${unitId} (supervision may have failed). Pausing autonomous mode.`, "warning", ); debugLog("autoLoop", { @@ -2499,7 +2501,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) { currentUnitResult.errorContext, ); ctx.ui.notify( - `Session creation failed for ${unitType} ${unitId}: ${currentUnitResult.errorContext?.message ?? "unknown"}. Stopping auto-mode.`, + `Session creation failed for ${unitType} ${unitId}: ${currentUnitResult.errorContext?.message ?? "unknown"}. Stopping autonomous mode.`, "warning", ); await deps.stopAuto( @@ -2848,7 +2850,7 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) { }); if (loopState.consecutiveFinalizeTimeouts >= MAX_FINALIZE_TIMEOUTS) { ctx.ui.notify( - `postUnitPreVerification timed out ${loopState.consecutiveFinalizeTimeouts} consecutive times — stopping auto-mode to prevent budget waste`, + `postUnitPreVerification timed out ${loopState.consecutiveFinalizeTimeouts} consecutive times — stopping autonomous mode to prevent budget waste`, "error", ); await deps.stopAuto( @@ -2909,7 +2911,7 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) { } if (pauseAfterUatDispatch) { ctx.ui.notify( - "UAT requires human execution. Auto-mode will pause after this unit writes the result file.", + "UAT requires human execution. Autonomous mode will pause after this unit writes the result file.", "info", ); await deps.pauseAuto(ctx, pi); @@ -3062,7 +3064,7 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) { }); if (loopState.consecutiveFinalizeTimeouts >= MAX_FINALIZE_TIMEOUTS) { ctx.ui.notify( - `postUnitPostVerification timed out ${loopState.consecutiveFinalizeTimeouts} consecutive times — stopping auto-mode to prevent budget waste`, + `postUnitPostVerification timed out ${loopState.consecutiveFinalizeTimeouts} consecutive times — stopping autonomous mode to prevent budget waste`, "error", ); await deps.stopAuto( @@ -3087,7 +3089,7 @@ export async function runFinalize(ic, iterData, loopState, sidecarItem) { return { action: "break", reason: "post-verification-stopped" }; } if (postResult === "step-wizard") { - // Step mode — exit the loop (caller handles wizard) + // Assisted mode — exit the loop (caller handles wizard) debugLog("autoLoop", { phase: "exit", reason: "step-wizard" }); return { action: "break", reason: "step-wizard" }; } diff --git a/src/resources/extensions/sf/auto/session.js b/src/resources/extensions/sf/auto/session.js index d033862a0..c124c0827 100644 --- a/src/resources/extensions/sf/auto/session.js +++ b/src/resources/extensions/sf/auto/session.js @@ -1,5 +1,5 @@ /** - * AutoSession — encapsulates all mutable auto-mode state into a single instance. + * AutoSession — encapsulates all mutable autonomous mode state into a single instance. * * Replaces ~40 module-level variables scattered across auto.ts with typed * properties on a class instance. Benefits: @@ -9,7 +9,7 @@ * - grep `s.` shows every state access * - Constructable for testing * - * MAINTENANCE RULE: All new mutable auto-mode state MUST be added here as a + * MAINTENANCE RULE: All new mutable autonomous mode state MUST be added here as a * class property, not as a module-level variable in auto.ts. If the state * needs clearing on stop, add it to reset(). Tests in * auto-session-encapsulation.test.ts enforce that auto.ts has no module-level @@ -41,7 +41,7 @@ export class AutoSession { stepMode = false; /** * When false, the agent is forbidden from calling ask_user_questions. - * Step mode sets this true; `/sf autonomous` sets it false. + * Assisted mode sets this true; `/sf autonomous` sets it false. */ canAskUser = true; verbose = false; @@ -113,7 +113,7 @@ export class AutoSession { * Last sf_task_complete execution error for the current turn. * Unlike malformed tool invocation errors, these are normal tool execution * failures (for example a transient SUMMARY.md write failure) and should be - * retried in-flow instead of pausing auto-mode. + * retried in-flow instead of pausing autonomous mode. */ lastTaskCompleteFailure = null; /** Per-unit task completion failures to surface in the next execute-task prompt. */ diff --git a/src/resources/extensions/sf/auto/types.js b/src/resources/extensions/sf/auto/types.js index dd78233ed..964f3295f 100644 --- a/src/resources/extensions/sf/auto/types.js +++ b/src/resources/extensions/sf/auto/types.js @@ -36,5 +36,5 @@ export const BUDGET_THRESHOLDS = [ }, { pct: 75, label: "Budget 75%", notifyLevel: "info", cmuxLevel: "progress" }, ]; -/** Max consecutive finalize timeouts before hard-stopping auto-mode. */ +/** Max consecutive finalize timeouts before hard-stopping autonomous mode. */ export const MAX_FINALIZE_TIMEOUTS = 3; diff --git a/src/resources/extensions/sf/autonomous-command-args.d.ts b/src/resources/extensions/sf/autonomous-command-args.d.ts new file mode 100644 index 000000000..d57753e58 --- /dev/null +++ b/src/resources/extensions/sf/autonomous-command-args.d.ts @@ -0,0 +1,12 @@ +/** + * autonomous-command-args.d.ts — Type declarations for autonomous command validation. + * + * Purpose: let TypeScript command surfaces import the JS extension helper + * without weakening root compile safety. + * + * Consumer: src/headless.ts and tests that validate removed shorthand flags. + */ + +export function findUnsupportedAutonomousArgs(args: string[]): string[]; + +export function formatUnsupportedAutonomousArgs(args: string[]): string; diff --git a/src/resources/extensions/sf/autonomous-command-args.js b/src/resources/extensions/sf/autonomous-command-args.js new file mode 100644 index 000000000..eb8b11ac1 --- /dev/null +++ b/src/resources/extensions/sf/autonomous-command-args.js @@ -0,0 +1,53 @@ +/** + * autonomous-command-args.js — validates `/sf 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. + * + * Consumer: headless.ts before machine-surface startup and the interactive + * autonomous command handler before dispatching the run loop. + */ + +const VALUE_FLAGS = new Set(["--yolo", "-y"]); +const SWITCH_FLAGS = new Set(["--verbose", "--debug"]); +const MILESTONE_TARGET_RE = /^M\d+(?:-[a-z0-9]{6})?$/i; + +/** + * Return autonomous arguments that are not part of the supported command grammar. + * + * 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. + */ +export function findUnsupportedAutonomousArgs(args) { + const unsupported = []; + for (let i = 0; i < args.length; i++) { + const arg = String(args[i] ?? "").trim(); + if (!arg) continue; + if (VALUE_FLAGS.has(arg)) { + const value = String(args[i + 1] ?? "").trim(); + if (!value || value.startsWith("-")) { + unsupported.push(arg); + } else { + i += 1; + } + continue; + } + if (SWITCH_FLAGS.has(arg) || MILESTONE_TARGET_RE.test(arg)) continue; + unsupported.push(arg); + } + return unsupported; +} + +/** + * Format unsupported autonomous arguments for human and machine-surface stderr. + * + * Purpose: give callers a direct correction path without giving unsupported + * arguments product meaning. + * + * 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 <file>, and optional M### milestone target.`; +} diff --git a/src/resources/extensions/sf/blocked-models.js b/src/resources/extensions/sf/blocked-models.js index 51ff7988c..4ff70b058 100644 --- a/src/resources/extensions/sf/blocked-models.js +++ b/src/resources/extensions/sf/blocked-models.js @@ -3,7 +3,7 @@ // capacity reasons. // // Lives at `.sf/runtime/blocked-models.json` so the block survives /sf autonomous -// restarts. Auto-mode model selection skips blocked entries; agent-end +// 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. import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; diff --git a/src/resources/extensions/sf/bootstrap/agent-end-recovery.js b/src/resources/extensions/sf/bootstrap/agent-end-recovery.js index a893d601f..85f0d8231 100644 --- a/src/resources/extensions/sf/bootstrap/agent-end-recovery.js +++ b/src/resources/extensions/sf/bootstrap/agent-end-recovery.js @@ -145,7 +145,7 @@ export async function handleAgentEnd(pi, event, ctx) { } catch (err) { const message = err instanceof Error ? err.message : String(err); ctx.ui.notify( - `Auto-mode error after empty-content abort: ${message}. Stopping auto-mode.`, + `Autonomous mode error after empty-content abort: ${message}. Stopping autonomous mode.`, "error", ); try { @@ -192,7 +192,7 @@ export async function handleAgentEnd(pi, event, ctx) { const cls = classifyError(rawErrorMsg, explicitRetryAfterMs); const currentRoute = getCurrentRouteFromMessage(lastMsg, ctx); const dash = getAutoDashboardData(); - // SF owns provider-route recovery in auto-mode. Quota/rate-limit/server/ + // SF owns provider-route recovery in autonomous mode. Quota/rate-limit/server/ // stream/connection failures must leave the failed provider/model route // immediately instead of sleeping or waiting for same-model retry loops. // Cap rate-limit backoff for CLI-style providers (openai-codex, google-gemini-cli) @@ -343,7 +343,7 @@ export async function handleAgentEnd(pi, event, ctx) { } catch (err) { const message = err instanceof Error ? err.message : String(err); ctx.ui.notify( - `Auto-mode error in agent_end handler: ${message}. Stopping auto-mode.`, + `Autonomous mode error in agent_end handler: ${message}. Stopping autonomous mode.`, "error", ); try { diff --git a/src/resources/extensions/sf/bootstrap/ask-gate.js b/src/resources/extensions/sf/bootstrap/ask-gate.js index 2dd77ae69..27ec4c3c2 100644 --- a/src/resources/extensions/sf/bootstrap/ask-gate.js +++ b/src/resources/extensions/sf/bootstrap/ask-gate.js @@ -22,7 +22,7 @@ import { logWarning } from "../workflow-logger.js"; * (`isAutoActive() && !isCanAskUser()`) the call is blocked with a structured * rejection message the agent can read and act on (escalate to Tier 1/2). * - * In auto/step mode (`canAskUser=true`) all calls pass through. + * In manual/assisted mode (`canAskUser=true`) all calls pass through. * * @param questionPayload - Raw tool-call input; used only for diagnostic logging. * @returns `{ allow: true }` to permit the call, or `{ allow: false, reason }` to block. diff --git a/src/resources/extensions/sf/bootstrap/db-tools.js b/src/resources/extensions/sf/bootstrap/db-tools.js index 043b99bf9..694b2e0eb 100644 --- a/src/resources/extensions/sf/bootstrap/db-tools.js +++ b/src/resources/extensions/sf/bootstrap/db-tools.js @@ -1741,7 +1741,7 @@ export function registerDbTools(pi) { }), continueWithDefault: Type.Boolean({ description: - "When true, loop continues (artifact logged for later review). When false, auto-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 /sf escalate resolve.", }), }, { @@ -2020,7 +2020,7 @@ export function registerDbTools(pi) { content: [ { type: "text", - text: `Skipped slice ${params.sliceId} (${params.milestoneId}). Reason: ${params.reason ?? "User-directed skip"}. Auto-mode will advance past this slice.`, + text: `Skipped slice ${params.sliceId} (${params.milestoneId}). Reason: ${params.reason ?? "User-directed skip"}. Autonomous mode will advance past this slice.`, }, ], details: { @@ -2046,10 +2046,10 @@ export function registerDbTools(pi) { name: "sf_skip_slice", label: "Skip Slice", description: - "Mark a slice as skipped so auto-mode advances past it without executing. " + + "Mark a slice as skipped so autonomous mode advances past it without executing. " + "The slice data is preserved for reference. The state machine treats skipped slices like completed ones for dependency satisfaction.", promptSnippet: - "Skip a SF slice (mark as skipped, auto-mode will advance past it)", + "Skip a SF slice (mark as skipped, autonomous mode will advance past it)", promptGuidelines: [ "Use sf_skip_slice when a slice should be bypassed — descoped, superseded, or no longer relevant.", "Cannot skip a slice that is already complete.", diff --git a/src/resources/extensions/sf/bootstrap/journal-tools.js b/src/resources/extensions/sf/bootstrap/journal-tools.js index 33ffe1e8c..bd8fa053d 100644 --- a/src/resources/extensions/sf/bootstrap/journal-tools.js +++ b/src/resources/extensions/sf/bootstrap/journal-tools.js @@ -6,12 +6,12 @@ export function registerJournalTools(pi) { name: "sf_journal_query", label: "Query Journal", description: - "Query the structured event journal for auto-mode iterations. " + + "Query the structured event journal for autonomous mode iterations. " + "Returns matching journal entries filtered by flow ID, unit ID, rule name, event type, or time range.", promptSnippet: "Query the SF event journal with filters (flowId, unitId, rule, eventType, time range, limit)", promptGuidelines: [ - "Filter by flowId to trace all events from a single auto-mode iteration.", + "Filter by flowId to trace all events from a single autonomous mode iteration.", "Filter by unitId to reconstruct the causal chain for a specific milestone/slice/task.", "Use limit to control context size — default is 100 entries.", ], diff --git a/src/resources/extensions/sf/bootstrap/provider-error-resume.js b/src/resources/extensions/sf/bootstrap/provider-error-resume.js index 1a46e5ace..5fe66e80c 100644 --- a/src/resources/extensions/sf/bootstrap/provider-error-resume.js +++ b/src/resources/extensions/sf/bootstrap/provider-error-resume.js @@ -21,7 +21,7 @@ export async function resumeAutoAfterProviderDelay( if (!snapshot.paused) return "not-paused"; if (!snapshot.basePath) { ctx.ui.notify( - "Provider error recovery delay elapsed, but no paused auto-mode base path was available. Leaving auto-mode paused.", + "Provider error recovery delay elapsed, but no paused autonomous mode base path was available. Leaving autonomous mode paused.", "warning", ); return "missing-base"; @@ -32,7 +32,7 @@ export async function resumeAutoAfterProviderDelay( : (deps.getCommandContext?.() ?? null); if (!commandCtx || typeof commandCtx.newSession !== "function") { ctx.ui.notify( - "Provider error recovery delay elapsed, but no command context with newSession was available. Leaving auto-mode paused.", + "Provider error recovery delay elapsed, but no command context with newSession was available. Leaving autonomous mode paused.", "warning", ); return "missing-command-context"; diff --git a/src/resources/extensions/sf/bootstrap/register-hooks.js b/src/resources/extensions/sf/bootstrap/register-hooks.js index 5560381b4..9d11dba84 100644 --- a/src/resources/extensions/sf/bootstrap/register-hooks.js +++ b/src/resources/extensions/sf/bootstrap/register-hooks.js @@ -427,8 +427,8 @@ export function registerHooks(pi, ecosystemHandlers = []) { } }); pi.on("session_before_compact", async () => { - // Only cancel compaction while auto-mode is actively running. - // Paused auto-mode should allow compaction — the user may be doing + // Only cancel compaction while autonomous mode is actively running. + // Paused autonomous mode should allow compaction — the user may be doing // interactive work (#3165). if (isAutoActive()) { return { cancel: true }; @@ -611,7 +611,7 @@ export function registerHooks(pi, ecosystemHandlers = []) { } if (!isToolCallEventType("write", event)) return; // ── Worktree isolation: block writes outside the worktree and main .sf/ ── - // Only enforced in auto-mode — interactive sessions skip this check. + // Only enforced in autonomous mode — interactive sessions skip this check. // When SF_WORKTREE is set, process.cwd() is the worktree directory. // The agent should only write inside the worktree OR inside the main repo's .sf/. if (isAutoActive() && process.env.SF_WORKTREE) { @@ -648,7 +648,7 @@ export function registerHooks(pi, ecosystemHandlers = []) { if (!isAutoActive()) return; safetyRecordToolCall(event.toolCallId, event.toolName, event.input); const policyDash = getAutoDashboardData(); - const policyProfile = isQueuePhaseActive() ? "plan" : "build"; + const policyProfile = isQueuePhaseActive() ? "restricted" : "normal"; if (policyDash.basePath) { emitJournalEvent( policyDash.basePath, @@ -857,7 +857,7 @@ export function registerHooks(pi, ecosystemHandlers = []) { applyCompletionNudgeTemperature(payload); // ── Observation Masking ───────────────────────────────────────────── // Replace old tool results with placeholders to reduce context bloat. - // Only active during auto-mode when context_management.observation_masking is enabled. + // Only active during autonomous mode when context_management.observation_masking is enabled. if (isAutoActive()) { try { const { loadEffectiveSFPreferences } = await import( diff --git a/src/resources/extensions/sf/bootstrap/system-context.js b/src/resources/extensions/sf/bootstrap/system-context.js index c2a743954..2aea2d17c 100644 --- a/src/resources/extensions/sf/bootstrap/system-context.js +++ b/src/resources/extensions/sf/bootstrap/system-context.js @@ -137,7 +137,7 @@ function warnDeprecatedAgentInstructions() { * stronger language that forbids `ask_user_questions` entirely and instructs * the agent to exit with a structured blocker message instead. * - * @param canAskUser - true in auto/step mode; false in autonomous mode. + * @param canAskUser - true in manual/assisted mode; false in autonomous mode. */ export function buildEscalationPolicyBlock(canAskUser) { const tier3 = canAskUser diff --git a/src/resources/extensions/sf/bootstrap/tool-call-loop-guard.js b/src/resources/extensions/sf/bootstrap/tool-call-loop-guard.js index 0f90c533a..25d9a9221 100644 --- a/src/resources/extensions/sf/bootstrap/tool-call-loop-guard.js +++ b/src/resources/extensions/sf/bootstrap/tool-call-loop-guard.js @@ -2,7 +2,7 @@ * Tool-call loop guard. * * Detects when a model calls the same tool with identical arguments - * repeatedly within a single agent turn. Works in both auto-mode and + * repeatedly within a single agent turn. Works in both autonomous mode and * interactive sessions by hooking into the `tool_call` event, which * fires before execution and can block the call. * diff --git a/src/resources/extensions/sf/captures.js b/src/resources/extensions/sf/captures.js index 1fedc4fa3..e105c5265 100644 --- a/src/resources/extensions/sf/captures.js +++ b/src/resources/extensions/sf/captures.js @@ -231,9 +231,9 @@ export function loadActionableCaptures(basePath, currentMilestoneId) { ); } /** - * Load unexecuted stop captures — user directives to halt auto-mode. + * Load unexecuted stop captures — user directives to halt autonomous mode. * These are checked in the pre-dispatch guard pipeline (runGuards) to - * pause auto-mode before the next unit is dispatched. + * pause autonomous mode before the next unit is dispatched. */ export function loadStopCaptures(basePath) { return loadAllCaptures(basePath).filter( @@ -245,7 +245,7 @@ export function loadStopCaptures(basePath) { } /** * Load unexecuted backtrack captures specifically — captures directing - * auto-mode to abandon current milestone and return to a previous one. + * autonomous mode to abandon current milestone and return to a previous one. */ export function loadBacktrackCaptures(basePath) { return loadAllCaptures(basePath).filter( diff --git a/src/resources/extensions/sf/commands-bootstrap.js b/src/resources/extensions/sf/commands-bootstrap.js index 476522c82..2ff046c2c 100644 --- a/src/resources/extensions/sf/commands-bootstrap.js +++ b/src/resources/extensions/sf/commands-bootstrap.js @@ -3,7 +3,7 @@ import { workflowTemplateCommandDefinitions } from "./workflow-templates.js"; const TOP_LEVEL_SUBCOMMANDS = [ { cmd: "help", desc: "Categorized command reference with descriptions" }, - { cmd: "next", desc: "Explicit step mode (same as /sf)" }, + { cmd: "next", desc: "Assisted mode — execute one unit, then pause" }, { cmd: "autonomous", desc: "Autonomous mode — research, plan, execute, commit, repeat", diff --git a/src/resources/extensions/sf/commands-escalate.js b/src/resources/extensions/sf/commands-escalate.js index ed802a9c7..c87cc3a0c 100644 --- a/src/resources/extensions/sf/commands-escalate.js +++ b/src/resources/extensions/sf/commands-escalate.js @@ -64,7 +64,7 @@ export async function handleEscalate(args, ctx) { if (!art) continue; count++; const isAutoResolved = - art.respondedAt && art.userRationale?.startsWith("auto-mode:"); + art.respondedAt && art.userRationale?.startsWith("autonomous mode:"); const status = task.escalation_pending === 1 ? "PENDING" @@ -138,7 +138,7 @@ export async function handleEscalate(args, ctx) { } out.push(`\nRationale for recommendation: ${art.recommendationRationale}`); if (art.respondedAt) { - const isAutoResolved = art.userRationale?.startsWith("auto-mode:"); + const isAutoResolved = art.userRationale?.startsWith("autonomous mode:"); const verb = isAutoResolved ? "Auto-accepted" : "Resolved"; out.push( `\n${verb} ${art.respondedAt} → choice="${art.userChoice}"${art.userRationale ? ` (rationale: ${art.userRationale})` : ""}`, diff --git a/src/resources/extensions/sf/commands-extract-learnings.js b/src/resources/extensions/sf/commands-extract-learnings.js index 11129b4b1..14cfad3cc 100644 --- a/src/resources/extensions/sf/commands-extract-learnings.js +++ b/src/resources/extensions/sf/commands-extract-learnings.js @@ -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 auto-mode complete-milestone turn. + * `/sf 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 auto-mode milestone-completion turn. +\`/sf 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 02e5b4b94..77b0f2be4 100644 --- a/src/resources/extensions/sf/commands-handlers.js +++ b/src/resources/extensions/sf/commands-handlers.js @@ -427,7 +427,7 @@ export async function handleSteer(change, ctx, pi) { const sid = state.activeSlice?.id ?? "none"; const tid = state.activeTask?.id ?? "none"; const appliedAt = `${mid}/${sid}/${tid}`; - // Resolve the correct target path: only route to a worktree when auto-mode + // Resolve the correct target path: only route to a worktree when autonomous mode // is actively running there (in-process or remote). A worktree directory may // exist from a previous session without being the active runtime path — // writing there without a live session would silently drop the override. @@ -565,7 +565,7 @@ Examples: ); if (!success) { ctx.ui.notify( - "Failed to dispatch hook. Auto-mode may have been cancelled.", + "Failed to dispatch hook. Autonomous mode may have been cancelled.", "error", ); } diff --git a/src/resources/extensions/sf/commands-logs.js b/src/resources/extensions/sf/commands-logs.js index 54a3f611c..bbb6f0abd 100644 --- a/src/resources/extensions/sf/commands-logs.js +++ b/src/resources/extensions/sf/commands-logs.js @@ -281,7 +281,7 @@ async function handleLogsList(basePath, ctx) { const debugLogs = listDebugLogs(basePath); if (activities.length === 0 && debugLogs.length === 0) { ctx.ui.notify( - "No logs found.\n\nActivity logs are created during auto-mode.\nDebug logs require SF_DEBUG=1.", + "No logs found.\n\nActivity logs are created during autonomous mode.\nDebug logs require SF_DEBUG=1.", "info", ); return; @@ -463,7 +463,7 @@ async function handleLogsTail(basePath, ctx, count) { const activities = listActivityLogs(basePath); if (activities.length === 0) { ctx.ui.notify( - "No activity logs found. Logs are created during auto-mode.", + "No activity logs found. Logs are created during autonomous mode.", "info", ); return; @@ -529,7 +529,7 @@ async function handleLogsCurrent(basePath, ctx) { const lockData = readSessionLockData(basePath); if (!lockData) { ctx.ui.notify( - "No active auto-mode session.\n\nauto.lock not found — auto-mode is not running.", + "No active autonomous mode session.\n\nauto.lock not found — autonomous mode is not running.", "info", ); return; diff --git a/src/resources/extensions/sf/commands-maintenance.js b/src/resources/extensions/sf/commands-maintenance.js index e508d5e3d..84bc7f711 100644 --- a/src/resources/extensions/sf/commands-maintenance.js +++ b/src/resources/extensions/sf/commands-maintenance.js @@ -318,7 +318,7 @@ export async function handleSkip(unitArg, ctx, basePath) { mkDir(pathJoin(basePath, ".sf"), { recursive: true }); writeFile(completedKeysFile, JSON.stringify(keys), "utf-8"); ctx.ui.notify( - `Skipped: ${skipKey}. Will not be dispatched in auto-mode.`, + `Skipped: ${skipKey}. Will not be dispatched in autonomous mode.`, "success", ); } diff --git a/src/resources/extensions/sf/commands-schedule.js b/src/resources/extensions/sf/commands-schedule.js index 3eba1f46c..8aef54bae 100644 --- a/src/resources/extensions/sf/commands-schedule.js +++ b/src/resources/extensions/sf/commands-schedule.js @@ -9,7 +9,7 @@ import { executeProjectScheduleCommand, markProjectScheduleDone, -} from "./schedule/schedule-auto-dispatch.js"; +} from "./schedule/schedule-autonomous-dispatch.js"; import { createScheduleStore } from "./schedule/schedule-store.js"; import { ALL_SCHEDULE_KINDS, isValidKind } from "./schedule/schedule-types.js"; import { generateULID } from "./schedule/schedule-ulid.js"; @@ -167,7 +167,7 @@ async function addItem(args, ctx) { let kind = "reminder"; let scope = "project"; let dueAt = null; - let autoDispatch = false; + let autonomousDispatch = false; let capture = null; const titleParts = []; @@ -205,10 +205,17 @@ async function addItem(args, ctx) { dueAt = new Date(parsed).toISOString(); continue; } - if (p === "--auto-dispatch" || p === "--auto") { - autoDispatch = true; + if (p === "--autonomous-dispatch") { + autonomousDispatch = true; continue; } + if (p === "--auto-dispatch" || p === "--auto") { + ctx.ui.notify( + "Unsupported schedule argument: use --autonomous-dispatch.", + "warning", + ); + return; + } if (p === "--capture") { capture = parts[++i]; continue; @@ -258,7 +265,7 @@ async function addItem(args, ctx) { created_at: new Date().toISOString(), payload: _payloadForKind(kind, title, capture), created_by: "user", - ...(autoDispatch ? { auto_dispatch: true } : {}), + ...(autonomousDispatch ? { autonomous_dispatch: true } : {}), }; store.appendEntry(scope, entry); ctx.ui.notify(`Scheduled: ${entry.id}\nDue: ${entry.due_at}`, "success"); @@ -488,7 +495,7 @@ async function runItem(args, ctx) { status: entry.status, cwd: _basePath(), command, - auto_dispatch: entry.auto_dispatch === true, + autonomous_dispatch: entry.autonomous_dispatch === true, would_execute: typeof command === "string" && command.length > 0, }, null, @@ -560,7 +567,7 @@ export async function handleSchedule(args, ctx) { case "": ctx.ui.notify( "Usage: /sf schedule add|list|done|cancel|snooze|run\n" + - " add --in \u003cduration\u003e [--kind \u003ckind\u003e] [--scope \u003cscope\u003e] [--auto-dispatch] \u003ctitle-or-command\u003e\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" + " cancel \u003cid\u003e\n" + diff --git a/src/resources/extensions/sf/commands-ship.js b/src/resources/extensions/sf/commands-ship.js index 7dc6ae600..c2c395f0e 100644 --- a/src/resources/extensions/sf/commands-ship.js +++ b/src/resources/extensions/sf/commands-ship.js @@ -149,7 +149,9 @@ function generatePRContent(basePath, milestoneId, milestoneTitle) { sections.push("- [ ] `chore` — Build, CI, or tooling changes\n"); // AI disclosure sections.push("---\n"); - sections.push("*This PR was prepared with AI assistance (SF auto-mode).*"); + sections.push( + "*This PR was prepared with AI assistance (SF autonomous mode).*", + ); return { title, body: sections.join("\n") }; } export async function handleShip(args, ctx, _pi) { diff --git a/src/resources/extensions/sf/commands-workflow-templates.js b/src/resources/extensions/sf/commands-workflow-templates.js index 9ec88b778..1310b2f39 100644 --- a/src/resources/extensions/sf/commands-workflow-templates.js +++ b/src/resources/extensions/sf/commands-workflow-templates.js @@ -185,12 +185,12 @@ export async function handleStart(args, ctx, pi) { ctx.ui.notify(listTemplates(), "info"); return; } - // ─── Auto-mode conflict guard ────────────────────────────────────────── + // ─── Autonomous mode conflict guard ────────────────────────────────────────── // Workflow templates dispatch their own messages and switch git branches, - // which would conflict with an active auto-mode dispatch loop. + // which would conflict with an active autonomous mode dispatch loop. if (isAutoActive()) { ctx.ui.notify( - "Cannot start a workflow template while auto-mode is running.\n" + + "Cannot start a workflow template while autonomous mode is running.\n" + "Run /sf pause first, then /sf start.", "warning", ); @@ -198,7 +198,7 @@ export async function handleStart(args, ctx, pi) { } if (isAutoPaused()) { ctx.ui.notify( - "Auto-mode is paused. Starting a workflow template will run independently.\n" + + "Autonomous mode is paused. Starting a workflow template will run independently.\n" + "The paused autonomous session can be resumed later with /sf autonomous.", "info", ); diff --git a/src/resources/extensions/sf/commands/catalog.js b/src/resources/extensions/sf/commands/catalog.js index 6fffb2722..513d84fa1 100644 --- a/src/resources/extensions/sf/commands/catalog.js +++ b/src/resources/extensions/sf/commands/catalog.js @@ -18,7 +18,7 @@ export const SF_COMMAND_DESCRIPTION = */ export const TOP_LEVEL_SUBCOMMANDS = [ { cmd: "help", desc: "Categorized command reference with descriptions" }, - { cmd: "next", desc: "Explicit step mode (same as /sf)" }, + { cmd: "next", desc: "Assisted mode — execute one unit, then pause" }, { cmd: "autonomous", desc: "Autonomous mode — continuous loop, never asks user (self-resolves or stops with blocker)", diff --git a/src/resources/extensions/sf/commands/dispatcher.js b/src/resources/extensions/sf/commands/dispatcher.js index e96323cc6..aec6b94f2 100644 --- a/src/resources/extensions/sf/commands/dispatcher.js +++ b/src/resources/extensions/sf/commands/dispatcher.js @@ -1,5 +1,5 @@ import { SFNoProjectError } from "./context.js"; -import { handleAutoCommand } from "./handlers/auto.js"; +import { handleAutonomousCommand } from "./handlers/autonomous.js"; import { handleCoreCommand } from "./handlers/core.js"; import { handleOpsCommand } from "./handlers/ops.js"; import { handleParallelCommand } from "./handlers/parallel.js"; @@ -8,7 +8,7 @@ export async function handleSFCommand(args, ctx, pi) { const trimmed = (typeof args === "string" ? args : "").trim(); const handlers = [ () => handleCoreCommand(trimmed, ctx, pi), - () => handleAutoCommand(trimmed, ctx, pi), + () => handleAutonomousCommand(trimmed, ctx, pi), () => handleParallelCommand(trimmed, ctx, pi), () => handleWorkflowCommand(trimmed, ctx, pi), () => handleOpsCommand(trimmed, ctx, pi), diff --git a/src/resources/extensions/sf/commands/handlers/auto.js b/src/resources/extensions/sf/commands/handlers/autonomous.js similarity index 92% rename from src/resources/extensions/sf/commands/handlers/auto.js rename to src/resources/extensions/sf/commands/handlers/autonomous.js index 98a89793d..80383d54a 100644 --- a/src/resources/extensions/sf/commands/handlers/auto.js +++ b/src/resources/extensions/sf/commands/handlers/autonomous.js @@ -9,6 +9,10 @@ import { stopAuto, stopAutoRemote, } from "../../auto.js"; +import { + findUnsupportedAutonomousArgs, + formatUnsupportedAutonomousArgs, +} from "../../autonomous-command-args.js"; import { handleRate } from "../../commands-rate.js"; import { enableDebug } from "../../debug-logger.js"; import { findMilestoneIds } from "../../milestone-id-utils.js"; @@ -46,12 +50,6 @@ export function parseMilestoneTarget(input) { const rest = input.replace(match[0], "").replace(/\s+/g, " ").trim(); return { milestoneId: match[1], rest }; } -function hasRemovedFullFlag(input) { - return input - .split(/\s+/) - .filter(Boolean) - .some((token) => token === "full" || token === "--full"); -} /** * Dispatch entry point for the autonomous command family. * @@ -70,7 +68,7 @@ function hasRemovedFullFlag(input) { * dispatches via `launchAuto` (which routes between machine-surface and detached * spawn paths). */ -export async function handleAutoCommand(trimmed, ctx, pi) { +export async function handleAutonomousCommand(trimmed, ctx, pi) { const isAutonomousVerb = trimmed === "autonomous" || trimmed.startsWith("autonomous "); /** @@ -119,17 +117,18 @@ export async function handleAutoCommand(trimmed, ctx, pi) { return true; } if (isAutonomousVerb) { - const { yoloSeedFile, rest: afterYolo } = parseYoloFlag(trimmed); + const autonomousArgsText = trimmed.replace(/^autonomous\b/, "").trim(); + const { yoloSeedFile, rest: afterYolo } = parseYoloFlag(autonomousArgsText); const { milestoneId, rest: afterMilestone } = parseMilestoneTarget(afterYolo); const verboseMode = afterMilestone.includes("--verbose"); const debugMode = afterMilestone.includes("--debug"); const canAskUser = false; - if (hasRemovedFullFlag(afterMilestone)) { - ctx.ui.notify( - "`/sf autonomous full` was removed. Use `/sf autonomous`; autonomous run control already continues through eligible milestones until policy, evidence, budget, blockers, or completion stops it.", - "warning", - ); + const unsupportedArgs = findUnsupportedAutonomousArgs( + afterMilestone.split(/\s+/).filter(Boolean), + ); + if (unsupportedArgs.length > 0) { + ctx.ui.notify(formatUnsupportedAutonomousArgs(unsupportedArgs), "error"); return true; } if (debugMode) enableDebug(projectRoot()); diff --git a/src/resources/extensions/sf/commands/handlers/core.js b/src/resources/extensions/sf/commands/handlers/core.js index 79ff3b674..5f4ebe7ed 100644 --- a/src/resources/extensions/sf/commands/handlers/core.js +++ b/src/resources/extensions/sf/commands/handlers/core.js @@ -25,7 +25,7 @@ export function showHelp(ctx, args = "") { "SF — Singularity Forge\n", "QUICK START", " /sf start <tpl> Start a workflow template", - " /sf Run next unit (same as /sf next)", + " /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", @@ -52,15 +52,15 @@ export function showHelp(ctx, args = "") { " /sf prefs Manage preferences", " /sf doctor Diagnose and repair .sf/ state", "", - "Use /sf help full for the complete command reference.", + "Use /sf help all for the complete command reference.", ]; - const fullLines = [ + 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 next unit in step mode (same as /sf next)", - " /sf next Execute next task, then pause [--dry-run] [--verbose]", + " /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)", @@ -121,8 +121,8 @@ export function showHelp(ctx, args = "") { " /sf inspect Show SQLite DB diagnostics (schema, row counts, recent entries)", " /sf update Update SF to the latest version via npm", ]; - const full = ["full", "--full", "all"].includes(args.trim().toLowerCase()); - ctx.ui.notify((full ? fullLines : summaryLines).join("\n"), "info"); + const showAll = args.trim().toLowerCase() === "all"; + ctx.ui.notify((showAll ? allLines : summaryLines).join("\n"), "info"); } export async function handleStatus(ctx) { const basePath = projectRoot(); @@ -301,9 +301,7 @@ async function selectModelByProvider(title, models, ctx, currentModel) { return optionToModel.get(modelChoice); } async function resolveRequestedModel(query, ctx) { - const { resolveModelId } = await import( - "../../autonomous model-selection.js" - ); + const { resolveModelId } = await import("../../auto-model-selection.js"); const models = ctx.modelRegistry.getAvailable(); const exact = resolveModelId(query, models, ctx.model?.provider); if (exact) return exact; diff --git a/src/resources/extensions/sf/context-masker.js b/src/resources/extensions/sf/context-masker.js index a8c5e48a7..0995eda33 100644 --- a/src/resources/extensions/sf/context-masker.js +++ b/src/resources/extensions/sf/context-masker.js @@ -1,5 +1,5 @@ /** - * Observation masking for SF auto-mode sessions. + * Observation masking for SF autonomous mode sessions. * * Replaces tool result content older than N turns with a placeholder. * Reduces context bloat between compactions with zero LLM overhead. diff --git a/src/resources/extensions/sf/crash-recovery.js b/src/resources/extensions/sf/crash-recovery.js index d98b4b0b8..b3fff0f51 100644 --- a/src/resources/extensions/sf/crash-recovery.js +++ b/src/resources/extensions/sf/crash-recovery.js @@ -1,7 +1,7 @@ /** * SF Crash Recovery * - * Detects interrupted auto-mode sessions via a lock file. + * Detects interrupted autonomous mode sessions via a lock file. * Written on auto-start, updated on each unit dispatch, deleted on clean stop. * If the lock file exists on next startup, the previous session crashed. * @@ -19,7 +19,7 @@ import { effectiveLockFile } from "./session-lock.js"; function lockPath(basePath) { return join(sfRoot(basePath), effectiveLockFile()); } -/** Write or update the lock file with current auto-mode state. */ +/** Write or update the lock file with current autonomous mode state. */ export function writeLock(basePath, unitType, unitId, sessionFile) { try { const data = { @@ -83,7 +83,7 @@ export function isLockProcessAlive(lock) { /** Format crash info for display or injection into a prompt. */ export function formatCrashInfo(lock) { const lines = [ - `Previous auto-mode session was interrupted.`, + `Previous autonomous mode session was interrupted.`, ` Was executing: ${lock.unitType} (${lock.unitId})`, ` Started at: ${lock.unitStartedAt}`, ` PID: ${lock.pid}`, diff --git a/src/resources/extensions/sf/dashboard-overlay.js b/src/resources/extensions/sf/dashboard-overlay.js index 05bc1e7e2..3a8440a6a 100644 --- a/src/resources/extensions/sf/dashboard-overlay.js +++ b/src/resources/extensions/sf/dashboard-overlay.js @@ -1,7 +1,7 @@ /** * SF Dashboard Overlay * - * Full-screen overlay showing auto-mode progress: milestone/slice/task + * 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. diff --git a/src/resources/extensions/sf/db-writer.js b/src/resources/extensions/sf/db-writer.js index bd6ff6a83..ea8a508a9 100644 --- a/src/resources/extensions/sf/db-writer.js +++ b/src/resources/extensions/sf/db-writer.js @@ -456,7 +456,7 @@ export async function saveDecisionToDb(fields, basePath) { // #2661: When a decision defers a slice, update the slice status in the DB // so the dispatcher skips it. Without this, STATE.md and DECISIONS.md are // in split-brain: the decision says "deferred" but the state still says - // "active", causing auto-mode to keep dispatching the deferred work. + // "active", causing autonomous mode to keep dispatching the deferred work. try { const sliceRef = extractDeferredSliceRef(fields); if (sliceRef) { diff --git a/src/resources/extensions/sf/debug-logger.js b/src/resources/extensions/sf/debug-logger.js index 297c8b8b9..a1da41da9 100644 --- a/src/resources/extensions/sf/debug-logger.js +++ b/src/resources/extensions/sf/debug-logger.js @@ -125,7 +125,7 @@ export function debugPeak(counter, value) { } } /** - * Write the debug summary and disable logging. Call this when auto-mode stops. + * Write the debug summary and disable logging. Call this when autonomous mode stops. * Returns the log file path for user notification. */ export function writeDebugSummary() { diff --git a/src/resources/extensions/sf/dev-workflow-engine.js b/src/resources/extensions/sf/dev-workflow-engine.js index f3ed668ca..0a003a279 100644 --- a/src/resources/extensions/sf/dev-workflow-engine.js +++ b/src/resources/extensions/sf/dev-workflow-engine.js @@ -3,7 +3,7 @@ * * Implements WorkflowEngine by delegating to existing SF state derivation * and dispatch logic. This is the "dev" engine — it wraps the current SF - * auto-mode behavior behind the engine-polymorphic interface. + * autonomous mode behavior behind the engine-polymorphic interface. */ import { resolveDispatch } from "./auto-dispatch.js"; import { loadEffectiveSFPreferences } from "./preferences.js"; @@ -42,7 +42,7 @@ export function bridgeDispatchAction(da) { } // ─── DevWorkflowEngine ─────────────────────────────────────────────────── /** - * DevWorkflowEngine wraps current SF auto-mode behavior behind the engine interface. + * DevWorkflowEngine wraps current SF autonomous mode behavior behind the engine interface. * Implements WorkflowEngine by delegating to existing state derivation and dispatch logic. */ export class DevWorkflowEngine { diff --git a/src/resources/extensions/sf/docs/preferences-reference.md b/src/resources/extensions/sf/docs/preferences-reference.md index ad7878997..01c50a050 100644 --- a/src/resources/extensions/sf/docs/preferences-reference.md +++ b/src/resources/extensions/sf/docs/preferences-reference.md @@ -145,7 +145,7 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea - `commit_type`: string — override the conventional commit type prefix. Must be one of: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `build`, `style`. Default: inferred from diff content. - `main_branch`: string — the primary branch name for new git repos (e.g., `"main"`, `"master"`, `"trunk"`). Also used by `getMainBranch()` as the preferred branch when auto-detection is ambiguous. Default: `"main"`. - `merge_strategy`: `"squash"` or `"merge"` — controls how worktree branches are merged back. `"squash"` combines all commits into one; `"merge"` preserves individual commits. Default: `"squash"`. - - `isolation`: `"worktree"`, `"branch"`, or `"none"` — controls autonomous mode git isolation strategy. `"worktree"` creates a milestone worktree for isolated work; `"branch"` works directly in the project root but creates a milestone branch (useful for submodule-heavy repos); `"none"` works directly on the current branch with no worktree or milestone branch (ideal for step-mode with hot reloads). Default: `"worktree"`. + - `isolation`: `"worktree"`, `"branch"`, or `"none"` — controls autonomous mode git isolation strategy. `"worktree"` creates a milestone worktree for isolated work; `"branch"` works directly in the project root but creates a milestone branch (useful for submodule-heavy repos); `"none"` works directly on the current branch with no worktree or milestone branch (ideal for assisted mode with hot reloads). Default: `"worktree"`. - `manage_gitignore`: boolean — when `false`, SF will not touch `.gitignore` at all. Useful when your project has a strictly managed `.gitignore` and you don't want SF adding entries. Default: `true`. - `worktree_post_create`: string — script to run after a worktree is created (both autonomous mode and manual `/worktree`). Receives `SOURCE_DIR` and `WORKTREE_DIR` as environment variables. Can be absolute or relative to project root. Runs with 30-second timeout. Failure is non-fatal (logged as warning). Default: none. - `auto_pr`: boolean — automatically create a GitHub pull request after a milestone branch is merged. Requires `gh` CLI to be installed. Default: `false`. diff --git a/src/resources/extensions/sf/doctor-proactive.js b/src/resources/extensions/sf/doctor-proactive.js index b7463363e..90d41749d 100644 --- a/src/resources/extensions/sf/doctor-proactive.js +++ b/src/resources/extensions/sf/doctor-proactive.js @@ -1,10 +1,10 @@ /** * SF Doctor — Proactive Healing Layer * - * Three mechanisms for automatic health monitoring during auto-mode: + * Three mechanisms for automatic health monitoring during autonomous mode: * * 1. Pre-dispatch health gate: lightweight check before each unit dispatch. - * Returns blocking issues that should pause auto-mode rather than + * Returns blocking issues that should pause autonomous mode rather than * dispatching into a broken state. * * 2. Health score tracking: tracks issue counts over time to detect @@ -40,7 +40,7 @@ import { } from "./snapshot-safety.js"; import { deriveState } from "./state.js"; -/** In-memory health history for the current auto-mode session. */ +/** In-memory health history for the current autonomous mode session. */ let healthHistory = []; /** Count of consecutive units with unresolved errors. */ let consecutiveErrorUnits = 0; @@ -48,11 +48,11 @@ let consecutiveErrorUnits = 0; let healthUnitIndex = 0; /** Previous progress level for state transition detection. */ let previousProgressLevel = "green"; -/** Callback for state transition notifications. Set by auto-mode. */ +/** Callback for state transition notifications. Set by autonomous mode. */ let onLevelChange = null; /** * Register a callback for progress level transitions (green→yellow, yellow→red, etc.). - * Called once when auto-mode starts. Pass null to unregister. + * Called once when autonomous mode starts. Pass null to unregister. */ export function setLevelChangeCallback(cb) { onLevelChange = cb; @@ -161,7 +161,7 @@ export function getLatestHealthFixes() { return []; } /** - * Reset health tracking state. Called on auto-mode start/stop. + * Reset health tracking state. Called on autonomous mode start/stop. */ export function resetHealthTracking() { healthHistory = []; @@ -424,13 +424,13 @@ export function checkHealEscalation(errors, unresolvedIssues) { }; } /** - * Reset escalation state. Called on auto-mode start/stop. + * Reset escalation state. Called on autonomous mode start/stop. */ export function resetEscalation() { escalationTriggered = false; } /** - * Format a health summary for display in the auto-mode dashboard. + * Format a health summary for display in the autonomous mode dashboard. * Human-readable with full words, not abbreviations. */ export function formatHealthSummary() { @@ -478,7 +478,7 @@ export function formatHealthSummary() { return parts.join(" · "); } /** - * Reset all proactive healing state. Called on auto-mode start/stop. + * Reset all proactive healing state. Called on autonomous mode start/stop. */ export function resetProactiveHealing() { resetHealthTracking(); diff --git a/src/resources/extensions/sf/doctor-runtime-checks.js b/src/resources/extensions/sf/doctor-runtime-checks.js index b877acb7e..dff0ce644 100644 --- a/src/resources/extensions/sf/doctor-runtime-checks.js +++ b/src/resources/extensions/sf/doctor-runtime-checks.js @@ -177,7 +177,7 @@ export async function checkRuntimeHealth( code: "stranded_lock_directory", scope: "project", unitId: "project", - message: `Stranded lock directory "${lockDir}" exists but no live process holds the session lock. This blocks new auto-mode sessions from starting.`, + message: `Stranded lock directory "${lockDir}" exists but no live process holds the session lock. This blocks new autonomous mode sessions from starting.`, file: lockDir, fixable: true, }); @@ -275,7 +275,7 @@ export async function checkRuntimeHealth( state.cycleCounts && typeof state.cycleCounts === "object" && Object.keys(state.cycleCounts).length > 0; - // Only flag if there are actual cycle counts AND no auto-mode is running + // Only flag if there are actual cycle counts AND no autonomous mode is running if (hasCycleCounts) { const lock = readCrashLock(basePath); const autoRunning = lock ? isLockProcessAlive(lock) : false; diff --git a/src/resources/extensions/sf/doctor.js b/src/resources/extensions/sf/doctor.js index 3faf087a7..cd1410f53 100644 --- a/src/resources/extensions/sf/doctor.js +++ b/src/resources/extensions/sf/doctor.js @@ -1067,7 +1067,7 @@ async function updateStateFile(basePath, fixesApplied) { * Rebuild STATE.md from current disk state. * * Invalidates state cache, re-derives from milestone/slice/task directories, - * and rewrites STATE.md. Called from auto-mode post-hooks and doctor recovery paths. + * and rewrites STATE.md. Called from autonomous mode post-hooks and doctor recovery paths. */ export async function rebuildState(basePath) { invalidateAllCaches(); diff --git a/src/resources/extensions/sf/escalation.js b/src/resources/extensions/sf/escalation.js index dcf2f747f..4c7816de6 100644 --- a/src/resources/extensions/sf/escalation.js +++ b/src/resources/extensions/sf/escalation.js @@ -312,9 +312,9 @@ export function resolveEscalation( }; } if (art.respondedAt) { - const wasAuto = art.userRationale?.startsWith("auto-mode:"); + const wasAuto = art.userRationale?.startsWith("autonomous mode:"); const detail = wasAuto - ? ` (auto-accepted in auto-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 /sf escalate resolve. Capture the corrective decision as \`/sf memory note "..."\` so future tasks pick it up.)` : ` (resolved by user → choice="${art.userChoice}").`; return { status: "already-resolved", @@ -352,7 +352,7 @@ export function resolveEscalation( traceId: `escalation:${milestoneId}:${sliceId}:${taskId}`, category: "gate", type: - source === "auto-mode" + source === "autonomous mode" ? "escalation-auto-accepted" : "escalation-user-responded", payload: { diff --git a/src/resources/extensions/sf/execution-policy.js b/src/resources/extensions/sf/execution-policy.js index 195014bdf..7108d1246 100644 --- a/src/resources/extensions/sf/execution-policy.js +++ b/src/resources/extensions/sf/execution-policy.js @@ -11,19 +11,20 @@ */ import { shouldBlockQueueExecutionInSnapshot } from "./bootstrap/write-gate.js"; +import { resolvePermissionProfile } from "./operating-model.js"; import { classifyCommand } from "./safety/destructive-guard.js"; export const EXECUTION_POLICY_PROFILES = { - plan: { - id: "plan", + restricted: { + id: "restricted", permissionProfile: "restricted", filesystem: "read-mostly", network: "read-only", git: "read-only", mutation: "planning-artifacts-only", }, - build: { - id: "build", + normal: { + id: "normal", permissionProfile: "normal", filesystem: "workspace-write", network: "allowed", @@ -57,7 +58,8 @@ export const EXECUTION_POLICY_PROFILES = { * Consumer: tool-call classifiers and future typed headless events. */ export function resolveExecutionPolicyProfile(profileId) { - return EXECUTION_POLICY_PROFILES[profileId] ?? EXECUTION_POLICY_PROFILES.plan; + const id = resolvePermissionProfile(profileId); + return EXECUTION_POLICY_PROFILES[id] ?? EXECUTION_POLICY_PROFILES.restricted; } function classifyBashRisk(command) { @@ -89,7 +91,7 @@ function classifyBashRisk(command) { */ export function classifyExecutionPolicyCall(profileId, toolName, input = "") { const profile = resolveExecutionPolicyProfile(profileId); - if (profile.id === "plan") { + if (profile.id === "restricted") { const queueDecision = shouldBlockQueueExecutionInSnapshot( { verifiedDepthMilestones: [], @@ -154,7 +156,7 @@ export function extractExecutionPolicyInput(toolName, input) { * the comparison-survey direction from Codex/Crush without changing runtime * permissions in this slice. * - * Consumer: SF auto-mode tool_call hook. + * Consumer: SF autonomous mode tool_call hook. */ export function buildExecutionPolicyJournalEntry(args) { const input = extractExecutionPolicyInput( diff --git a/src/resources/extensions/sf/exit-command.js b/src/resources/extensions/sf/exit-command.js index 9489904ed..43b48ee3e 100644 --- a/src/resources/extensions/sf/exit-command.js +++ b/src/resources/extensions/sf/exit-command.js @@ -3,7 +3,7 @@ export function registerExitCommand(pi, deps = {}) { pi.registerCommand("exit", { description: "Exit SF gracefully", handler: async (_args, ctx) => { - // Stop auto-mode first so locks and activity state are cleaned up before shutdown. + // Stop autonomous mode first so locks and activity state are cleaned up before shutdown. // Wrapped in try/catch: if sf-run was updated on disk mid-session, the dynamic // import may resolve a new auto-worktree.js whose static imports reference // exports absent from the process-cached native-git-bridge.js (ESM cache is @@ -16,7 +16,7 @@ export function registerExitCommand(pi, deps = {}) { } catch (e) { const msg = e instanceof Error ? e.message : String(e); ctx.ui?.notify?.( - `Auto-mode cleanup skipped (module version mismatch): ${msg}`, + `Autonomous mode cleanup skipped (module version mismatch): ${msg}`, "warning", ); } diff --git a/src/resources/extensions/sf/forensics.js b/src/resources/extensions/sf/forensics.js index 803307721..7bd09edf3 100644 --- a/src/resources/extensions/sf/forensics.js +++ b/src/resources/extensions/sf/forensics.js @@ -1,5 +1,5 @@ /** - * SF Forensics — Post-mortem investigation of auto-mode failures + * SF Forensics — Post-mortem investigation of autonomous mode failures * * Programmatically scans activity logs, metrics, crash locks, and doctor * diagnostics for anomalies, then hands a structured report to the LLM @@ -130,7 +130,7 @@ async function writeForensicsDedupPref(ctx, enabled) { export async function handleForensics(args, ctx, pi) { if (isAutoActive()) { ctx.ui.notify( - "Cannot run forensics while auto-mode is active. Stop auto-mode first.", + "Cannot run forensics while autonomous mode is active. Stop autonomous mode first.", "error", ); return; @@ -146,7 +146,7 @@ export async function handleForensics(args, ctx, pi) { problemDescription = (await ctx.ui.input( "Describe what went wrong:", - "e.g. auto-mode got stuck on task T03", + "e.g. autonomous mode got stuck on task T03", )) ?? ""; } if (!problemDescription?.trim()) { @@ -397,7 +397,7 @@ const MAX_JOURNAL_RECENT_EVENTS = 20; /** * Intelligently scan journal files for forensic summary. * - * Journal files can be huge (thousands of JSONL entries over weeks of auto-mode). + * Journal files can be huge (thousands of JSONL entries over weeks of autonomous mode). * Instead of loading all entries into memory: * - Only fully parse the most recent N daily files (event counts, flow tracking) * - Line-count older files for approximate totals (no JSON parsing) @@ -772,7 +772,7 @@ function detectJournalAnomalies(journal, anomalies) { type: "journal-stuck", severity: stuckCount >= 3 ? "error" : "warning", summary: `Journal recorded ${stuckCount} stuck-detected event(s)`, - details: `The auto-mode loop detected it was stuck ${stuckCount} time(s). Check journal events for flow IDs and causal chains to trace the root cause.`, + details: `The autonomous mode loop detected it was stuck ${stuckCount} time(s). Check journal events for flow IDs and causal chains to trace the root cause.`, }); } // Detect guard-block events (dispatch was blocked by a guard) diff --git a/src/resources/extensions/sf/git-self-heal.js b/src/resources/extensions/sf/git-self-heal.js index 5182767a0..cbe898c6e 100644 --- a/src/resources/extensions/sf/git-self-heal.js +++ b/src/resources/extensions/sf/git-self-heal.js @@ -2,7 +2,7 @@ * git-self-heal.ts — Automated git state recovery utilities. * * Four synchronous functions for recovering from broken git state - * during auto-mode operations. Uses only `git reset --hard HEAD` — + * during autonomous mode operations. Uses only `git reset --hard HEAD` — * never `git clean` (which would delete untracked .sf/ dirs). * * Observability: Each function returns structured results describing diff --git a/src/resources/extensions/sf/git-service.js b/src/resources/extensions/sf/git-service.js index 51f459acb..a2f05cca3 100644 --- a/src/resources/extensions/sf/git-service.js +++ b/src/resources/extensions/sf/git-service.js @@ -191,7 +191,7 @@ export function readIntegrationBranch(basePath, milestoneId) { /** * Persist the integration branch for a milestone. * - * Called when auto-mode starts on a milestone. Records the branch the user + * Called when autonomous mode starts on a milestone. Records the branch the user * was on at that point, so the milestone worktree merges back to the correct * branch. Idempotent when the branch matches; updates the record when the * user starts from a different branch. @@ -214,7 +214,7 @@ export function writeIntegrationBranch(basePath, milestoneId, branch) { // Validate if (!VALID_BRANCH_NAME.test(branch)) return; // Skip if already recorded with the same branch (idempotent across restarts). - // If recorded with a different branch, update it — the user started auto-mode + // If recorded with a different branch, update it — the user started autonomous mode // from a new branch and expects slices to merge back there (#300). const existingBranch = readIntegrationBranch(basePath, milestoneId); if (existingBranch === branch) return; @@ -622,7 +622,7 @@ export class GitServiceImpl { ) { return this.prefs.main_branch; } - // Check milestone integration branch — recorded when auto-mode starts + // Check milestone integration branch — recorded when autonomous mode starts if (this._milestoneId) { const resolved = resolveMilestoneIntegrationBranch( this.basePath, @@ -634,10 +634,10 @@ export class GitServiceImpl { } const wtName = detectWorktreeName(this.basePath); if (wtName) { - // Auto-mode worktrees use milestone/<MID> branches (wtName = milestone ID) + // Autonomous mode worktrees use milestone/<MID> branches (wtName = milestone ID) const _milestoneBranch = `milestone/${wtName}`; const currentBranch = nativeGetCurrentBranch(this.basePath); - // If we're on a milestone/<MID> branch, use it (auto-mode case) + // If we're on a milestone/<MID> branch, use it (autonomous mode case) if (currentBranch.startsWith("milestone/")) { return currentBranch; } diff --git a/src/resources/extensions/sf/guided-flow-queue.js b/src/resources/extensions/sf/guided-flow-queue.js index eadda4824..edf0bf90f 100644 --- a/src/resources/extensions/sf/guided-flow-queue.js +++ b/src/resources/extensions/sf/guided-flow-queue.js @@ -2,8 +2,8 @@ * SF Queue Management — showQueue, reorder, add, and context builder. * * Self-contained queue UI extracted from guided-flow.ts. - * Safe to run while auto-mode is executing — only writes to future milestone - * directories (which auto-mode won't touch until it reaches them). + * Safe to run while autonomous mode is executing — only writes to future milestone + * directories (which autonomous mode won't touch until it reaches them). */ import { existsSync, readFileSync } from "node:fs"; import { showNextAction } from "../shared/tui.js"; @@ -27,15 +27,15 @@ import { deriveState } from "./state.js"; /** * Queue future milestones via conversational intake. * - * Safe to run while auto-mode is executing — only writes to future milestone - * directories (which auto-mode won't touch until it reaches them) and appends + * Safe to run while autonomous mode is executing — only writes to future milestone + * directories (which autonomous mode won't touch until it reaches them) and appends * to project.md / queue.md. * * The flow: * 1. Build context about all existing milestones (complete, active, pending) * 2. Dispatch the queue prompt — LLM discusses with the user, assesses scope * 3. LLM writes CONTEXT.md files for new milestones (no roadmaps — JIT) - * 4. Auto-mode picks them up naturally when it advances past current work + * 4. Autonomous mode picks them up naturally when it advances past current work * * Root durable artifacts use uppercase names like PROJECT.md and QUEUE.md. */ diff --git a/src/resources/extensions/sf/guided-flow.js b/src/resources/extensions/sf/guided-flow.js index 83e9f529a..d6afb89c1 100644 --- a/src/resources/extensions/sf/guided-flow.js +++ b/src/resources/extensions/sf/guided-flow.js @@ -117,7 +117,7 @@ const TODO_FILE_NAMES = ["todo.md", "TODO.md", "SPEC.md", "spec.md"]; * append its contents to `preamble` so any discuss or bootstrap prompt treats * it as the primary specification. Returns the (possibly enriched) preamble. * - * Called identically in auto-mode bootstrap and interactive discuss — one flow. + * Called identically in autonomous mode bootstrap and interactive discuss — one flow. */ export function injectTodoContext(basePath, preamble) { for (const fname of TODO_FILE_NAMES) { @@ -259,7 +259,7 @@ export function getDiscussionMilestoneId(basePath) { } return null; } -/** Called from agent_end to check if auto-mode should start after discuss */ +/** Called from agent_end to check if autonomous mode should start after discuss */ export function checkAutoStartAfterDiscuss() { const entry = _getPendingAutoStart(); if (!entry) return false; @@ -392,7 +392,7 @@ function parseMilestoneSequenceFromProject(content) { * When a unitType is provided, resolves the user's model preference for that * phase (e.g., models.planning → "plan-milestone", models.discuss → "discuss-milestone") and applies it before * dispatching. This ensures guided-flow dispatches respect the same - * per-phase model preferences that auto-mode uses. + * per-phase model preferences that autonomous mode uses. */ async function dispatchWorkflow( pi, @@ -402,7 +402,7 @@ async function dispatchWorkflow( unitType, ) { // Route through the dynamic routing pipeline (complexity classification, - // tier downgrade, fallback chains) — same path as auto-mode dispatches (#2958). + // tier downgrade, fallback chains) — same path as autonomous mode dispatches (#2958). if (ctx && unitType) { const prefs = loadEffectiveSFPreferences()?.preferences; const result = await selectAndApplyModel( @@ -713,7 +713,7 @@ export async function showHeadlessMilestoneCreation( mkdirSync(milestoneDir, { recursive: true }); // Build and dispatch the headless discuss prompt. const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath); - // Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss) + // Set pending auto start (autonomous mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss) pendingAutoStartMap.set(basePath, { ctx, pi, @@ -731,8 +731,8 @@ export async function showHeadlessMilestoneCreation( } /** * Single discuss-dispatch entry point for new milestones. - * auto=true → headless prompt, rootFiles seed, discuss-milestone workflow, no pendingAutoStartMap - * auto=false → discuss prompt with preparation, discuss-milestone workflow, sets pendingAutoStartMap + * autonomousBootstrap=true → headless prompt, rootFiles seed, discuss-milestone workflow + * autonomousBootstrap=false → discuss prompt with preparation and operator review */ export async function dispatchNewMilestoneDiscuss( ctx, @@ -741,7 +741,7 @@ export async function dispatchNewMilestoneDiscuss( nextId, options, ) { - if (options.auto) { + if (options.autonomousBootstrap) { const seedParts = [options.preamble, ""]; const rootFiles = [ "README.md", @@ -1166,7 +1166,7 @@ export async function showDiscuss(ctx, pi, basePath) { lockData.pid !== process.pid && isSessionLockProcessAlive(lockData); const nextStep = remoteAutoRunning - ? "Auto-mode is already running — use /sf status to check progress." + ? "Autonomous mode is already running — use /sf status to check progress." : "Run /sf to start planning."; ctx.ui.notify( `All ${pendingSlices.length} slices discussed. ${nextStep}`, @@ -1262,7 +1262,7 @@ export async function showDiscuss(ctx, pi, basePath) { /** * Show a picker of queued (pending) milestones and dispatch a discuss flow for * the chosen one. Discussing a queued milestone does NOT activate it — it only - * refines the CONTEXT.md artifact so it is better prepared when auto-mode + * refines the CONTEXT.md artifact so it is better prepared when autonomous mode * eventually reaches it. */ async function showDiscussQueuedMilestone( @@ -1389,7 +1389,7 @@ async function dispatchDiscussForMilestone( */ /** * Self-heal: scan runtime records and clear stale ones left behind when - * auto-mode crashed mid-unit. auto.ts has its own selfHealRuntimeRecords() + * 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 * starts from a clean state regardless of how the previous session ended. @@ -1627,7 +1627,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { // ── Ensure .gitignore has baseline patterns ────────────────────────── ensureGitignore(basePath); untrackRuntimeFiles(basePath); - // ── Self-heal stale runtime records from crashed auto-mode sessions ── + // ── Self-heal stale runtime records from crashed autonomous mode sessions ── selfHealRuntimeRecords(basePath, ctx); const interrupted = await assessInterruptedSession(basePath); if (interrupted.classification === "running") { @@ -1750,7 +1750,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { const isFirst = milestoneIds.length === 0; if (isFirst) { await dispatchNewMilestoneDiscuss(ctx, pi, basePath, nextId, { - auto: false, + autonomousBootstrap: false, preamble: injectTodoContext( basePath, `New project, milestone ${nextId}. Do NOT read or explore .sf/ — it's empty scaffolding.`, @@ -1773,7 +1773,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { }); if (choice === "new_milestone") { await dispatchNewMilestoneDiscuss(ctx, pi, basePath, nextId, { - auto: false, + autonomousBootstrap: false, preamble: injectTodoContext(basePath, `New milestone ${nextId}.`), step: stepMode, }); @@ -1809,7 +1809,7 @@ export async function showWorkflowEntry(ctx, pi, basePath, options) { !!loadEffectiveSFPreferences()?.preferences?.unique_milestone_ids; const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds); await dispatchNewMilestoneDiscuss(ctx, pi, basePath, nextId, { - auto: false, + autonomousBootstrap: false, preamble: injectTodoContext(basePath, `New milestone ${nextId}.`), step: stepMode, }); diff --git a/src/resources/extensions/sf/health-widget-core.js b/src/resources/extensions/sf/health-widget-core.js index 5fc430acf..15ffce6f2 100644 --- a/src/resources/extensions/sf/health-widget-core.js +++ b/src/resources/extensions/sf/health-widget-core.js @@ -90,7 +90,7 @@ export function buildHealthLines(data) { : ""; parts.push(`Last commit: ${relTime}${msg}`); } - // Suggest remote questions if not configured (helps auto-mode users) + // Suggest remote questions if not configured (helps autonomous mode users) if (data.projectState === "active" && !data.remoteQuestionsConfigured) { parts.push("/sf remote telegram"); } diff --git a/src/resources/extensions/sf/history.js b/src/resources/extensions/sf/history.js index 477ce4511..1cad85653 100644 --- a/src/resources/extensions/sf/history.js +++ b/src/resources/extensions/sf/history.js @@ -1,5 +1,5 @@ // SF Extension — Session History View -// Human-readable display of past auto-mode unit executions. +// Human-readable display of past autonomous mode unit executions. import { formatDuration, truncateWithEllipsis, @@ -20,7 +20,7 @@ import { */ export async function handleHistory(args, ctx, basePath) { const ledger = getLedger(); - // If ledger is null (metrics not initialized from auto-mode), try loading from disk + // If ledger is null (metrics not initialized from autonomous mode), try loading from disk let units; if (ledger && ledger.units.length > 0) { units = ledger.units; diff --git a/src/resources/extensions/sf/interrupted-session.js b/src/resources/extensions/sf/interrupted-session.js index 478d1ba0b..8d2a9fc74 100644 --- a/src/resources/extensions/sf/interrupted-session.js +++ b/src/resources/extensions/sf/interrupted-session.js @@ -157,14 +157,14 @@ export function formatInterruptedSessionSummary(assessment) { if (assessment.lock) return [formatCrashInfo(assessment.lock)]; if (assessment.pausedSession?.milestoneId) { return [ - `Paused auto-mode session detected for ${assessment.pausedSession.milestoneId}.`, + `Paused autonomous mode session detected for ${assessment.pausedSession.milestoneId}.`, ]; } - return ["Paused auto-mode session detected."]; + return ["Paused autonomous mode session detected."]; } export function formatInterruptedSessionRunningMessage(assessment) { const pid = assessment.lock?.pid; return pid - ? `Another auto-mode session (PID ${pid}) appears to be running.\nStop it with \`kill ${pid}\` before starting a new session.` - : "Another auto-mode session appears to be running."; + ? `Another autonomous mode session (PID ${pid}) appears to be running.\nStop it with \`kill ${pid}\` before starting a new session.` + : "Another autonomous mode session appears to be running."; } diff --git a/src/resources/extensions/sf/journal.js b/src/resources/extensions/sf/journal.js index 1cd5373c8..1b29cd70a 100644 --- a/src/resources/extensions/sf/journal.js +++ b/src/resources/extensions/sf/journal.js @@ -1,5 +1,5 @@ /** - * SF Event Journal — structured JSONL event log for auto-mode iterations. + * SF Event Journal — structured JSONL event log for autonomous mode iterations. * * Writes daily-rotated JSONL files to `.sf/journal/YYYY-MM-DD.jsonl`. * Zero imports from `auto/` — depends only on node:fs, node:path, and paths.ts. @@ -68,7 +68,7 @@ export function emitJournalEvent(basePath, entry) { { onLocked: "skip" }, ); } catch (err) { - // Non-fatal — journal must never break auto-mode, but record for health signal + // Non-fatal — journal must never break autonomous mode, but record for health signal writeError = err instanceof Error ? err : new Error(String(err)); } // Emit bounded health warning on journal write failure (#sf-moobj36p-ikq3b2) diff --git a/src/resources/extensions/sf/memory-extractor.js b/src/resources/extensions/sf/memory-extractor.js index 8b08bfb33..11da1eef6 100644 --- a/src/resources/extensions/sf/memory-extractor.js +++ b/src/resources/extensions/sf/memory-extractor.js @@ -2,7 +2,7 @@ // // After each unit completes, extracts durable knowledge from the session // transcript and stores it as memory entries. One extraction at a time -// (mutex guard). Fire-and-forget — never blocks auto-mode. +// (mutex guard). Fire-and-forget — never blocks autonomous mode. import { readFileSync, statSync } from "node:fs"; import { applyMemoryActions, @@ -325,7 +325,7 @@ export async function extractMemoriesFromUnit( if (actions2.length > 0) applyMemoryActions(actions2, unitType, unitId); markUnitProcessed(unitKey, activityFile); } catch { - // Non-fatal — memory extraction failure should never affect auto-mode + // Non-fatal — memory extraction failure should never affect autonomous mode } } } finally { diff --git a/src/resources/extensions/sf/memory-sleeper.js b/src/resources/extensions/sf/memory-sleeper.js index 36d5450fb..86b9e22cb 100644 --- a/src/resources/extensions/sf/memory-sleeper.js +++ b/src/resources/extensions/sf/memory-sleeper.js @@ -11,7 +11,7 @@ const bashFailures = new Map(); const MAX_RESULT_CHARS = 6000; const REPEAT_FAILURE_WINDOW_MS = 10 * 60 * 1000; // Maximum number of seen keys to retain before clearing; prevents unbounded -// accumulation across long auto-mode sessions spanning many units. +// accumulation across long autonomous mode sessions spanning many units. const MAX_SEEN_KEYS = 200; export function resetMemorySleeper() { seenKeys.clear(); @@ -105,7 +105,7 @@ function maybeRepeatedFailureSteer(event, text) { severity: "warning", content: buildSteer( "repeated failing command", - `The same bash command has failed ${count} times in this auto-mode unit:\n\n${command}\n\nStop retrying it as-is. Read the error, inspect the relevant files/config, and choose a different repair or verification path.\n\nLatest result excerpt:\n${text.slice(0, 1200)}`, + `The same bash command has failed ${count} times in this autonomous mode unit:\n\n${command}\n\nStop retrying it as-is. Read the error, inspect the relevant files/config, and choose a different repair or verification path.\n\nLatest result excerpt:\n${text.slice(0, 1200)}`, ), }); } diff --git a/src/resources/extensions/sf/metrics.js b/src/resources/extensions/sf/metrics.js index 24003e6a1..1c25f4d19 100644 --- a/src/resources/extensions/sf/metrics.js +++ b/src/resources/extensions/sf/metrics.js @@ -1,7 +1,7 @@ /** * SF Metrics — Token & Cost Tracking * - * Accumulates per-unit usage data across auto-mode sessions. + * Accumulates per-unit usage data across autonomous mode sessions. * Data is extracted from session entries before each context wipe, * written to .sf/metrics.json, and surfaced in the dashboard. * @@ -129,7 +129,7 @@ export function initMetrics(base) { ledger = loadLedger(base); } /** - * Reset in-memory state. Called when auto-mode stops. + * Reset in-memory state. Called when autonomous mode stops. */ export function resetMetrics() { ledger = null; @@ -547,7 +547,7 @@ export function pruneMetricsLedger(base, keepCount) { } /** * Load ledger from disk without initializing in-memory state. - * Used by history/export commands outside of auto-mode. + * Used by history/export commands outside of autonomous mode. */ export function loadLedgerFromDisk(base) { return loadJsonFileOrNull(metricsPath(base), isMetricsLedger); diff --git a/src/resources/extensions/sf/milestone-scope-classifier.js b/src/resources/extensions/sf/milestone-scope-classifier.js index 3024028d1..48302e276 100644 --- a/src/resources/extensions/sf/milestone-scope-classifier.js +++ b/src/resources/extensions/sf/milestone-scope-classifier.js @@ -1,7 +1,7 @@ // SF — Milestone scope classifier (#4781 / ADR-003 companion). // // Pure heuristics over milestone planning fields. Produces a PipelineVariant -// that downstream dispatch logic can use to shape the auto-mode sequence. +// that downstream dispatch logic can use to shape the autonomous mode sequence. // No LLM calls, no file I/O, sub-millisecond. // // Distinct from `complexity-classifier.ts`, which decides *model tier* diff --git a/src/resources/extensions/sf/milestone-summary-classifier.js b/src/resources/extensions/sf/milestone-summary-classifier.js index 296d0e084..62daa2332 100644 --- a/src/resources/extensions/sf/milestone-summary-classifier.js +++ b/src/resources/extensions/sf/milestone-summary-classifier.js @@ -33,7 +33,7 @@ export function classifyMilestoneSummaryContent(content) { } const failureSignal = /(?:^|\n)\s*#\s*BLOCKER\b/i.test(content) || - /auto-mode recovery failed/i.test(content) || + /autonomous mode recovery failed/i.test(content) || /verification\s+failed/i.test(content) || /(?:^|\n)\s*(?:status|verdict|outcome|result)\s*[:=-]\s*not complete\b/i.test( content, diff --git a/src/resources/extensions/sf/model-route-failure.js b/src/resources/extensions/sf/model-route-failure.js index e4097b825..942f61113 100644 --- a/src/resources/extensions/sf/model-route-failure.js +++ b/src/resources/extensions/sf/model-route-failure.js @@ -25,7 +25,7 @@ function dedupeConfiguredRoutes(modelConfig) { /** * Resolve the next configured model route after a provider/model failure. * - * Purpose: keep auto-mode recovery inside the user's explicit primary/fallback + * Purpose: keep autonomous mode recovery inside the user's explicit primary/fallback * chain, skip routes already failed for this unit, and avoid returning the same * provider/model again. * @@ -74,7 +74,7 @@ export function resolveNextConfiguredModelRoute(args) { * Resolve another currently available provider/model route when configured * fallbacks are missing or exhausted. * - * Purpose: keep auto-mode moving on quota/rate-limit/server failures instead + * Purpose: keep autonomous mode moving on quota/rate-limit/server failures instead * of pausing just because the configured fallback chain did not cover every * live provider route. * diff --git a/src/resources/extensions/sf/native-git-bridge.js b/src/resources/extensions/sf/native-git-bridge.js index ace878c1e..ed2ff3dd2 100644 --- a/src/resources/extensions/sf/native-git-bridge.js +++ b/src/resources/extensions/sf/native-git-bridge.js @@ -35,7 +35,7 @@ function isPathBeyondSymlink(basePath, relPath) { } return false; } -// Issue #453: keep auto-mode bookkeeping on the stable git CLI path unless a +// Issue #453: keep autonomous mode bookkeeping on the stable git CLI path unless a // caller explicitly opts into the native helper. const NATIVE_SF_GIT_ENABLED = process.env.SF_ENABLE_NATIVE_SF_GIT === "1"; // ─── Native Module Loading ────────────────────────────────────────────────── @@ -693,7 +693,7 @@ export function nativeAddPaths(basePath, paths) { // intentionally placed that state outside version control. // Also drop any path whose first segment is literally `.sf` — this is a // second independent barrier so that even when `.sf` is a real directory - // (not a symlink) its contents cannot be staged by auto-mode. + // (not a symlink) its contents cannot be staged by autonomous mode. const safe = paths .filter((p) => p.replace(/\\/g, "/").split("/")[0] !== ".sf") .filter((p) => !isPathBeyondSymlink(basePath, p)); diff --git a/src/resources/extensions/sf/notification-store.js b/src/resources/extensions/sf/notification-store.js index 7c8f63476..982fe0e70 100644 --- a/src/resources/extensions/sf/notification-store.js +++ b/src/resources/extensions/sf/notification-store.js @@ -45,8 +45,8 @@ const NOISY_STATUS_PATTERNS = [ /^\[unit\]/, /^Model \[/, /^\[mcp\]/, - /^Step-mode started\b/, - /^Auto-mode resumed\b/, + /^Assisted mode started\b/, + /^Autonomous mode resumed\b/, /^Resuming paused session\b/, /^Resumed paused session\b/, ]; diff --git a/src/resources/extensions/sf/notifications.js b/src/resources/extensions/sf/notifications.js index 55243ef40..48634f2a4 100644 --- a/src/resources/extensions/sf/notifications.js +++ b/src/resources/extensions/sf/notifications.js @@ -1,5 +1,5 @@ // SF Extension — Desktop Notification Helper -// Cross-platform desktop notifications for auto-mode events. +// Cross-platform desktop notifications for autonomous mode events. import { execFileSync } from "node:child_process"; import { CmuxClient, diff --git a/src/resources/extensions/sf/operating-model.js b/src/resources/extensions/sf/operating-model.js new file mode 100644 index 000000000..900f2f742 --- /dev/null +++ b/src/resources/extensions/sf/operating-model.js @@ -0,0 +1,92 @@ +/** + * operating-model.js — canonical SF operating vocabulary. + * + * Purpose: keep UOK, command surfaces, and policy evidence aligned on the same + * run-control and permission-profile model instead of scattered string aliases. + * + * Consumer: UOK kernel ledgers, execution-policy decisions, and command surfaces + * that need stable product terms. + */ + +export const RUN_CONTROL_MODES = Object.freeze([ + "manual", + "assisted", + "autonomous", +]); + +export const PERMISSION_PROFILES = Object.freeze([ + "restricted", + "normal", + "trusted", + "unrestricted", +]); + +/** + * Returns true for a canonical SF run-control mode. + * + * Purpose: let surfaces reject aliases such as "auto" or "full" before they + * leak into UOK ledgers or policy records. + * + * Consumer: command parsers and UOK context builders. + */ +export function isRunControlMode(value) { + return RUN_CONTROL_MODES.includes(value); +} + +/** + * Returns true for a canonical SF permission profile. + * + * Purpose: keep trust/sandbox posture separate from whether the loop continues. + * + * Consumer: execution-policy resolution and UOK context builders. + */ +export function isPermissionProfile(value) { + return PERMISSION_PROFILES.includes(value); +} + +/** + * Resolve an unknown run-control value to the conservative manual mode. + * + * Purpose: fail closed when a caller passes missing or unsupported run-control + * input, preserving the manual/assisted/autonomous vocabulary. + * + * Consumer: UOK context construction and future protocol adapters. + */ +export function resolveRunControlMode(value) { + return isRunControlMode(value) ? value : "manual"; +} + +/** + * Resolve an unknown permission profile to restricted. + * + * Purpose: fail closed when permission posture is absent or misspelled. + * + * Consumer: execution-policy classifiers and UOK ledgers. + */ +export function resolvePermissionProfile(value) { + return isPermissionProfile(value) ? value : "restricted"; +} + +/** + * Derive the UOK run-control mode from the live auto session state. + * + * Purpose: preserve the old `stepMode` runtime bit as storage detail while UOK + * ledgers expose the product vocabulary: assisted or autonomous. + * + * Consumer: runAutoLoopWithUok before recording kernel lifecycle events. + */ +export function runControlModeForSession(session) { + return session?.stepMode ? "assisted" : "autonomous"; +} + +/** + * Choose the default permission profile for a run-control mode. + * + * Purpose: make loop autonomy independent from trust level while giving every + * UOK run a concrete policy posture for evidence and future enforcement. + * + * Consumer: UOK kernel run ledger and audit envelope. + */ +export function defaultPermissionProfileForRunControl(mode) { + return resolveRunControlMode(mode) === "manual" ? "restricted" : "normal"; +} diff --git a/src/resources/extensions/sf/parallel-orchestrator.js b/src/resources/extensions/sf/parallel-orchestrator.js index 73b0c066c..42dd9515c 100644 --- a/src/resources/extensions/sf/parallel-orchestrator.js +++ b/src/resources/extensions/sf/parallel-orchestrator.js @@ -498,12 +498,12 @@ function createMilestoneWorktree(basePath, milestoneId) { * * IMPORTANT: We use `headless --json autonomous` instead of `--print "/sf autonomous"`. * --print mode calls session.prompt() which returns immediately after the - * extension command handler fires, because auto-mode's ctx.newSession() + * extension command handler fires, because autonomous mode's ctx.newSession() * resets the session and unblocks the outer prompt() await. This causes * process.exit(0) to fire before any LLM work happens. See #2792. * * The headless subcommand uses an RPC client that keeps the process alive - * until auto-mode emits a terminal notification or the idle timer fires. + * until autonomous mode emits a terminal notification or the idle timer fires. * It outputs NDJSON events to stdout (with --json), which our * processWorkerLine() parser already understands. */ @@ -795,7 +795,7 @@ function processWorkerLine(basePath, milestoneId, line) { } // tool_execution_start can track current unit if (type === "extension_ui_request" && event.method === "notify") { - // SF auto-mode sends notifications about current unit + // SF autonomous mode sends notifications about current unit const worker = state.workers.get(milestoneId); if (worker) { writeSessionStatus(basePath, { diff --git a/src/resources/extensions/sf/phase-anchor.js b/src/resources/extensions/sf/phase-anchor.js index 2e562e98c..9f789df3a 100644 --- a/src/resources/extensions/sf/phase-anchor.js +++ b/src/resources/extensions/sf/phase-anchor.js @@ -1,6 +1,6 @@ /** * Phase handoff anchors — compact structured summaries written between - * SF auto-mode phases so downstream agents inherit decisions, blockers, + * SF autonomous mode phases so downstream agents inherit decisions, blockers, * and intent without re-inferring from scratch. */ import { existsSync, mkdirSync, readFileSync } from "node:fs"; diff --git a/src/resources/extensions/sf/preferences-models.js b/src/resources/extensions/sf/preferences-models.js index 7e6b00b5b..99183ef4a 100644 --- a/src/resources/extensions/sf/preferences-models.js +++ b/src/resources/extensions/sf/preferences-models.js @@ -235,7 +235,7 @@ export function isProviderAllowedForAdvisor(providerKey, prefs) { ); } /** - * Resolve which model ID to use for a given auto-mode unit type. + * Resolve which model ID to use for a given autonomous mode unit type. * Returns undefined if no model preference is set for this unit type. */ export function resolveModelForUnit(unitType) { @@ -243,7 +243,7 @@ export function resolveModelForUnit(unitType) { return resolved?.primary; } /** - * Resolve model and fallbacks for a given auto-mode unit type. + * Resolve model and fallbacks for a given autonomous mode unit type. * Returns the primary model and ordered fallbacks, or undefined if not configured. * * Supports both legacy string format and extended object format: @@ -422,7 +422,7 @@ export function resolveModelWithFallbacksForUnit(unitType, options = {}) { /** * Resolve the default session model from SF preferences. * - * Used at auto-mode bootstrap to override the session model that was + * Used at autonomous mode bootstrap to override the session model that was * determined by settings.json (defaultProvider/defaultModel). When * PREFERENCES.md (or project preferences) configures an `execution` model * we treat that as the session default. Falls back through execution → @@ -492,11 +492,11 @@ export function resolveDefaultSessionModel(sessionProvider) { * `~/.sf/agent/models.json` (Ollama, vLLM, LM Studio, OpenAI-compatible * proxies, etc.). * - * Used by auto-mode bootstrap to decide whether the session model + * Used by autonomous mode bootstrap to decide whether the session model * (set via `/sf 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 auto-mode tries to start the built-in provider from + * priority — otherwise autonomous mode tries to start the built-in provider from * PREFERENCES.md and fails with "Not logged in · Please run /login" (#4122). * * Reads models.json directly with a lightweight JSON parse to avoid @@ -625,7 +625,7 @@ export function updatePreferencesModels(models) { * When a unit completes on a provider that matches `subscription.provider`, * this function atomically updates `subscription.tokens_used_this_month` on * disk. It is best-effort: any error is silently swallowed so that a - * preference-write failure never disrupts auto-mode. + * preference-write failure never disrupts autonomous mode. * * Pass `provider` (e.g. "anthropic") and `tokensConsumed` from the unit * ledger entry. The function is a no-op when: @@ -666,7 +666,7 @@ export function updateSubscriptionTokensUsed(provider, tokensConsumed) { } writeFileSync(prefsPath, newContent, "utf-8"); } catch { - // Best-effort — never interrupt auto-mode for a pref write failure + // Best-effort — never interrupt autonomous mode for a pref write failure } } /** diff --git a/src/resources/extensions/sf/project-research-policy.js b/src/resources/extensions/sf/project-research-policy.js index eb252d0f9..3354f2061 100644 --- a/src/resources/extensions/sf/project-research-policy.js +++ b/src/resources/extensions/sf/project-research-policy.js @@ -173,7 +173,7 @@ export function finalizeProjectResearchTimeout(basePath, reason) { [ `# ${dimension} research blocker`, ``, - `Auto-mode stopped project research before this dimension produced a durable artifact.`, + `Autonomous mode stopped project research before this dimension produced a durable artifact.`, ``, `**Reason**: ${reason}`, ``, @@ -198,7 +198,7 @@ export function finalizeProjectResearchTimeout(basePath, reason) { [ `# Project research blocker`, ``, - `Auto-mode stopped project research before any usable research dimension completed.`, + `Autonomous mode stopped project research before any usable research dimension completed.`, ``, `**Reason**: ${reason}`, ``, diff --git a/src/resources/extensions/sf/prompt-loader.js b/src/resources/extensions/sf/prompt-loader.js index 2ac6cfaa1..1e77b1b97 100644 --- a/src/resources/extensions/sf/prompt-loader.js +++ b/src/resources/extensions/sf/prompt-loader.js @@ -14,7 +14,7 @@ * set A) can read a newer template from disk (which expects variable set B), * causing a "template declares {{X}} but no value was provided" crash * mid-session — especially for late-loading templates like complete-milestone - * that aren't read until the end of a long auto-mode run. + * that aren't read until the end of a long autonomous mode run. */ import { existsSync, readdirSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; diff --git a/src/resources/extensions/sf/prompts/forensics.md b/src/resources/extensions/sf/prompts/forensics.md index 4054d703c..432d4d136 100644 --- a/src/resources/extensions/sf/prompts/forensics.md +++ b/src/resources/extensions/sf/prompts/forensics.md @@ -16,7 +16,7 @@ SF extension source code is at: `{{sfSourceDir}}` | Domain | Files | |--------|-------| -| **Autonomous mode engine** | `auto.ts` `auto-loop.ts` `auto-dispatch.ts` `auto-start.ts` `auto-supervisor.ts` `auto-timers.ts` `auto-timeout-recovery.ts` `auto-unit-closeout.ts` `auto-post-unit.ts` `auto-verification.ts` `auto-recovery.ts` `auto-worktree.ts` `autonomous model-selection.ts` `auto-budget.ts` `dispatch-guard.ts` | +| **Autonomous mode engine** | `auto.ts` `auto-loop.ts` `auto-dispatch.ts` `auto-start.ts` `auto-supervisor.ts` `auto-timers.ts` `auto-timeout-recovery.ts` `auto-unit-closeout.ts` `auto-post-unit.ts` `auto-verification.ts` `auto-recovery.ts` `auto-worktree.ts` `auto-model-selection.ts` `auto-budget.ts` `dispatch-guard.ts` | | **State & persistence** | `state.ts` `types.ts` `files.ts` `paths.ts` `json-persistence.ts` `atomic-write.ts` | | **Forensics & recovery** | `forensics.ts` `session-forensics.ts` `crash-recovery.ts` `session-lock.ts` | | **Metrics & telemetry** | `metrics.ts` `skill-telemetry.ts` `token-counter.ts` | diff --git a/src/resources/extensions/sf/provider-error-pause.js b/src/resources/extensions/sf/provider-error-pause.js index 6c269ac2a..3210b2eda 100644 --- a/src/resources/extensions/sf/provider-error-pause.js +++ b/src/resources/extensions/sf/provider-error-pause.js @@ -1,5 +1,5 @@ /** - * Pause auto-mode due to a provider error. + * Pause autonomous mode due to a provider error. * * For transient errors (rate limits, server errors, overloaded), schedules * an automatic resume after a delay. For permanent errors (auth, billing), @@ -29,14 +29,14 @@ export async function pauseAutoForProviderError( // Schedule auto-resume after the delay setTimeout(() => { const resumeMsg = options.isRateLimit - ? "Rate limit window elapsed. Resuming auto-mode." - : "Server error recovery delay elapsed. Resuming auto-mode."; + ? "Rate limit window elapsed. Resuming autonomous mode." + : "Server error recovery delay elapsed. Resuming autonomous mode."; ui.notify(resumeMsg, "info"); options.resume(); }, options.retryAfterMs); } else { ui.notify( - `Auto-mode paused due to provider error${errorDetail}`, + `Autonomous mode paused due to provider error${errorDetail}`, "warning", ); await pause(); diff --git a/src/resources/extensions/sf/rethink.js b/src/resources/extensions/sf/rethink.js index f94f4bfc8..a5a68f741 100644 --- a/src/resources/extensions/sf/rethink.js +++ b/src/resources/extensions/sf/rethink.js @@ -21,7 +21,7 @@ import { deriveState } from "./state.js"; export async function handleRethink(_args, ctx, pi) { if (isAutoActive()) { ctx.ui.notify( - "Cannot rethink while auto-mode is active. Stop auto-mode first.", + "Cannot rethink while autonomous mode is active. Stop autonomous mode first.", "error", ); return; @@ -53,7 +53,7 @@ export async function handleRethink(_args, ctx, pi) { ); const commitInstruction = isSfGitignored(basePath) ? "Do not commit planning artifacts — .sf/ is gitignored in this project." - : 'After changes, run `git add .sf/ && git commit -m "docs(sf): rethink milestone plan"` to persist (rethink runs interactively outside auto-mode, so no system auto-commit)'; + : 'After changes, run `git add .sf/ && git commit -m "docs(sf): rethink milestone plan"` to persist (rethink runs interactively outside autonomous mode, so no system auto-commit)'; const content = loadPrompt("rethink", { rethinkData, existingMilestonesContext, diff --git a/src/resources/extensions/sf/roadmap-slices.js b/src/resources/extensions/sf/roadmap-slices.js index af7ead7b9..94b2a8283 100644 --- a/src/resources/extensions/sf/roadmap-slices.js +++ b/src/resources/extensions/sf/roadmap-slices.js @@ -216,7 +216,7 @@ export function parseRoadmapSlices(content) { /** * Fallback parser for prose-style roadmaps where the LLM wrote * slice headers instead of the machine-readable `## Slices` checklist. - * Extracts slice IDs and titles so auto-mode can at least identify + * Extracts slice IDs and titles so autonomous mode can at least identify * slices and plan them. * * Handles these LLM-generated variants: diff --git a/src/resources/extensions/sf/safety/content-validator.js b/src/resources/extensions/sf/safety/content-validator.js index 12558fac2..bc13ca87e 100644 --- a/src/resources/extensions/sf/safety/content-validator.js +++ b/src/resources/extensions/sf/safety/content-validator.js @@ -1,5 +1,5 @@ /** - * Lightweight content validator for auto-mode safety harness. + * Lightweight content validator for autonomous mode safety harness. * Validates that high-value unit outputs contain minimum expected content. * * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net> diff --git a/src/resources/extensions/sf/safety/destructive-guard.js b/src/resources/extensions/sf/safety/destructive-guard.js index b79faf253..dcdfbe6eb 100644 --- a/src/resources/extensions/sf/safety/destructive-guard.js +++ b/src/resources/extensions/sf/safety/destructive-guard.js @@ -1,5 +1,5 @@ /** - * Destructive command classifier for auto-mode safety harness. + * Destructive command classifier for autonomous mode safety harness. * Classifies bash commands and warns on potentially destructive operations. * Does NOT block — only classifies for logging/notification. * diff --git a/src/resources/extensions/sf/safety/evidence-collector.js b/src/resources/extensions/sf/safety/evidence-collector.js index 3cada98d6..5feeb9653 100644 --- a/src/resources/extensions/sf/safety/evidence-collector.js +++ b/src/resources/extensions/sf/safety/evidence-collector.js @@ -1,5 +1,5 @@ /** - * Real-time tool call evidence collector for auto-mode safety harness. + * Real-time tool call evidence collector for autonomous mode safety harness. * Tracks every bash command, file write, and file edit during a unit execution. * Evidence is compared against LLM completion claims in evidence-cross-ref.ts. * diff --git a/src/resources/extensions/sf/safety/evidence-cross-ref.js b/src/resources/extensions/sf/safety/evidence-cross-ref.js index 4a11629e9..6102f8847 100644 --- a/src/resources/extensions/sf/safety/evidence-cross-ref.js +++ b/src/resources/extensions/sf/safety/evidence-cross-ref.js @@ -1,5 +1,5 @@ /** - * Evidence cross-reference for auto-mode safety harness. + * Evidence cross-reference for autonomous mode safety harness. * Compares the LLM's claimed verification evidence (command + exitCode) * against actual bash tool calls recorded by the evidence collector. * diff --git a/src/resources/extensions/sf/safety/file-change-validator.js b/src/resources/extensions/sf/safety/file-change-validator.js index 855d4df8b..06922f32f 100644 --- a/src/resources/extensions/sf/safety/file-change-validator.js +++ b/src/resources/extensions/sf/safety/file-change-validator.js @@ -1,5 +1,5 @@ /** - * Post-unit file change validator for auto-mode safety harness. + * Post-unit file change validator for autonomous mode safety harness. * Compares actual file changes against the task plan's expected output files. * * Uses tasks.expected_output (DB column, populated from per-task ## Expected Output) diff --git a/src/resources/extensions/sf/safety/git-checkpoint.js b/src/resources/extensions/sf/safety/git-checkpoint.js index 5a1faa3b7..87eabb869 100644 --- a/src/resources/extensions/sf/safety/git-checkpoint.js +++ b/src/resources/extensions/sf/safety/git-checkpoint.js @@ -1,5 +1,5 @@ /** - * Pre-unit git checkpoint and rollback for auto-mode safety harness. + * Pre-unit git checkpoint and rollback for autonomous mode safety harness. * Uses the existing refs/sf/ namespace (already pruned by doctor). * * Creates a lightweight ref at HEAD before unit execution. On failure, diff --git a/src/resources/extensions/sf/safety/safety-harness.js b/src/resources/extensions/sf/safety/safety-harness.js index f8e7704f1..e9d160642 100644 --- a/src/resources/extensions/sf/safety/safety-harness.js +++ b/src/resources/extensions/sf/safety/safety-harness.js @@ -1,5 +1,5 @@ /** - * Safety Harness — central module for LLM damage control during auto-mode. + * Safety Harness — central module for LLM damage control during autonomous mode. * Provides types, preference resolution, and orchestration for all safety components. * * Components: diff --git a/src/resources/extensions/sf/schedule-launch-banner.js b/src/resources/extensions/sf/schedule-launch-banner.js index 0a6012cf5..9671bac27 100644 --- a/src/resources/extensions/sf/schedule-launch-banner.js +++ b/src/resources/extensions/sf/schedule-launch-banner.js @@ -7,7 +7,7 @@ * Consumer: cli.ts interactive startup path. */ -import { isAutoDispatchScheduleEntry } from "./schedule/schedule-auto-dispatch.js"; +import { isAutonomousDispatchScheduleEntry } from "./schedule/schedule-autonomous-dispatch.js"; import { createScheduleStore } from "./schedule/schedule-store.js"; /** @@ -33,14 +33,16 @@ export function printScheduleBanner(basePath) { // Best-effort — never block startup } - const autoDispatch = due.filter(isAutoDispatchScheduleEntry); - const passive = due.filter((entry) => !isAutoDispatchScheduleEntry(entry)); + const autonomousDispatch = due.filter(isAutonomousDispatchScheduleEntry); + const passive = due.filter( + (entry) => !isAutonomousDispatchScheduleEntry(entry), + ); if (due.length === 0) return; // Sort by due_at ascending passive.sort((a, b) => new Date(a.due_at) - new Date(b.due_at)); - autoDispatch.sort((a, b) => new Date(a.due_at) - new Date(b.due_at)); + autonomousDispatch.sort((a, b) => new Date(a.due_at) - new Date(b.due_at)); if (passive.length > 0) { const titles = passive @@ -55,13 +57,13 @@ export function printScheduleBanner(basePath) { ); } - if (autoDispatch.length > 0) { + if (autonomousDispatch.length > 0) { const label = - autoDispatch.length === 1 - ? "scheduled auto-dispatch item due" - : "scheduled auto-dispatch items due"; + autonomousDispatch.length === 1 + ? "scheduled autonomous dispatch item due" + : "scheduled autonomous dispatch items due"; process.stderr.write( - `[forge] ${autoDispatch.length} ${label}; autonomous mode will consume project entries.\n`, + `[forge] ${autonomousDispatch.length} ${label}; autonomous mode will consume project entries.\n`, ); } } diff --git a/src/resources/extensions/sf/schedule/schedule-auto-dispatch.js b/src/resources/extensions/sf/schedule/schedule-autonomous-dispatch.js similarity index 91% rename from src/resources/extensions/sf/schedule/schedule-auto-dispatch.js rename to src/resources/extensions/sf/schedule/schedule-autonomous-dispatch.js index ff1ee568b..f664cfa37 100644 --- a/src/resources/extensions/sf/schedule/schedule-auto-dispatch.js +++ b/src/resources/extensions/sf/schedule/schedule-autonomous-dispatch.js @@ -1,5 +1,5 @@ /** - * Schedule Auto Dispatch — first-class execution for due project schedule entries. + * Schedule Autonomous Dispatch — first-class execution for due project schedule entries. * * Purpose: let autonomous mode consume repo-owned scheduled work without a * human approval loop while preserving append-only evidence in the schedule log. @@ -14,7 +14,7 @@ const MAX_RESULT_CHARS = 12_000; /** * Return true when a schedule entry is allowed to run from autonomous mode. * - * Purpose: keep auto execution explicit; passive reminders and global schedule + * Purpose: keep autonomous execution explicit; passive reminders and global schedule * entries remain visible but do not become repo cron jobs accidentally. * * Consumer: auto-dispatch.js schedule rule. @@ -22,9 +22,9 @@ const MAX_RESULT_CHARS = 12_000; * @param {import("./schedule-types.js").ScheduleEntry} entry * @returns {boolean} */ -export function isAutoDispatchScheduleEntry(entry) { +export function isAutonomousDispatchScheduleEntry(entry) { return ( - entry?.auto_dispatch === true && + entry?.autonomous_dispatch === true && (entry.kind === "command" || entry.kind === "prompt") ); } @@ -32,7 +32,7 @@ export function isAutoDispatchScheduleEntry(entry) { /** * Mark a project schedule entry done with a bounded result note. * - * Purpose: make schedule consumption durable so due auto-dispatch entries do + * Purpose: make schedule consumption durable so due autonomous dispatch entries do * not repeat forever after a successful autonomous tick. * * Consumer: executeProjectScheduleCommand and prompt schedule dispatch. @@ -86,7 +86,7 @@ export function markProjectScheduleCancelled(basePath, entry, reason) { /** * Execute one project-scoped command schedule entry from the repo root. * - * Purpose: make `kind: "command", auto_dispatch: true` behave like a repo cron + * 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`. diff --git a/src/resources/extensions/sf/schedule/schedule-store.d.ts b/src/resources/extensions/sf/schedule/schedule-store.d.ts index 88c13bb00..5105c98e5 100644 --- a/src/resources/extensions/sf/schedule/schedule-store.d.ts +++ b/src/resources/extensions/sf/schedule/schedule-store.d.ts @@ -17,8 +17,8 @@ export interface ScheduleEntry { due_at: string; created_at: string; payload: Record<string, unknown>; - created_by: "auto" | "user" | "system"; - auto_dispatch?: boolean; + created_by: "agent" | "user" | "system"; + autonomous_dispatch?: boolean; snoozed_at?: string; } diff --git a/src/resources/extensions/sf/schedule/schedule-store.js b/src/resources/extensions/sf/schedule/schedule-store.js index d3bfe76e7..8b5a4ac50 100644 --- a/src/resources/extensions/sf/schedule/schedule-store.js +++ b/src/resources/extensions/sf/schedule/schedule-store.js @@ -4,7 +4,7 @@ * Purpose: provide durable, queryable storage for schedule entries with * status-grouping semantics (latest entry per ID wins) and time-based queries. * - * Consumer: schedule CLI commands (S02), auto-dispatch reminders, and UI overlays. + * Consumer: schedule CLI commands (S02), autonomous dispatch reminders, and UI overlays. */ import { existsSync, mkdirSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; @@ -33,7 +33,7 @@ const _sfHome = process.env.SF_HOME || join(homedir(), ".sf"); * Purpose: factory that closes over basePath so callers don't repeat it * on every operation. * - * Consumer: CLI commands and auto-mode schedulers. + * Consumer: CLI commands and autonomous mode schedulers. * * @param {string} basePath * @returns {{ diff --git a/src/resources/extensions/sf/schedule/schedule-types.js b/src/resources/extensions/sf/schedule/schedule-types.js index 585c407cf..65a89bedc 100644 --- a/src/resources/extensions/sf/schedule/schedule-types.js +++ b/src/resources/extensions/sf/schedule/schedule-types.js @@ -26,12 +26,12 @@ * "reminder" — general reminder. * "milestone_check" — milestone health check. * "recurring" — cron-based recurring entry. - * "prompt" — autonomous prompt to dispatch when due and auto_dispatch=true. - * "command" — repo command to execute when due and auto_dispatch=true. + * "prompt" — autonomous prompt to dispatch when due and autonomous_dispatch=true. + * "command" — repo command to execute when due and autonomous_dispatch=true. */ /** - * @typedef {("auto"|"user"|"system")} ScheduleCreatedBy + * @typedef {("agent"|"user"|"system")} ScheduleCreatedBy */ // ─── Payloads (discriminated union by kind) ───────────────────────────────── @@ -102,7 +102,7 @@ * @property {string} created_at ISO-8601 timestamp * @property {SchedulePayload} payload Kind-specific data * @property {ScheduleCreatedBy} created_by Who created the entry - * @property {boolean} [auto_dispatch] If true and kind is 'prompt' or 'command', autonomous mode consumes the project entry when due. Defaults false. + * @property {boolean} [autonomous_dispatch] If true and kind is 'prompt' or 'command', autonomous mode consumes the project entry when due. Defaults false. * @property {string} [snoozed_at] ISO-8601 timestamp; set when the entry was last snoozed */ diff --git a/src/resources/extensions/sf/session-forensics.js b/src/resources/extensions/sf/session-forensics.js index 3f16069fd..8e4233ccb 100644 --- a/src/resources/extensions/sf/session-forensics.js +++ b/src/resources/extensions/sf/session-forensics.js @@ -32,7 +32,7 @@ import { sfRuntimeRoot } from "./paths.js"; // MAX_JSONL_BYTES and parseJSONL are imported from ./jsonl-utils.js /** * Find the entries belonging to the last session in a JSONL file. - * Auto-mode creates a new session per unit, so the last session header + * Autonomous mode creates a new session per unit, so the last session header * marks the start of the crashed unit's entries. */ function extractLastSession(entries) { diff --git a/src/resources/extensions/sf/session-lock.js b/src/resources/extensions/sf/session-lock.js index 4c728e703..bfdeab9c1 100644 --- a/src/resources/extensions/sf/session-lock.js +++ b/src/resources/extensions/sf/session-lock.js @@ -1,7 +1,7 @@ /** - * SF Session Lock — OS-level exclusive locking for auto-mode sessions. + * SF Session Lock — OS-level exclusive locking for autonomous mode sessions. * - * Prevents multiple SF processes from running auto-mode concurrently on + * Prevents multiple SF processes from running autonomous mode concurrently on * the same project. Uses proper-lockfile for OS-level file locking (flock/ * lockfile) which eliminates the TOCTOU race condition that existed with * the old advisory JSON lock approach. @@ -251,7 +251,7 @@ export function acquireSessionLock(basePath, sessionInfo) { // Clean up numbered lock file variants from cloud sync conflicts (#1315) cleanupStrayLockFiles(basePath); // Write our lock data first (the content is informational; the OS lock is the real guard). - // sessionId/sessionFile let observers correlate this auto-mode session with the + // sessionId/sessionFile let observers correlate this autonomous mode session with the // .sf/sessions/<...>.jsonl event log (closes sf-moocx6lv-9grpvt). const lockData = { pid: process.pid, @@ -343,8 +343,8 @@ export function acquireSessionLock(basePath, sessionInfo) { // #3218: Provide actionable workaround when lock recovery fails const lockDirPath = lockTarget + ".lock"; const reason = existingPid - ? `Another auto-mode session (PID ${existingPid}) appears to be running.\nStop it with \`kill ${existingPid}\` before starting a new session.` - : `Another auto-mode session lock is stuck on this project.\nRun: rm -rf "${lockDirPath}" && rm -f "${lp}"`; + ? `Another autonomous mode session (PID ${existingPid}) appears to be running.\nStop it with \`kill ${existingPid}\` before starting a new session.` + : `Another autonomous mode session lock is stuck on this project.\nRun: rm -rf "${lockDirPath}" && rm -f "${lp}"`; return { acquired: false, reason, existingPid }; } } @@ -360,7 +360,7 @@ function acquireFallbackLock(basePath, lp, lockData) { if (isPidAlive(existing.pid)) { return { acquired: false, - reason: `Another auto-mode session (PID ${existing.pid}) is already running on this project.`, + reason: `Another autonomous mode session (PID ${existing.pid}) is already running on this project.`, existingPid: existing.pid, }; } diff --git a/src/resources/extensions/sf/session-model-override.js b/src/resources/extensions/sf/session-model-override.js index 5bac734f3..326b0e163 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 auto-mode). + * Store a model override for a session (e.g., from /sf model command in autonomous mode). */ export function setSessionModelOverride(sessionId, override) { const key = normalizeSessionId(sessionId); @@ -22,7 +22,7 @@ export function getSessionModelOverride(sessionId) { return sessionOverrides.get(key); } /** - * Clear the model override for a session (e.g., at auto-mode end). + * Clear the model override for a session (e.g., at autonomous mode end). */ export function clearSessionModelOverride(sessionId) { const key = normalizeSessionId(sessionId); diff --git a/src/resources/extensions/sf/sf-db.js b/src/resources/extensions/sf/sf-db.js index fb356745e..172aad015 100644 --- a/src/resources/extensions/sf/sf-db.js +++ b/src/resources/extensions/sf/sf-db.js @@ -173,7 +173,7 @@ function ensureScheduleTables(db) { snoozed_at TEXT DEFAULT NULL, payload_json TEXT NOT NULL DEFAULT '{}', created_by TEXT NOT NULL DEFAULT 'user', - auto_dispatch INTEGER NOT NULL DEFAULT 0, + autonomous_dispatch INTEGER NOT NULL DEFAULT 0, full_json TEXT NOT NULL DEFAULT '{}', imported_from TEXT DEFAULT NULL ) @@ -184,6 +184,12 @@ function ensureScheduleTables(db) { db.exec( "CREATE INDEX IF NOT EXISTS idx_schedule_entries_scope_due ON schedule_entries(scope, status, due_at)", ); + ensureColumn( + db, + "schedule_entries", + "autonomous_dispatch", + "ALTER TABLE schedule_entries ADD COLUMN autonomous_dispatch INTEGER NOT NULL DEFAULT 0", + ); } function ensureSolverEvalTables(db) { db.exec(` @@ -5468,7 +5474,7 @@ function scheduleEntryFromRow(row) { snoozed_at: row.snoozed_at ?? full.snoozed_at, payload: parseJsonObject(row.payload_json, full.payload ?? {}), created_by: row.created_by, - auto_dispatch: !!row.auto_dispatch, + autonomous_dispatch: !!row.autonomous_dispatch, }; } /** @@ -5488,11 +5494,11 @@ export function insertScheduleEntry(scope, entry, importedFrom = null) { .prepare( `INSERT INTO schedule_entries ( scope, id, schema_version, kind, status, due_at, created_at, - snoozed_at, payload_json, created_by, auto_dispatch, full_json, + snoozed_at, payload_json, created_by, autonomous_dispatch, full_json, imported_from ) VALUES ( :scope, :id, :schema_version, :kind, :status, :due_at, :created_at, - :snoozed_at, :payload_json, :created_by, :auto_dispatch, :full_json, + :snoozed_at, :payload_json, :created_by, :autonomous_dispatch, :full_json, :imported_from )`, ) @@ -5507,7 +5513,7 @@ export function insertScheduleEntry(scope, entry, importedFrom = null) { ":snoozed_at": entry.snoozed_at ?? null, ":payload_json": JSON.stringify(entry.payload ?? {}), ":created_by": entry.created_by ?? "user", - ":auto_dispatch": entry.auto_dispatch ? 1 : 0, + ":autonomous_dispatch": entry.autonomous_dispatch ? 1 : 0, ":full_json": JSON.stringify(full), ":imported_from": importedFrom, }); diff --git a/src/resources/extensions/sf/skill-catalog.js b/src/resources/extensions/sf/skill-catalog.js index ebc6431de..84254ec7d 100644 --- a/src/resources/extensions/sf/skill-catalog.js +++ b/src/resources/extensions/sf/skill-catalog.js @@ -979,7 +979,7 @@ export function isPackInstalled(pack) { * Installs essential packs only (find-skills, skill-creator, etc.). * Stack-specific skills are deferred — once the LLM establishes a design * and creates project files (package.json, firebase.json, etc.), brownfield - * detection will pick them up on the next `sf init` or via auto-mode + * detection will pick them up on the next `sf init` or via autonomous mode * skill discovery. * * Returns the list of installed pack labels. diff --git a/src/resources/extensions/sf/skill-discovery.js b/src/resources/extensions/sf/skill-discovery.js index 33e690bfd..358fc9199 100644 --- a/src/resources/extensions/sf/skill-discovery.js +++ b/src/resources/extensions/sf/skill-discovery.js @@ -1,8 +1,8 @@ /** * SF Skill Discovery * - * Detects skills installed during auto-mode by comparing the current - * skills directory against a snapshot taken at auto-mode start. + * Detects skills installed during autonomous mode by comparing the current + * skills directory against a snapshot taken at autonomous mode start. * * New skills are injected into the system prompt via before_agent_start, * making them visible to all subsequent units without requiring a reload. @@ -15,22 +15,22 @@ import { join } from "node:path"; const SKILLS_DIR = join(homedir(), ".agents", "skills"); const CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills"); const PI_SKILLS_DIR = join(homedir(), ".pi", "agent", "skills"); -/** Snapshot of skill names at auto-mode start */ +/** Snapshot of skill names at autonomous mode start */ let baselineSkills = null; /** - * Snapshot the current skills directory. Call at auto-mode start. + * Snapshot the current skills directory. Call at autonomous mode start. */ export function snapshotSkills() { baselineSkills = new Set(listSkillDirs()); } /** - * Clear the snapshot. Call when auto-mode stops. + * Clear the snapshot. Call when autonomous mode stops. */ export function clearSkillSnapshot() { baselineSkills = null; } /** - * Check if a snapshot is active (auto-mode is running with discovery). + * Check if a snapshot is active (autonomous mode is running with discovery). */ export function hasSkillSnapshot() { return baselineSkills !== null; @@ -75,7 +75,7 @@ export function formatSkillsXml(skills) { ) .join("\n"); return `\n<newly_discovered_skills> -The following skills were installed during this auto-mode session. +The following skills were installed during this autonomous mode session. Use the read tool to load a skill's file when the task matches its description. ${entries} diff --git a/src/resources/extensions/sf/skill-health.js b/src/resources/extensions/sf/skill-health.js index 658d6a10e..92179bdc1 100644 --- a/src/resources/extensions/sf/skill-health.js +++ b/src/resources/extensions/sf/skill-health.js @@ -72,7 +72,7 @@ export function formatSkillHealthReport(report) { lines.push(""); if (report.skills.length === 0) { lines.push( - "No skill telemetry data yet. Run auto-mode to start collecting.", + "No skill telemetry data yet. Run autonomous mode to start collecting.", ); lines.push("Skill usage is recorded per-unit in metrics.json."); return lines.join("\n"); diff --git a/src/resources/extensions/sf/skill-manifest.js b/src/resources/extensions/sf/skill-manifest.js index 49eaf1241..ded6b3576 100644 --- a/src/resources/extensions/sf/skill-manifest.js +++ b/src/resources/extensions/sf/skill-manifest.js @@ -1,6 +1,6 @@ // SF2 + skill-manifest — per-unit-type skill allowlist resolver (RFC #4779) // -// Each auto-mode unit type can declare which skills are relevant to it. This +// Each autonomous mode unit type can declare which skills are relevant to it. This // trims the set of skills considered for activation in the per-unit prompt, // reducing prompt token bloat and sharpening model focus. // @@ -21,7 +21,7 @@ function normalize(name) { return name.trim().toLowerCase(); } /** - * Allowlist per unit type. Keys match unit type identifiers used by auto-mode + * Allowlist per unit type. Keys match unit type identifiers used by autonomous mode * dispatch. Values are normalized skill names. * * Wildcard semantics: a unit type absent from this map resolves to `null` diff --git a/src/resources/extensions/sf/skill-telemetry.js b/src/resources/extensions/sf/skill-telemetry.js index 97f445015..ce545fa72 100644 --- a/src/resources/extensions/sf/skill-telemetry.js +++ b/src/resources/extensions/sf/skill-telemetry.js @@ -62,7 +62,7 @@ export function getAndClearSkills() { return result; } /** - * Clear all telemetry state. Called when auto-mode stops. + * Clear all telemetry state. Called when autonomous mode stops. */ export function resetSkillTelemetry() { availableSkills = []; diff --git a/src/resources/extensions/sf/skills/brainstorming/SKILL.md b/src/resources/extensions/sf/skills/brainstorming/SKILL.md index 5b4ca1ac0..ce2edc1f7 100644 --- a/src/resources/extensions/sf/skills/brainstorming/SKILL.md +++ b/src/resources/extensions/sf/skills/brainstorming/SKILL.md @@ -22,7 +22,7 @@ For trivial changes (typo fix, dependency bump, lint cleanup), skip this skill a ## Skill Chain ``` -← prev: (entry point — user request, sf autonomous-mode trigger, or new milestone) +← prev: (entry point — user request, sf autonomous mode trigger, or new milestone) → next: clarify-spec (if underspecified) → plan-slice → spec-first-tdd ``` @@ -53,7 +53,7 @@ Do NOT advance past this skill until: 1. The idea is anchored in sf's planning hierarchy (SPEC → milestone → slice). 2. You've checked what exists — code search, memory recall, requirements scan. 3. You've identified a real production consumer. -4. A design has been presented and approved (or, in auto-mode, satisfies the portfolio-approved envelope). +4. A design has been presented and approved (or, in autonomous mode, satisfies the portfolio-approved envelope). ## Step 1 — Anchor in sf's Planning Hierarchy diff --git a/src/resources/extensions/sf/skills/sf-headless/SKILL.md b/src/resources/extensions/sf/skills/sf-headless/SKILL.md index 4485413dd..3601bbec6 100644 --- a/src/resources/extensions/sf/skills/sf-headless/SKILL.md +++ b/src/resources/extensions/sf/skills/sf-headless/SKILL.md @@ -31,12 +31,12 @@ sf headless [flags] [command] [args...] ### 1. Create + Execute a Milestone (end-to-end) ```bash -sf headless new-milestone --context spec.md --auto +sf headless new-milestone --context spec.md --autonomous ``` -Reads spec, bootstraps `.sf/`, creates milestone, then chains into auto-mode executing all phases (discuss → research → plan → execute → summarize → complete). +Reads spec, bootstraps `.sf/`, creates milestone, then chains into autonomous mode executing all phases (discuss → research → plan → execute → summarize → complete). -Extra flags for `new-milestone`: `--context <path>` (use `-` for stdin), `--context-text <text>`, `--auto`. +Extra flags for `new-milestone`: `--context <path>` (use `-` for stdin), `--context-text <text>`, `--autonomous`. ### 2. Run All Queued Work @@ -74,7 +74,7 @@ Returns a single JSON object with the full project snapshot — no LLM session, # What phase is the project in? sf headless query | jq '.state.phase' -# What would auto-mode do next? +# What would autonomous mode do next? sf headless query | jq '.next' # Total spend across parallel workers @@ -239,7 +239,7 @@ Quick reference — see [references/commands.md](references/commands.md) for the | `new-milestone` | Create milestone from spec | | `queue` | Queue/reorder milestones | | `history` | View execution history | -| `stop` / `pause` | Control auto-mode | +| `stop` / `pause` | Control autonomous mode | | `dispatch <phase>` | Force specific phase | | `skip` / `undo` | Unit control | | `doctor` | Health check + auto-fix | diff --git a/src/resources/extensions/sf/skills/sf-headless/references/answer-injection.md b/src/resources/extensions/sf/skills/sf-headless/references/answer-injection.md index 1319b6474..feeffce5c 100644 --- a/src/resources/extensions/sf/skills/sf-headless/references/answer-injection.md +++ b/src/resources/extensions/sf/skills/sf-headless/references/answer-injection.md @@ -6,7 +6,7 @@ Pre-supply answers to eliminate interactive prompts during headless execution. ```bash sf headless --answers answers.json auto -sf headless --answers answers.json new-milestone --context spec.md --auto +sf headless --answers answers.json new-milestone --context spec.md --autonomous ``` The `--answers` flag takes a path to a JSON file containing pre-supplied answers and secrets. diff --git a/src/resources/extensions/sf/skills/sf-headless/references/commands.md b/src/resources/extensions/sf/skills/sf-headless/references/commands.md index 1da9c3486..c077f4c7b 100644 --- a/src/resources/extensions/sf/skills/sf-headless/references/commands.md +++ b/src/resources/extensions/sf/skills/sf-headless/references/commands.md @@ -7,9 +7,9 @@ All commands can be run via `sf headless [command]`. | Command | Description | |---------|-------------| | `auto` | Autonomous mode — loop until milestone complete (default) | -| `next` | Step mode — execute one unit, then exit | -| `stop` | Stop auto-mode gracefully | -| `pause` | Pause auto-mode (preserves state, resumable) | +| `next` | Assisted mode — execute one unit, then exit | +| `stop` | Stop autonomous mode gracefully | +| `pause` | Pause autonomous mode (preserves state, resumable) | | `new-milestone` | Create milestone from specification (requires `--context`) | | `dispatch <phase>` | Force-dispatch: research, plan, execute, complete, reassess, uat, replan | | `discuss` | Start guided milestone/slice discussion | @@ -27,7 +27,7 @@ All commands can be run via `sf headless [command]`. | Command | Description | |---------|-------------| -| `skip` | Prevent a unit from auto-mode dispatch | +| `skip` | Prevent a unit from autonomous mode dispatch | | `undo` | Revert last completed unit (--force flag) | | `steer <desc>` | Hard-steer plan documents during execution | | `queue` | Queue and reorder future milestones | @@ -46,9 +46,9 @@ All commands can be run via `sf headless [command]`. | `cleanup` | Remove merged branches or snapshots | | `export` | Export results (--json, --markdown) | | `migrate` | Migrate v1 .planning directory to .sf format | -| `remote` | Control remote auto-mode (slack, discord, status, disconnect) | +| `remote` | Control remote autonomous mode (slack, discord, status, disconnect) | | `inspect` | Show SQLite DB diagnostics (schema, row counts) | -| `forensics` | Post-mortem investigation of auto-mode failures | +| `forensics` | Post-mortem investigation of autonomous mode failures | ## Phases diff --git a/src/resources/extensions/sf/skills/spec-first-tdd/SKILL.md b/src/resources/extensions/sf/skills/spec-first-tdd/SKILL.md index f6f026477..2d709327f 100644 --- a/src/resources/extensions/sf/skills/spec-first-tdd/SKILL.md +++ b/src/resources/extensions/sf/skills/spec-first-tdd/SKILL.md @@ -19,7 +19,7 @@ NO JUDGMENT CALL WITHOUT A CONFIDENCE AND FALSIFIER. ## When to Run -- A slice has been planned, the user has approved it (`go`, or auto-mode portfolio approval), and the contract is decision-complete. +- A slice has been planned, the user has approved it (`go`, or autonomous mode portfolio approval), and the contract is decision-complete. - A bug fix needs a regression test. - A reviewer found a real behaviour gap (invoke from `receiving-code-review`). diff --git a/src/resources/extensions/sf/spec-projections.js b/src/resources/extensions/sf/spec-projections.js index d77b54eca..9c2e2320c 100644 --- a/src/resources/extensions/sf/spec-projections.js +++ b/src/resources/extensions/sf/spec-projections.js @@ -155,7 +155,9 @@ export function generateOperatingModelSpec(basePath) { "- **assisted** — SF proposes and executes bounded steps, with human approval for important or uncertain actions.", "- **autonomous** — SF continues through the flow until policy, evidence, budget, or completion stops it.", "", - "`auto` is acceptable only as shorthand in commands, flags, and UI labels. The concept name is **autonomous**.", + "`auto` is not a run-control mode. Use **autonomous** for continuous run control; use **assisted** for bounded human-guided progression.", + "", + "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", "", @@ -168,6 +170,8 @@ export function generateOperatingModelSpec(basePath) { "", "Run control and permission profile are independent. For example, `autonomous + restricted` can keep going with narrow permissions, while `manual + trusted` still asks before each consequential step but can perform broader approved actions.", "", + "UOK kernel records and execution-policy decisions carry `permissionProfile` as the trust posture. Permission expansion never implies autonomous continuation.", + "", "## Naming Rules", "", "- Say **flow** for the shared planning/execution engine.", diff --git a/src/resources/extensions/sf/state.js b/src/resources/extensions/sf/state.js index 01955e9d4..a0c695a07 100644 --- a/src/resources/extensions/sf/state.js +++ b/src/resources/extensions/sf/state.js @@ -47,7 +47,7 @@ import { logWarning } from "./workflow-logger.js"; * A "ghost" milestone directory contains only META.json (and no substantive * files like CONTEXT, CONTEXT-DRAFT, ROADMAP, or SUMMARY). These appear when * a milestone is created but never initialised. Treating them as active causes - * auto-mode to stall or falsely declare completion. + * autonomous mode to stall or falsely declare completion. * * However, a milestone is NOT a ghost if: * - It has a DB row with a meaningful status (queued, active, etc.) — the DB @@ -56,7 +56,7 @@ import { logWarning } from "./workflow-logger.js"; * legitimately created and is expected to be populated. * * Fixes #2921: queued milestones with worktrees were incorrectly classified - * as ghosts, causing auto-mode to skip them entirely. + * as ghosts, causing autonomous mode to skip them entirely. */ export function isGhostMilestone(basePath, mid) { // If the milestone has a DB row, it's usually a known milestone — not a ghost. @@ -1818,7 +1818,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 - // resolve before auto-mode will continue. Falls through (returns null + // resolve before autonomous mode will continue. Falls through (returns null // from detectPendingEscalation) when nothing is paused — no perf cost // in the common path. { diff --git a/src/resources/extensions/sf/templates/PREFERENCES.md b/src/resources/extensions/sf/templates/PREFERENCES.md index a5f270df2..3d8d3e530 100644 --- a/src/resources/extensions/sf/templates/PREFERENCES.md +++ b/src/resources/extensions/sf/templates/PREFERENCES.md @@ -41,17 +41,17 @@ phases: # SF ADR-011 P2: mid-execution escalation. When true, sf_task_complete honors # an optional escalation: { question, options, recommendation, ... } payload. # The agent's choice carries forward as a hard constraint into the next - # executor. See escalation_auto_accept for whether auto-mode pauses or + # executor. See escalation_auto_accept for whether autonomous mode pauses or # auto-accepts. mid_execution_escalation: - # When true (default), an escalation in auto-mode auto-accepts the agent's - # recommendation and continues — auto-mode is autonomous. The user can + # 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 # SF's pause-and-ask behavior (loop halts until user runs # `/sf escalate resolve`). escalation_auto_accept: # Deep-mode planning gate (top-level, not under phases). When set to "deep", -# auto-mode runs project-level discussion → requirements → optional research +# autonomous mode runs project-level discussion → requirements → optional research # BEFORE any milestone work. Default: light (current SF behavior). planning_depth: dynamic_routing: diff --git a/src/resources/extensions/sf/tests/auto-command-full-removed.test.mjs b/src/resources/extensions/sf/tests/auto-command-full-removed.test.mjs deleted file mode 100644 index a41c3d080..000000000 --- a/src/resources/extensions/sf/tests/auto-command-full-removed.test.mjs +++ /dev/null @@ -1,29 +0,0 @@ -import assert from "node:assert/strict"; -import { test } from "vitest"; -import { handleAutoCommand } from "../commands/handlers/auto.js"; - -function makeCtx() { - const notifications = []; - return { - ctx: { - ui: { - notify(message, level = "info") { - notifications.push({ message, level }); - }, - }, - }, - notifications, - }; -} - -test("handleAutoCommand_when_autonomous_full_requested_reports_removed_flag", async () => { - const { ctx, notifications } = makeCtx(); - - const handled = await handleAutoCommand("autonomous --full", ctx, {}); - - assert.equal(handled, true); - assert.equal(notifications.length, 1); - assert.equal(notifications[0].level, "warning"); - assert.match(notifications[0].message, /was removed/); - assert.match(notifications[0].message, /Use `\/sf autonomous`/); -}); diff --git a/src/resources/extensions/sf/tests/autonomous-command-args.test.mjs b/src/resources/extensions/sf/tests/autonomous-command-args.test.mjs new file mode 100644 index 000000000..6c743567f --- /dev/null +++ b/src/resources/extensions/sf/tests/autonomous-command-args.test.mjs @@ -0,0 +1,32 @@ +import assert from "node:assert/strict"; +import { test } from "vitest"; +import { handleAutonomousCommand } from "../commands/handlers/autonomous.js"; + +function makeCtx() { + const notifications = []; + return { + ctx: { + ui: { + notify(message, level = "info") { + notifications.push({ message, level }); + }, + }, + }, + notifications, + }; +} + +test("handleAutonomousCommand_when_unsupported_argument_requested_reports_error", async () => { + const { ctx, notifications } = makeCtx(); + + const handled = await handleAutonomousCommand("autonomous --turbo", ctx, {}); + + 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, /--turbo/); +}); diff --git a/src/resources/extensions/sf/tests/commands-schedule.test.mjs b/src/resources/extensions/sf/tests/commands-schedule.test.mjs index 8390ba383..da9e6ee99 100644 --- a/src/resources/extensions/sf/tests/commands-schedule.test.mjs +++ b/src/resources/extensions/sf/tests/commands-schedule.test.mjs @@ -103,7 +103,7 @@ describe("handleSchedule", () => { "0m", "--kind", "command", - "--auto-dispatch", + "--autonomous-dispatch", "--", "node", "-e", @@ -115,7 +115,7 @@ describe("handleSchedule", () => { const entries = createScheduleStore(testDir).readEntries("project"); assert.equal(entries.length, 1); assert.equal(entries[0].kind, "command"); - assert.equal(entries[0].auto_dispatch, true); + assert.equal(entries[0].autonomous_dispatch, true); assert.equal( entries[0].payload.command, "node -e 'require('\\''fs'\\'').writeFileSync('\\''argv-cron.txt'\\'','\\''ok'\\'')'", @@ -124,6 +124,21 @@ describe("handleSchedule", () => { await handleSchedule(["run", entries[0].id.slice(0, 8)], mockCtx()); assert.equal(readFileSync(join(testDir, "argv-cron.txt"), "utf-8"), "ok"); }); + + it("rejects removed autonomous dispatch shorthand", async () => { + const ctx = mockCtx(); + await handleSchedule( + "add --in 0m --kind command --auto-dispatch -- echo old", + ctx, + ); + + assert.equal(ctx.notifications[0].type, "warning"); + assert.match(ctx.notifications[0].msg, /--autonomous-dispatch/); + assert.equal( + createScheduleStore(testDir).readEntries("project").length, + 0, + ); + }); }); describe("list", () => { @@ -154,7 +169,7 @@ describe("handleSchedule", () => { "1h", "--kind", "command", - "--auto-dispatch", + "--autonomous-dispatch", "--", "node", "-e", @@ -170,7 +185,7 @@ describe("handleSchedule", () => { assert.equal(listed.length, 1); assert.equal(listed[0].kind, "command"); - assert.equal(listed[0].auto_dispatch, true); + assert.equal(listed[0].autonomous_dispatch, true); assert.equal(listed[0].payload.command, stored.payload.command); }); }); @@ -255,7 +270,7 @@ describe("handleSchedule", () => { "0m", "--kind", "command", - "--auto-dispatch", + "--autonomous-dispatch", "--", "node", "-e", diff --git a/src/resources/extensions/sf/tests/doctor-flow-audit-auto-cleanup.test.mjs b/src/resources/extensions/sf/tests/doctor-flow-audit-auto-cleanup.test.mjs index 080699ad0..fce5993aa 100644 --- a/src/resources/extensions/sf/tests/doctor-flow-audit-auto-cleanup.test.mjs +++ b/src/resources/extensions/sf/tests/doctor-flow-audit-auto-cleanup.test.mjs @@ -126,7 +126,7 @@ describe("flow audit optional child cleanup", () => { JSON.stringify({ ts: "2026-05-06T10:00:00.000Z", severity: "error", - message: "Auto-mode paused: old provider failure", + message: "Autonomous mode paused: old provider failure", }), JSON.stringify({ ts: "2026-05-06T10:45:00.000Z", @@ -155,7 +155,7 @@ describe("flow audit optional child cleanup", () => { JSON.stringify({ ts: "2026-05-06T11:45:00.000Z", severity: "error", - message: "Auto-mode paused: recent provider failure", + message: "Autonomous mode paused: recent provider failure", }) + "\n", "utf-8", ); @@ -167,7 +167,7 @@ describe("flow audit optional child cleanup", () => { assert.equal(report.ok, false); assert.deepEqual(report.lastErrors, [ - "Auto-mode paused: recent provider failure", + "Autonomous mode paused: recent provider failure", ]); assert.equal( report.recommendedAction, diff --git a/src/resources/extensions/sf/tests/execution-policy.test.mjs b/src/resources/extensions/sf/tests/execution-policy.test.mjs index 18d28110a..8aced0c0d 100644 --- a/src/resources/extensions/sf/tests/execution-policy.test.mjs +++ b/src/resources/extensions/sf/tests/execution-policy.test.mjs @@ -8,28 +8,36 @@ import { } from "../execution-policy.js"; describe("execution policy profiles", () => { - test("resolveExecutionPolicyProfile_when_unknown_returns_plan_profile", () => { - assert.equal(resolveExecutionPolicyProfile("missing").id, "plan"); + test("resolveExecutionPolicyProfile_when_unknown_returns_restricted_profile", () => { + assert.equal(resolveExecutionPolicyProfile("missing").id, "restricted"); }); - test("classifyExecutionPolicyCall_plan_allows_read_only_bash", () => { - const decision = classifyExecutionPolicyCall("plan", "bash", "git status"); + test("classifyExecutionPolicyCall_restricted_allows_read_only_bash", () => { + const decision = classifyExecutionPolicyCall( + "restricted", + "bash", + "git status", + ); assert.equal(decision.allowed, true); - assert.equal(decision.profile, "plan"); + assert.equal(decision.profile, "restricted"); assert.equal(decision.permissionProfile, "restricted"); }); - test("classifyExecutionPolicyCall_plan_blocks_source_write", () => { - const decision = classifyExecutionPolicyCall("plan", "write", "src/app.ts"); + test("classifyExecutionPolicyCall_restricted_blocks_source_write", () => { + const decision = classifyExecutionPolicyCall( + "restricted", + "write", + "src/app.ts", + ); assert.equal(decision.allowed, false); assert.match(decision.reason, /planning tool/i); }); - test("classifyExecutionPolicyCall_plan_allows_sf_artifact_write", () => { + test("classifyExecutionPolicyCall_restricted_allows_sf_artifact_write", () => { const decision = classifyExecutionPolicyCall( - "plan", + "restricted", "write", ".sf/milestones/M001/CONTEXT.md", ); @@ -37,9 +45,9 @@ describe("execution policy profiles", () => { assert.equal(decision.allowed, true); }); - test("classifyExecutionPolicyCall_build_marks_destructive_bash_but_allows_for_later_gate", () => { + test("classifyExecutionPolicyCall_normal_marks_destructive_bash_but_allows_for_later_gate", () => { const decision = classifyExecutionPolicyCall( - "build", + "normal", "bash", "git reset --hard", ); @@ -85,7 +93,7 @@ describe("execution policy profiles", () => { test("buildExecutionPolicyJournalEntry_records_tool_decision", () => { const entry = buildExecutionPolicyJournalEntry({ now: () => "2026-05-07T16:30:00.000Z", - profileId: "plan", + profileId: "restricted", unit: { type: "execute-task", id: "M001/S01/T01" }, event: { toolCallId: "call-1", @@ -99,7 +107,7 @@ describe("execution policy profiles", () => { assert.equal(entry.data.toolName, "write"); assert.equal(entry.data.input, "src/app.ts"); assert.equal(entry.data.decision.allowed, false); - assert.equal(entry.data.decision.profile, "plan"); + assert.equal(entry.data.decision.profile, "restricted"); assert.equal(entry.data.decision.permissionProfile, "restricted"); }); }); diff --git a/src/resources/extensions/sf/tests/extension-models-gap5.test.mjs b/src/resources/extensions/sf/tests/extension-models-gap5.test.mjs index af901326d..9aa68e5d9 100644 --- a/src/resources/extensions/sf/tests/extension-models-gap5.test.mjs +++ b/src/resources/extensions/sf/tests/extension-models-gap5.test.mjs @@ -5,7 +5,7 @@ * those models are discoverable and selectable by the model router. * * Consumer: Extensions like claude-code-cli register custom models; - * auto-mode dispatch must be able to select them. + * autonomous mode dispatch must be able to select them. */ import { describe, expect, it } from "vitest"; diff --git a/src/resources/extensions/sf/tests/notification-detection-headless-medium-low.test.mjs b/src/resources/extensions/sf/tests/notification-detection-headless-medium-low.test.mjs index fe30138ac..11334fa35 100644 --- a/src/resources/extensions/sf/tests/notification-detection-headless-medium-low.test.mjs +++ b/src/resources/extensions/sf/tests/notification-detection-headless-medium-low.test.mjs @@ -104,7 +104,7 @@ describe("S08 MEDIUM: notification + detection + headless", () => { }); describe("S08 LOW: notification + detection + headless", () => { - describe("auto-mode visibility restrictions", () => { + describe("autonomous mode visibility restrictions", () => { it("should detect milestone ready in text delta", () => { expect(isMilestoneReadyText("milestone M008 ready")).toBe(true); }); diff --git a/src/resources/extensions/sf/tests/operating-model.test.mjs b/src/resources/extensions/sf/tests/operating-model.test.mjs new file mode 100644 index 000000000..ed6fc9f06 --- /dev/null +++ b/src/resources/extensions/sf/tests/operating-model.test.mjs @@ -0,0 +1,46 @@ +import assert from "node:assert/strict"; +import { describe, test } from "vitest"; +import { + defaultPermissionProfileForRunControl, + isPermissionProfile, + isRunControlMode, + PERMISSION_PROFILES, + RUN_CONTROL_MODES, + resolvePermissionProfile, + resolveRunControlMode, + runControlModeForSession, +} from "../operating-model.js"; + +describe("operating model vocabulary", () => { + test("runControlModes_are_manual_assisted_autonomous_only", () => { + assert.deepEqual(RUN_CONTROL_MODES, ["manual", "assisted", "autonomous"]); + assert.equal(isRunControlMode("auto"), false); + assert.equal(isRunControlMode("full"), false); + }); + + test("permissionProfiles_are_trust_posture_not_run_control", () => { + assert.deepEqual(PERMISSION_PROFILES, [ + "restricted", + "normal", + "trusted", + "unrestricted", + ]); + assert.equal(isPermissionProfile("autonomous"), false); + }); + + test("resolvers_fail_closed", () => { + assert.equal(resolveRunControlMode("auto"), "manual"); + assert.equal(resolvePermissionProfile("full"), "restricted"); + }); + + test("session_step_bit_derives_assisted_or_autonomous_run_control", () => { + assert.equal(runControlModeForSession({ stepMode: true }), "assisted"); + assert.equal(runControlModeForSession({ stepMode: false }), "autonomous"); + }); + + test("default_permission_profile_is_independent_from_autonomous_looping", () => { + assert.equal(defaultPermissionProfileForRunControl("autonomous"), "normal"); + assert.equal(defaultPermissionProfileForRunControl("assisted"), "normal"); + assert.equal(defaultPermissionProfileForRunControl("manual"), "restricted"); + }); +}); diff --git a/src/resources/extensions/sf/tests/schedule-dispatch.test.mjs b/src/resources/extensions/sf/tests/schedule-dispatch.test.mjs index 301a6620a..bc78c506a 100644 --- a/src/resources/extensions/sf/tests/schedule-dispatch.test.mjs +++ b/src/resources/extensions/sf/tests/schedule-dispatch.test.mjs @@ -1,10 +1,10 @@ /** - * Schedule Auto-Dispatch Rule tests. + * Schedule Autonomous Dispatch Rule tests. * * Purpose: verify the schedule dispatch rule in auto-dispatch.js: - * - consumes repo-scoped due auto_dispatch commands between autonomous units + * - consumes repo-scoped due autonomous_dispatch commands between autonomous units * - returns null when no matching due entries - * - dispatches due auto_dispatch prompts as custom-step units + * - dispatches due autonomous_dispatch prompts as custom-step units * - non-fatal on errors * * Consumer: CI test runner (vitest). @@ -57,7 +57,7 @@ describe("schedule-dispatch", () => { async function runScheduleRule(state, basePath = testDir) { const rule = DISPATCH_RULES.find( - (r) => r.name === "schedule auto-dispatch", + (r) => r.name === "schedule autonomous dispatch", ); assert.ok(rule, "schedule dispatch rule not found"); return rule.match({ state, basePath }); @@ -69,14 +69,14 @@ describe("schedule-dispatch", () => { assert.equal(result, null); }); - it("executes due auto_dispatch command from the project root", async () => { + it("executes due autonomous_dispatch command from the project root", async () => { const store = createScheduleStore(testDir); store.appendEntry( "project", makeEntry({ kind: "command", due_at: new Date(Date.now() - 60 * 60 * 1000).toISOString(), - auto_dispatch: true, + autonomous_dispatch: true, payload: { command: "node -e \"require('fs').writeFileSync('cron-ran.txt', process.cwd())\"", @@ -98,14 +98,14 @@ describe("schedule-dispatch", () => { assert.equal(entries[0].payload.result_note, "command completed"); }); - it("returns null when due entries have auto_dispatch=false", async () => { + it("returns null when due entries have autonomous_dispatch=false", async () => { const store = createScheduleStore(testDir); store.appendEntry( "project", makeEntry({ kind: "command", due_at: new Date(Date.now() - 60 * 60 * 1000).toISOString(), - auto_dispatch: false, + autonomous_dispatch: false, payload: { command: "node -e \"require('fs').writeFileSync('should-not-run.txt', 'bad')\"", @@ -125,7 +125,7 @@ describe("schedule-dispatch", () => { "project", makeEntry({ due_at: new Date(Date.now() - 60 * 60 * 1000).toISOString(), - auto_dispatch: true, + autonomous_dispatch: true, kind: "milestone_check", }), ); @@ -135,14 +135,14 @@ describe("schedule-dispatch", () => { assert.equal(result, null); }); - it("pauses when due auto_dispatch command fails and records evidence", async () => { + it("pauses when due autonomous_dispatch command fails and records evidence", async () => { const store = createScheduleStore(testDir); store.appendEntry( "project", makeEntry({ kind: "command", due_at: new Date(Date.now() - 60 * 60 * 1000).toISOString(), - auto_dispatch: true, + autonomous_dispatch: true, payload: { command: "node -e \"process.stderr.write('boom'); process.exit(9)\"", }, @@ -168,7 +168,7 @@ describe("schedule-dispatch", () => { id: "SECOND", kind: "command", due_at: "2024-01-02T00:00:00.000Z", - auto_dispatch: true, + autonomous_dispatch: true, payload: { command: "node -e \"require('fs').writeFileSync('picked.txt', 'second')\"", @@ -181,7 +181,7 @@ describe("schedule-dispatch", () => { id: "FIRST", kind: "command", due_at: "2024-01-01T00:00:00.000Z", - auto_dispatch: true, + autonomous_dispatch: true, payload: { command: "node -e \"require('fs').writeFileSync('picked.txt', 'first')\"", @@ -199,7 +199,7 @@ describe("schedule-dispatch", () => { assert.equal(entries.find((e) => e.id === "SECOND")?.status, "pending"); }); - it("dispatches due auto_dispatch prompt as a custom step and consumes the trigger", async () => { + it("dispatches due autonomous_dispatch prompt as a custom step and consumes the trigger", async () => { const store = createScheduleStore(testDir); store.appendEntry( "project", @@ -207,7 +207,7 @@ describe("schedule-dispatch", () => { id: "PROMPT", kind: "prompt", due_at: "2024-01-01T00:00:00.000Z", - auto_dispatch: true, + autonomous_dispatch: true, payload: { prompt: "refresh schedule diagnostics" }, }), ); 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 5652792a6..9c8b638c9 100644 --- a/src/resources/extensions/sf/tests/schedule-launch-banner.test.mjs +++ b/src/resources/extensions/sf/tests/schedule-launch-banner.test.mjs @@ -113,20 +113,20 @@ describe("schedule-launch-banner", () => { assert.ok(output.includes("Second")); }); - it("labels due auto-dispatch project entries as autonomous work", () => { + it("labels due autonomous dispatch project entries as autonomous work", () => { const store = createScheduleStore(testDir); store.appendEntry( "project", makeEntry({ kind: "command", due_at: "2024-01-01T00:00:00.000Z", - auto_dispatch: true, + autonomous_dispatch: true, payload: { command: 'node -e "process.exit(0)"' }, }), ); const output = captureStderr(() => printScheduleBanner(testDir)); - assert.ok(output.includes("1 scheduled auto-dispatch item due")); + assert.ok(output.includes("1 scheduled autonomous dispatch item due")); assert.ok( output.includes("autonomous mode will consume project entries"), ); diff --git a/src/resources/extensions/sf/tests/uok-parity-report.test.mjs b/src/resources/extensions/sf/tests/uok-parity-report.test.mjs index b18c7ce15..58af08f1e 100644 --- a/src/resources/extensions/sf/tests/uok-parity-report.test.mjs +++ b/src/resources/extensions/sf/tests/uok-parity-report.test.mjs @@ -343,6 +343,10 @@ test("runAutoLoopWithUok_success_writes_balanced_run_id_heartbeats", async () => assert.equal(events[1].phase, "exit"); assert.equal(events[1].status, "ok"); assert.equal(events[0].runId, events[1].runId); + assert.equal(events[0].runControl, "autonomous"); + assert.equal(events[0].permissionProfile, "normal"); + assert.equal(events[1].runControl, "autonomous"); + assert.equal(events[1].permissionProfile, "normal"); const report = JSON.parse( readFileSync( join(projectRoot, ".sf", "runtime", "uok-parity-report.json"), @@ -353,6 +357,30 @@ test("runAutoLoopWithUok_success_writes_balanced_run_id_heartbeats", async () => assert.equal(hasCurrentParityWarning(report), false); }); +test("runAutoLoopWithUok_assisted_trusted_profile_is_recorded_at_kernel_boundary", async () => { + const projectRoot = makeProject(); + const state = { basePath: projectRoot, autoStartTime: NOW, stepMode: true }; + + await runAutoLoopWithUok({ + ctx: testCtx("session-assisted"), + pi: {}, + s: state, + deps: testDeps({ uok: { permission_profile: "trusted" } }), + async runKernelLoop() {}, + async runStandardLoop() { + throw new Error("standard loop should not run when uok is enabled"); + }, + }); + + const events = readProjectParityEvents(projectRoot); + assert.equal(events[0].runControl, "assisted"); + assert.equal(events[0].permissionProfile, "trusted"); + assert.equal(events[0].flags.runControl, "assisted"); + assert.equal(events[0].flags.permissionProfile, "trusted"); + assert.equal(events[1].runControl, "assisted"); + assert.equal(events[1].permissionProfile, "trusted"); +}); + test("runAutoLoopWithUok_ignores_legacy_disabled_flag_and_uses_kernel_path", async () => { const projectRoot = makeProject(); const state = { basePath: projectRoot, autoStartTime: NOW }; diff --git a/src/resources/extensions/sf/triage-resolution.js b/src/resources/extensions/sf/triage-resolution.js index 9ceef26e6..3571e1ed1 100644 --- a/src/resources/extensions/sf/triage-resolution.js +++ b/src/resources/extensions/sf/triage-resolution.js @@ -152,7 +152,7 @@ export function executeBacktrack(basePath, currentMilestoneId, capture) { `**Target:** ${targetMilestoneId ?? "(user to specify)"}`, `**Triggered:** ${ts}`, ``, - `Auto-mode was paused by this backtrack directive. The user directed`, + `Autonomous mode was paused by this backtrack directive. The user directed`, `that the current milestone (${currentMilestoneId}) be abandoned and work`, `should return to ${targetMilestoneId ?? "a previous milestone"}.`, ``, @@ -160,7 +160,7 @@ export function executeBacktrack(basePath, currentMilestoneId, capture) { ``, `1. Review what went wrong in ${currentMilestoneId}`, `2. Identify missing features/requirements from the target milestone`, - `3. Resume auto-mode — the state machine will re-enter discussion for the target`, + `3. Resume autonomous mode — the state machine will re-enter discussion for the target`, ].join("\n"); atomicWriteSync(triggerPath, content, "utf-8"); // If we have a valid target, also reset that milestone's completion status @@ -346,7 +346,7 @@ export function loadReplanCaptures(basePath) { */ export function buildQuickTaskPrompt(capture) { return [ - `You are executing a quick one-off task captured during a SF auto-mode session.`, + `You are executing a quick one-off task captured during a SF autonomous mode session.`, ``, `## Quick Task`, ``, diff --git a/src/resources/extensions/sf/triage-ui.js b/src/resources/extensions/sf/triage-ui.js index cfe3b6554..9eb9dcc79 100644 --- a/src/resources/extensions/sf/triage-ui.js +++ b/src/resources/extensions/sf/triage-ui.js @@ -1,7 +1,7 @@ /** * SF Triage UI — Confirmation flow for programmatic triage results * - * Used by auto-mode dispatch (S02) when triage fires between tasks. + * Used by autonomous mode dispatch (S02) when triage fires between tasks. * For manual `/sf triage`, the LLM session handles confirmation directly. * * This module provides `showTriageConfirmation` which presents each @@ -38,7 +38,7 @@ const CLASSIFICATION_LABELS = { stop: { label: "Stop", description: - "Halt auto-mode immediately — user directive to cease execution.", + "Halt autonomous mode immediately — user directive to cease execution.", }, backtrack: { label: "Backtrack", diff --git a/src/resources/extensions/sf/unit-context-manifest.js b/src/resources/extensions/sf/unit-context-manifest.js index 442d3a5c8..eddbd297a 100644 --- a/src/resources/extensions/sf/unit-context-manifest.js +++ b/src/resources/extensions/sf/unit-context-manifest.js @@ -1,6 +1,6 @@ // SF — UnitContextManifest (#4782 phase 1). // -// Declarative description of what context each auto-mode unit type needs +// Declarative description of what context each autonomous mode unit type needs // in its system prompt. Establishes the contract that later phases will // use to drive a single composeSystemPromptForUnit() — replacing the // per-unit-type branching currently spread across `auto-prompts.ts`. @@ -89,7 +89,7 @@ const TOOLS_DOCS = { ], }; /** - * Canonical unit types handled by auto-mode dispatch. The coverage test + * Canonical unit types handled by autonomous mode dispatch. The coverage test * enumerates these against `UNIT_MANIFESTS` to catch manifest drift when * a new unit type lands. */ diff --git a/src/resources/extensions/sf/uok/contracts.js b/src/resources/extensions/sf/uok/contracts.js index 57534277e..da3ac2ef7 100644 --- a/src/resources/extensions/sf/uok/contracts.js +++ b/src/resources/extensions/sf/uok/contracts.js @@ -19,6 +19,8 @@ * @property {string} [taskId] * @property {string} [modelId] — LLM model identifier * @property {string} [provider] — LLM provider name + * @property {"manual"|"assisted"|"autonomous"} [runControl] — who advances the flow + * @property {"restricted"|"normal"|"trusted"|"unrestricted"} [permissionProfile] — tool/file/network trust posture * @property {number} [iteration] — current retry/dispatch iteration * @property {number} [tokenCount] — tokens consumed this turn * @property {number} [costUsd] — estimated cost this turn diff --git a/src/resources/extensions/sf/uok/dispatch-envelope.js b/src/resources/extensions/sf/uok/dispatch-envelope.js index e3dc55eb7..39e57f572 100644 --- a/src/resources/extensions/sf/uok/dispatch-envelope.js +++ b/src/resources/extensions/sf/uok/dispatch-envelope.js @@ -4,6 +4,8 @@ export function buildDispatchEnvelope(input) { nodeKind: input.node?.kind, unitType: input.unitType, unitId: input.unitId, + runControl: input.runControl, + permissionProfile: input.permissionProfile, prompt: input.prompt, reason: { reasonCode: input.reasonCode, diff --git a/src/resources/extensions/sf/uok/flags.js b/src/resources/extensions/sf/uok/flags.js index 51f5fd8d3..a48b5a222 100644 --- a/src/resources/extensions/sf/uok/flags.js +++ b/src/resources/extensions/sf/uok/flags.js @@ -1,3 +1,4 @@ +import { resolvePermissionProfile } from "../operating-model.js"; import { loadEffectiveSFPreferences } from "../preferences.js"; export function resolveUokFlags(prefs) { @@ -21,6 +22,7 @@ export function resolveUokFlags(prefs) { (uok?.audit_unified?.enabled ?? true), planningFlow: (uok?.planning_flow?.enabled ?? true) || (uok?.plan_v2?.enabled ?? true), + permissionProfile: resolvePermissionProfile(uok?.permission_profile), }; } export function loadUokFlags() { diff --git a/src/resources/extensions/sf/uok/kernel.js b/src/resources/extensions/sf/uok/kernel.js index 5a6646719..39f2d9544 100644 --- a/src/resources/extensions/sf/uok/kernel.js +++ b/src/resources/extensions/sf/uok/kernel.js @@ -1,5 +1,11 @@ import { randomUUID } from "node:crypto"; import { debugLog } from "../debug-logger.js"; +import { + defaultPermissionProfileForRunControl, + resolvePermissionProfile, + resolveRunControlMode, + runControlModeForSession, +} from "../operating-model.js"; import { isDbAvailable, recordUokRunExit, @@ -38,7 +44,7 @@ function resolveKernelPathLabel() { * Records an abnormal UOK kernel termination in both durable stores. * * Purpose: keep the DB-backed UOK run ledger and JSONL parity heartbeat - * symmetrical when auto-mode exits via signal and bypasses the async kernel + * symmetrical when autonomous mode exits via signal and bypasses the async kernel * finally block. * * Consumer: auto signal cleanup and UOK parity tests. @@ -48,16 +54,23 @@ export function recordUokKernelTermination({ runId, sessionId, flags, + runControl, + permissionProfile, status = "signal", error, }) { const endedAt = new Date().toISOString(); + const lifecycleFlags = { + ...(flags ?? {}), + ...(runControl ? { runControl } : {}), + ...(permissionProfile ? { permissionProfile } : {}), + }; if (runId && isDbAvailable()) { recordUokRunExit({ runId, sessionId, path: resolveKernelPathLabel(), - flags: { ...(flags ?? {}) }, + flags: lifecycleFlags, status, endedAt, ...(error ? { error } : {}), @@ -68,7 +81,9 @@ export function recordUokKernelTermination({ ...(runId ? { runId } : {}), sessionId, path: resolveKernelPathLabel(), - flags: { ...(flags ?? {}) }, + flags: lifecycleFlags, + ...(runControl ? { runControl } : {}), + ...(permissionProfile ? { permissionProfile } : {}), phase: "exit", status, ...(error ? { error } : {}), @@ -87,6 +102,19 @@ export async function runAutoLoopWithUok(args) { const { ctx, pi, s, deps, runKernelLoop } = args; const prefs = deps.loadEffectiveSFPreferences()?.preferences; const flags = { ...resolveUokFlags(prefs), enabled: true }; + const runControl = resolveRunControlMode( + args.runControl ?? runControlModeForSession(s), + ); + const permissionProfile = resolvePermissionProfile( + args.permissionProfile ?? + prefs?.uok?.permission_profile ?? + defaultPermissionProfileForRunControl(runControl), + ); + const lifecycleFlags = { + ...flags, + runControl, + permissionProfile, + }; const healthVerdict = writeUokDiagnostics(s.basePath); debugLog("uok-system-health-verdict", healthVerdict); @@ -113,7 +141,7 @@ export async function runAutoLoopWithUok(args) { runId, sessionId: ctx.sessionManager?.getSessionId?.(), path: resolveKernelPathLabel(), - flags: { ...flags }, + flags: lifecycleFlags, startedAt, }); } @@ -122,7 +150,9 @@ export async function runAutoLoopWithUok(args) { runId, sessionId: ctx.sessionManager?.getSessionId?.(), path: resolveKernelPathLabel(), - flags: { ...flags }, + flags: lifecycleFlags, + runControl, + permissionProfile, phase: "enter", }); if (flags.auditEnvelope) { @@ -133,7 +163,9 @@ export async function runAutoLoopWithUok(args) { category: "orchestration", type: "uok-kernel-enter", payload: { - flags, + flags: lifecycleFlags, + runControl, + permissionProfile, sessionId: ctx.sessionManager?.getSessionId?.(), }, }), @@ -148,7 +180,11 @@ export async function runAutoLoopWithUok(args) { enableAudit: flags.auditEnvelope, enableGitops: flags.gitops, enableChaosMonkey: flags.chaosMonkey, + runControl, + permissionProfile, }), + uokRunControl: runControl, + uokPermissionProfile: permissionProfile, }; let status = "ok"; let error; @@ -163,7 +199,9 @@ export async function runAutoLoopWithUok(args) { basePath: s.basePath, runId, sessionId: ctx.sessionManager?.getSessionId?.(), - flags: { ...flags }, + flags: lifecycleFlags, + runControl, + permissionProfile, status, ...(error ? { error } : {}), }); diff --git a/src/resources/extensions/sf/uok/loop-adapter.js b/src/resources/extensions/sf/uok/loop-adapter.js index 1995342e2..2e1dff7ed 100644 --- a/src/resources/extensions/sf/uok/loop-adapter.js +++ b/src/resources/extensions/sf/uok/loop-adapter.js @@ -33,27 +33,33 @@ export function createTurnObserver(options) { return { onTurnStart(contract) { if (chaosMonkey) chaosMonkey.strike("turn-start"); - current = contract; + current = { + ...contract, + runControl: options.runControl, + permissionProfile: options.permissionProfile, + }; phaseResults.length = 0; writerToken = acquireWriterToken({ basePath: options.basePath, - traceId: contract.traceId, - turnId: contract.turnId, + traceId: current.traceId, + turnId: current.turnId, }); if (options.enableGitops) { writeTurnGitTransaction({ basePath: options.basePath, - traceId: contract.traceId, - turnId: contract.turnId, - unitType: contract.unitType, - unitId: contract.unitId, + traceId: current.traceId, + turnId: current.turnId, + unitType: current.unitType, + unitId: current.unitId, stage: "turn-start", action: options.gitAction, push: options.gitPush, status: "ok", metadata: nextSequenceMetadata("gitops", "insert", { - iteration: contract.iteration, - sidecarKind: contract.sidecarKind, + iteration: current.iteration, + sidecarKind: current.sidecarKind, + runControl: current.runControl, + permissionProfile: current.permissionProfile, }), }); } @@ -61,15 +67,17 @@ export function createTurnObserver(options) { emitUokAuditEvent( options.basePath, buildAuditEnvelope({ - traceId: contract.traceId, - turnId: contract.turnId, + traceId: current.traceId, + turnId: current.turnId, category: "orchestration", type: "turn-start", payload: nextSequenceMetadata("audit", "append", { - iteration: contract.iteration, - unitType: contract.unitType, - unitId: contract.unitId, - sidecarKind: contract.sidecarKind, + iteration: current.iteration, + unitType: current.unitType, + unitId: current.unitId, + sidecarKind: current.sidecarKind, + runControl: current.runControl, + permissionProfile: current.permissionProfile, }), }), ); @@ -129,6 +137,8 @@ export function createTurnObserver(options) { }, onTurnResult(result) { const merged = { + runControl: options.runControl, + permissionProfile: options.permissionProfile, ...result, phaseResults: result.phaseResults.length > 0 @@ -150,6 +160,8 @@ export function createTurnObserver(options) { failureClass: merged.failureClass, error: merged.error, phaseCount: merged.phaseResults.length, + runControl: merged.runControl, + permissionProfile: merged.permissionProfile, }), }), ); diff --git a/src/resources/extensions/sf/uok/unit-runtime.js b/src/resources/extensions/sf/uok/unit-runtime.js index b687722a4..8ba79697f 100644 --- a/src/resources/extensions/sf/uok/unit-runtime.js +++ b/src/resources/extensions/sf/uok/unit-runtime.js @@ -586,7 +586,7 @@ export function reconcileStaleCompleteSliceRecords(basePath) { * after a unit wrote its durable completion artifacts but before the in-memory * finalizer cleared `.sf/runtime/units/*.json`. * - * Consumer: auto-mode bootstrap before dispatching the next autonomous unit. + * Consumer: autonomous mode bootstrap before dispatching the next autonomous unit. */ export async function reconcileDurableCompleteUnitRuntimeRecords(basePath) { const dir = runtimeDir(basePath); diff --git a/src/resources/extensions/sf/visualizer-data.js b/src/resources/extensions/sf/visualizer-data.js index a349f941c..c516ccfe5 100644 --- a/src/resources/extensions/sf/visualizer-data.js +++ b/src/resources/extensions/sf/visualizer-data.js @@ -437,7 +437,7 @@ function loadHealth(units, totals, basePath) { } catch { /* non-fatal */ } - // Current progress score — only meaningful when auto-mode has health data + // Current progress score — only meaningful when autonomous mode has health data let progressScore = null; try { const history = getHealthHistory(); diff --git a/src/resources/extensions/sf/visualizer-views.js b/src/resources/extensions/sf/visualizer-views.js index 05507c3d1..3ef611211 100644 --- a/src/resources/extensions/sf/visualizer-views.js +++ b/src/resources/extensions/sf/visualizer-views.js @@ -685,7 +685,7 @@ export function renderAgentView(data, th, width) { `Current: ${th.fg("accent", `${activity.currentUnit.type} ${activity.currentUnit.id}`)}`, ); } else { - lines.push(th.fg("dim", "Not in auto mode")); + lines.push(th.fg("dim", "Not in autonomous mode")); } lines.push(""); // Progress bar diff --git a/src/resources/extensions/sf/worktree-command.js b/src/resources/extensions/sf/worktree-command.js index c8a69b4e3..94657a978 100644 --- a/src/resources/extensions/sf/worktree-command.js +++ b/src/resources/extensions/sf/worktree-command.js @@ -294,7 +294,7 @@ export function registerWorktreeCommand(pi) { // ─── Handlers ────────────────────────────────────────────────────────────── /** * Check if the worktree has existing SF milestones that would - * cause auto-mode to continue previous work instead of starting fresh. + * cause autonomous mode to continue previous work instead of starting fresh. */ function hasExistingMilestones(wtPath) { const mDir = milestonesDir(wtPath); @@ -309,7 +309,7 @@ function hasExistingMilestones(wtPath) { } } /** - * Clear SF planning artifacts so auto-mode starts fresh with the discuss flow. + * Clear SF planning artifacts so autonomous mode starts fresh with the discuss flow. * Keeps the .sf/ directory structure intact but removes milestones and root planning files. */ function clearSFPlans(wtPath) { diff --git a/src/resources/extensions/sf/worktree-manager.js b/src/resources/extensions/sf/worktree-manager.js index 551cadba8..704f5fa50 100644 --- a/src/resources/extensions/sf/worktree-manager.js +++ b/src/resources/extensions/sf/worktree-manager.js @@ -234,7 +234,7 @@ export function createWorktree(basePath, name, opts = {}) { } if (opts.reuseExistingBranch) { // Attach worktree to the existing branch as-is (preserving commits). - // Used when resuming auto-mode: the milestone branch has valid work + // Used when resuming autonomous mode: the milestone branch has valid work // from prior sessions that must not be reset. nativeWorktreeAdd(basePath, wtPath, branch); } else { diff --git a/src/resources/extensions/sf/worktree-resolver.js b/src/resources/extensions/sf/worktree-resolver.js index 6c6d88fc3..64664defb 100644 --- a/src/resources/extensions/sf/worktree-resolver.js +++ b/src/resources/extensions/sf/worktree-resolver.js @@ -31,7 +31,7 @@ import { } from "./worktree-telemetry.js"; // ─── WorktreeResolver ────────────────────────────────────────────────────── /** - * Manages worktree lifecycle for auto-mode: enter/exit milestone, merge/transition. + * Manages worktree lifecycle for autonomous mode: enter/exit milestone, merge/transition. * Encapsulates path state, handles git isolation modes, and emits telemetry (ADR-019). */ export class WorktreeResolver { diff --git a/src/resources/extensions/sf/worktree-telemetry.js b/src/resources/extensions/sf/worktree-telemetry.js index d80d1ec1d..251342c03 100644 --- a/src/resources/extensions/sf/worktree-telemetry.js +++ b/src/resources/extensions/sf/worktree-telemetry.js @@ -11,7 +11,7 @@ * - worktree-created worktree entered/created for a milestone * - worktree-merged worktree merge back to main completed * - worktree-orphaned audit detected an orphaned branch/worktree - * - auto-exit auto-mode exited (pause/stop/blocked/error) + * - auto-exit autonomous mode exited (pause/stop/blocked/error) * - worktree-sync syncStateToProjectRoot snapshot * - canonical-root-redirect resolveCanonicalMilestoneRoot redirected * diff --git a/src/resources/extensions/sf/worktree.js b/src/resources/extensions/sf/worktree.js index 55cd869a0..0b3d1584b 100644 --- a/src/resources/extensions/sf/worktree.js +++ b/src/resources/extensions/sf/worktree.js @@ -54,7 +54,7 @@ export function setActiveMilestoneId(basePath, milestoneId) { } /** * Record the current branch as the integration branch for a milestone. - * Called once when auto-mode starts — captures where slice branches should + * Called once when autonomous mode starts — captures where slice branches should * merge back to. No-op if the same branch is already recorded. Updates the * record when the user starts from a different branch (#300). Always a no-op * if on a SF slice branch. diff --git a/src/resources/skills/create-skill/references/sf-skill-ecosystem.md b/src/resources/skills/create-skill/references/sf-skill-ecosystem.md index 0ca90a8bb..dc0f91271 100644 --- a/src/resources/skills/create-skill/references/sf-skill-ecosystem.md +++ b/src/resources/skills/create-skill/references/sf-skill-ecosystem.md @@ -19,11 +19,11 @@ Skills in any of the four directories follow the same SKILL.md format and router </skill_directories> <skill_discovery> -SF auto-discovers skills at session start and during auto-mode: +SF auto-discovers skills at session start and during autonomous mode: **Session start:** Skills in all four directories are enumerated and their names + descriptions are injected into the system prompt as `<available_skills>`. -**Auto-mode discovery:** `skill-discovery.ts` takes a snapshot of the skills directories at auto-mode start. On each unit boundary it diffs against the snapshot. Any new skills found are injected via a `<newly_discovered_skills>` XML block so the LLM sees them without requiring `/reload`. +**Autonomous mode discovery:** `skill-discovery.ts` takes a snapshot of the skills directories at autonomous mode start. On each unit boundary it diffs against the snapshot. Any new skills found are injected via a `<newly_discovered_skills>` XML block so the LLM sees them without requiring `/reload`. **Manual reload:** Running `/reload` re-scans all four directories and updates the available skills list mid-session. </skill_discovery> @@ -65,5 +65,5 @@ After creating or modifying a skill: 1. Run `/reload` to make it available in the current session 2. On next session start, auto-discovery picks it up automatically -3. In auto-mode, new skills are detected at unit boundaries without any action needed +3. In autonomous mode, new skills are detected at unit boundaries without any action needed </activation> diff --git a/src/resources/skills/forensics/SKILL.md b/src/resources/skills/forensics/SKILL.md index 9014fb088..8ff26f22c 100644 --- a/src/resources/skills/forensics/SKILL.md +++ b/src/resources/skills/forensics/SKILL.md @@ -1,6 +1,6 @@ --- name: forensics -description: Post-mortem a failed sf autonomous-mode run. Traces from symptom to root cause using `.sf/activity/*.jsonl`, `.sf/journal/YYYY-MM-DD.jsonl`, `.sf/metrics.json`, and `.sf/auto.lock`. Produces a filing-ready bug report with file:line references and a concrete fix suggestion. Use when asked to "forensics", "post-mortem", "why did autonomous-mode fail", "trace the stuck loop", "debug the crash", after `/sf forensics` is invoked, or when a session ended in an unexpected terminal state. Reads existing artifacts — does NOT re-run anything. +description: Post-mortem a failed sf autonomous mode run. Traces from symptom to root cause using `.sf/activity/*.jsonl`, `.sf/journal/YYYY-MM-DD.jsonl`, `.sf/metrics.json`, and `.sf/auto.lock`. Produces a filing-ready bug report with file:line references and a concrete fix suggestion. Use when asked to "forensics", "post-mortem", "why did autonomous mode fail", "trace the stuck loop", "debug the crash", after `/sf forensics` is invoked, or when a session ended in an unexpected terminal state. Reads existing artifacts — does NOT re-run anything. --- <objective> @@ -16,14 +16,14 @@ sf persists a lot of runtime evidence under `.sf/`: - `auto.lock` — JSON snapshot of the currently-owning PID; stale lock = crash mid-unit - `forensics/` — saved prior reports - `debug/` — debug logs if enabled -- `runtime/paused-session.json` — serialized session when auto-mode paused +- `runtime/paused-session.json` — serialized session when autonomous mode paused - `doctor-history.jsonl` — doctor check history The `/sf forensics` command pre-computes a forensic report with anomalies flagged. This skill is the manual investigation that goes deeper, or runs when the automated report isn't enough. Invocation points: - `/sf forensics` has been run and user wants deeper analysis -- Auto-mode exited unexpectedly, no obvious cause +- Autonomous mode exited unexpectedly, no obvious cause - Same unit dispatched multiple times (stuck loop suspected) - A session crashed and `auto.lock` is stale - User reports "it just stopped" or "it did the wrong thing" @@ -68,7 +68,7 @@ For each symptom from the activity log, find the matching journal events: - `stuck-detected` + same `flowId` → the loop detected repetition. `data.reason` says why. - `guard-block` → a dispatch guard refused to run a unit. Check `data.reason` and trace to `dispatch-guard.ts` logic. - `unit-end` followed by another `unit-start` for the same `unitId` → re-dispatch. If tied to `stuck-detected`, the artifact verification failed after the unit succeeded. -- `terminal` → auto-mode decided to stop. `data.reason` tells you why. +- `terminal` → autonomous mode decided to stop. `data.reason` tells you why. Use `flowId` to reconstruct one iteration; use `causedBy` to follow causal chains across iterations. @@ -79,7 +79,7 @@ A good root cause is: - Falsifiable: if we changed X, would the failure go away? - Sourced: cites a file and (where applicable) a line number. -Bad root cause: "Auto-mode got stuck in a loop." Good root cause: "After slice completion, `auto-unit-closeout.ts` emits `unit-end` before `auto-post-unit.ts` updates the roadmap checkbox. The next `iteration-start` finds the same unit `[ ]` and re-dispatches — `dispatch-guard.ts:42` has no check against the freshly-ended `unitId`." +Bad root cause: "Autonomous mode got stuck in a loop." Good root cause: "After slice completion, `auto-unit-closeout.ts` emits `unit-end` before `auto-post-unit.ts` updates the roadmap checkbox. The next `iteration-start` finds the same unit `[ ]` and re-dispatches — `dispatch-guard.ts:42` has no check against the freshly-ended `unitId`." Consult the source map in `src/resources/extensions/sf/prompts/forensics.md` to map symptoms to the likely domain files. @@ -133,7 +133,7 @@ Offer to file this as a GitHub issue via `mcp__github__issue_write` — explicit <anti_patterns> - **Running any `sf_*` write tool during forensics.** Evidence stays pristine. -- **Re-running the auto-mode loop to "reproduce."** That overwrites the activity log. Read the existing one. +- **Re-running the autonomous mode loop to "reproduce."** That overwrites the activity log. Read the existing one. - **Vague root cause.** "There's a race" is not a root cause. Name the race. - **No citations.** Every claim gets an artifact path. - **Skipping the journal.** The journal is the only view that shows dispatch-level decisions. diff --git a/src/resources/traces.ts b/src/resources/traces.ts index c43762cff..981e0ab97 100644 --- a/src/resources/traces.ts +++ b/src/resources/traces.ts @@ -1,7 +1,7 @@ /** * Structured Trace Data Model & Export * - * Provides a hierarchical span model for tracing auto-mode execution. + * Provides a hierarchical span model for tracing autonomous mode execution. * Spans form a tree: root session span → unit spans (milestone/slice/task) → tool spans. * * Two export modes: diff --git a/src/resources/workflows/build-from-spec.md b/src/resources/workflows/build-from-spec.md index 3200cc58f..3abb9ad9f 100644 --- a/src/resources/workflows/build-from-spec.md +++ b/src/resources/workflows/build-from-spec.md @@ -55,21 +55,21 @@ SPEC **Fire-and-forget (simplest — SF does everything):** ```bash cd "$PROJECT_DIR" -RESULT=$(sf headless --output-format json --timeout 0 --context spec.md new-milestone --auto 2>/dev/null) +RESULT=$(sf headless --output-format json --timeout 0 --context spec.md new-milestone --autonomous 2>/dev/null) EXIT=$? ``` -`--timeout 0` disables the timeout for long builds. `--auto` chains milestone creation into execution. +`--timeout 0` disables the timeout for long builds. `--autonomous` chains milestone creation into execution. **With budget limit:** ```bash -# Use step-by-step mode with budget checks instead of auto +# Use assisted mode with budget checks instead of autonomous mode # See workflows/step-by-step.md ``` **For CI or ecosystem runs (no user config):** ```bash -RESULT=$(sf headless --bare --output-format json --timeout 0 --context spec.md new-milestone --auto 2>/dev/null) +RESULT=$(sf headless --bare --output-format json --timeout 0 --context spec.md new-milestone --autonomous 2>/dev/null) EXIT=$? ``` @@ -168,7 +168,7 @@ Build a REST API for managing todo items using Node.js and Express. SPEC # 3. Launch -RESULT=$(sf headless --output-format json --timeout 0 --context spec.md new-milestone --auto 2>/dev/null) +RESULT=$(sf headless --output-format json --timeout 0 --context spec.md new-milestone --autonomous 2>/dev/null) EXIT=$? # 4. Report diff --git a/src/tests/auto-mode-piped.test.ts b/src/tests/auto-mode-piped.test.ts index 1eab8699e..23ee6616b 100644 --- a/src/tests/auto-mode-piped.test.ts +++ b/src/tests/auto-mode-piped.test.ts @@ -73,7 +73,7 @@ test("cli.ts routes `autonomous` to headless runner", () => { }); // ═══════════════════════════════════════════════════════════════════════════ -// Verify piped-mode hint in error message when auto mode is not available +// Verify piped-mode hint in error message when autonomous mode is not available // ═══════════════════════════════════════════════════════════════════════════ test("TTY error message mentions non-interactive alternatives", () => { diff --git a/src/tests/auto-piped-io.test.ts b/src/tests/auto-piped-io.test.ts index abe08ac58..ee1d64459 100644 --- a/src/tests/auto-piped-io.test.ts +++ b/src/tests/auto-piped-io.test.ts @@ -1,5 +1,5 @@ /** - * Tests for autonomous-mode piped I/O detection (#2732). + * Tests for autonomous mode piped I/O detection (#2732). * * When `sf autonomous` is run with piped stdout (e.g. `sf autonomous | cat`), * the CLI should detect the non-TTY stdout and redirect to headless diff --git a/src/tests/auto-resume-resource-loader.test.ts b/src/tests/auto-resume-resource-loader.test.ts index b882cbd73..9ac0108f7 100644 --- a/src/tests/auto-resume-resource-loader.test.ts +++ b/src/tests/auto-resume-resource-loader.test.ts @@ -1,4 +1,4 @@ -// sf — Regression test: auto-mode resume resolves resource-loader.js from deployed path (#3949) +// sf — Regression test: autonomous mode resume resolves resource-loader.js from deployed path (#3949) // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net> import assert from "node:assert/strict"; diff --git a/src/tests/headless-cli-surface.test.ts b/src/tests/headless-cli-surface.test.ts index 47d40715b..3e6f90226 100644 --- a/src/tests/headless-cli-surface.test.ts +++ b/src/tests/headless-cli-surface.test.ts @@ -22,6 +22,10 @@ import { import type { HeadlessJsonResult, OutputFormat } from "../headless-types.js"; import { VALID_OUTPUT_FORMATS } from "../headless-types.js"; +import { + findUnsupportedAutonomousArgs, + formatUnsupportedAutonomousArgs, +} from "../resources/extensions/sf/autonomous-command-args.js"; // ─── Extracted parsing logic (mirrors headless.ts) ───────────────────────── @@ -34,7 +38,7 @@ interface HeadlessOptions { commandArgs: string[]; context?: string; contextText?: string; - auto?: boolean; + chainAutonomous?: boolean; verbose?: boolean; maxRestarts?: number; supervised?: boolean; @@ -82,8 +86,12 @@ function parseHeadlessArgs(argv: string[]): HeadlessOptions { options.context = args[++i]; } else if (arg === "--context-text" && i + 1 < args.length) { options.contextText = args[++i]; + } else if (arg === "--autonomous") { + options.chainAutonomous = true; } else if (arg === "--auto") { - options.auto = true; + throw new Error( + "--auto was removed. Use --autonomous to chain into autonomous mode.", + ); } else if (arg === "--verbose") { options.verbose = true; } else if (arg === "--max-restarts" && i + 1 < args.length) { @@ -114,7 +122,7 @@ function parseHeadlessArgs(argv: string[]): HeadlessOptions { } else if (!commandSeen) { if (arg === "autonomous") { options.command = "autonomous"; - options.auto = true; + options.chainAutonomous = true; } else { options.command = arg; } @@ -132,7 +140,7 @@ function parseHeadlessArgs(argv: string[]): HeadlessOptions { test("bare headless defaults to help, not autonomous", () => { const opts = parseHeadlessArgs(["node", "sf", "headless"]); assert.equal(opts.command, "help"); - assert.equal(opts.auto, undefined); + assert.equal(opts.chainAutonomous, undefined); assert.deepEqual(opts.commandArgs, []); }); @@ -284,11 +292,21 @@ test("unknown command flags after subcommand are preserved for slash command han "sf", "headless", "autonomous", - "--full", + "--turbo", ]); assert.equal(opts.command, "autonomous"); - assert.deepEqual(opts.commandArgs, ["--full"]); + assert.deepEqual(opts.commandArgs, ["--turbo"]); +}); + +test("unsupported autonomous args reject invented knobs and report supported grammar", () => { + assert.deepEqual(findUnsupportedAutonomousArgs(["--turbo"]), ["--turbo"]); + assert.deepEqual(findUnsupportedAutonomousArgs(["turbo"]), ["turbo"]); + assert.deepEqual(findUnsupportedAutonomousArgs(["--yolo", "brief.md"]), []); + assert.deepEqual(findUnsupportedAutonomousArgs(["M001", "--debug"]), []); + const message = formatUnsupportedAutonomousArgs(["--turbo"]); + assert.match(message, /Unsupported/); + assert.match(message, /--yolo <file>/); }); // ─── Exit code constants ─────────────────────────────────────────────────── @@ -568,7 +586,7 @@ test("--bare combined with --output-format json", () => { // ─── Command-first ordering (flags after command) ───────────────────────── -test("command before flags: new-milestone --context-text --auto --verbose", () => { +test("command before flags: new-milestone --context-text --autonomous --verbose", () => { const opts = parseHeadlessArgs([ "node", "sf", @@ -576,12 +594,12 @@ test("command before flags: new-milestone --context-text --auto --verbose", () = "new-milestone", "--context-text", "build something cool", - "--auto", + "--autonomous", "--verbose", ]); assert.equal(opts.command, "new-milestone"); assert.equal(opts.contextText, "build something cool"); - assert.equal(opts.auto, true); + assert.equal(opts.chainAutonomous, true); assert.equal(opts.verbose, true); }); @@ -600,20 +618,28 @@ test("command before flags: next --json --timeout", () => { assert.equal(opts.timeout, 60000); }); -test("command between flags: --auto new-milestone --verbose", () => { +test("command between flags: --autonomous new-milestone --verbose", () => { const opts = parseHeadlessArgs([ "node", "sf", "headless", - "--auto", + "--autonomous", "new-milestone", "--verbose", ]); assert.equal(opts.command, "new-milestone"); - assert.equal(opts.auto, true); + assert.equal(opts.chainAutonomous, true); assert.equal(opts.verbose, true); }); +test("removed --auto shorthand is rejected", () => { + assert.throws( + () => + parseHeadlessArgs(["node", "sf", "headless", "new-milestone", "--auto"]), + /--auto was removed/, + ); +}); + test("--bare does not affect other flags", () => { const opts = parseHeadlessArgs([ "node", diff --git a/src/tests/headless-detection.test.ts b/src/tests/headless-detection.test.ts index 8b3952095..96cbe2364 100644 --- a/src/tests/headless-detection.test.ts +++ b/src/tests/headless-detection.test.ts @@ -1,7 +1,7 @@ /** * Tests for headless completion detection. * - * Verifies that isTerminalNotification only matches actual auto-mode stop + * Verifies that isTerminalNotification only matches actual autonomous mode stop * signals and does not false-positive on progress notifications that * happen to contain words like "complete" or "stopped". */ @@ -16,10 +16,10 @@ import { test } from "vitest"; // ─── Extracted detection logic (mirrors headless.ts) ──────────────────────── const TERMINAL_PREFIXES = [ - "auto-mode stopped", - "step-mode stopped", - "auto-mode paused", - "step-mode paused", + "autonomous mode stopped", + "assisted mode stopped", + "autonomous mode paused", + "assisted mode paused", ]; function isTerminalNotification(event: Record<string, unknown>): boolean { @@ -35,8 +35,8 @@ function isBlockedNotification(event: Record<string, unknown>): boolean { const message = String(event.message ?? "").toLowerCase(); return ( message.includes("blocked:") || - message.startsWith("auto-mode paused") || - message.startsWith("step-mode paused") + message.startsWith("autonomous mode paused") || + message.startsWith("assisted mode paused") ); } @@ -46,45 +46,45 @@ function makeNotify(message: string): Record<string, unknown> { // ─── isTerminalNotification ───────────────────────────────────────────────── -test("detects 'Auto-mode stopped.' as terminal", () => { - assert.ok(isTerminalNotification(makeNotify("Auto-mode stopped."))); +test("detects 'Autonomous mode stopped.' as terminal", () => { + assert.ok(isTerminalNotification(makeNotify("Autonomous mode stopped."))); }); -test("detects 'Auto-mode stopped (All milestones complete).' as terminal", () => { +test("detects 'Autonomous mode stopped (All milestones complete).' as terminal", () => { assert.ok( isTerminalNotification( makeNotify( - "Auto-mode stopped (All milestones complete). Session: $0.42 · 15K tokens · 8 units", + "Autonomous mode stopped (All milestones complete). Session: $0.42 · 15K tokens · 8 units", ), ), ); }); -test("detects 'Auto-mode stopped (Blocked: missing API key).' as terminal", () => { +test("detects 'Autonomous mode stopped (Blocked: missing API key).' as terminal", () => { assert.ok( isTerminalNotification( - makeNotify("Auto-mode stopped (Blocked: missing API key)."), + makeNotify("Autonomous mode stopped (Blocked: missing API key)."), ), ); }); -test("detects 'Auto-mode stopped (Milestone M001 complete).' as terminal", () => { +test("detects 'Autonomous mode stopped (Milestone M001 complete).' as terminal", () => { assert.ok( isTerminalNotification( - makeNotify("Auto-mode stopped (Milestone M001 complete)."), + makeNotify("Autonomous mode stopped (Milestone M001 complete)."), ), ); }); -test("detects 'Step-mode stopped.' as terminal", () => { - assert.ok(isTerminalNotification(makeNotify("Step-mode stopped."))); +test("detects 'Assisted mode stopped.' as terminal", () => { + assert.ok(isTerminalNotification(makeNotify("Assisted mode stopped."))); }); test("detects provider-error auto pause as terminal", () => { assert.ok( isTerminalNotification( makeNotify( - "Auto-mode paused due to provider error: Schema overload: consecutive tool validation failures exceeded cap", + "Autonomous mode paused due to provider error: Schema overload: consecutive tool validation failures exceeded cap", ), ), ); @@ -146,7 +146,7 @@ test("does NOT match non-notify events", () => { !isTerminalNotification({ type: "extension_ui_request", method: "select", - message: "Auto-mode stopped.", + message: "Autonomous mode stopped.", }), ); }); @@ -156,7 +156,7 @@ test("does NOT match non-notify events", () => { test("detects blocked notification with 'Blocked:' prefix", () => { assert.ok( isBlockedNotification( - makeNotify("Auto-mode stopped (Blocked: missing API key)."), + makeNotify("Autonomous mode stopped (Blocked: missing API key)."), ), ); }); @@ -173,11 +173,13 @@ test("detects auto pause as blocked for headless callers", () => { assert.ok( isBlockedNotification( makeNotify( - "Auto-mode paused due to provider error: Schema overload: consecutive tool validation failures exceeded cap", + "Autonomous mode paused due to provider error: Schema overload: consecutive tool validation failures exceeded cap", ), ), ); - assert.ok(isBlockedNotification(makeNotify("Auto-mode paused (Escape)."))); + assert.ok( + isBlockedNotification(makeNotify("Autonomous mode paused (Escape).")), + ); }); test("does NOT match 'blocked' without colon (avoids false positives)", () => { diff --git a/src/tests/headless-events.test.ts b/src/tests/headless-events.test.ts index 72e51d806..19e72df6e 100644 --- a/src/tests/headless-events.test.ts +++ b/src/tests/headless-events.test.ts @@ -21,7 +21,7 @@ interface HeadlessOptions { commandArgs: string[]; context?: string; contextText?: string; - auto?: boolean; + chainAutonomous?: boolean; verbose?: boolean; maxRestarts?: number; supervised?: boolean; @@ -56,8 +56,12 @@ function parseHeadlessArgs(argv: string[]): HeadlessOptions { options.context = args[++i]; } else if (arg === "--context-text" && i + 1 < args.length) { options.contextText = args[++i]; + } else if (arg === "--autonomous") { + options.chainAutonomous = true; } else if (arg === "--auto") { - options.auto = true; + throw new Error( + "--auto was removed. Use --autonomous to chain into autonomous mode.", + ); } else if (arg === "--verbose") { options.verbose = true; } else if (arg === "--max-restarts" && i + 1 < args.length) { @@ -257,7 +261,7 @@ test("isTerminalNotification: auto pause is terminal for headless callers", () = type: "extension_ui_request", method: "notify", message: - "Auto-mode paused due to provider error: Schema overload: consecutive tool validation failures exceeded cap", + "Autonomous mode paused due to provider error: Schema overload: consecutive tool validation failures exceeded cap", }), true, ); @@ -268,7 +272,7 @@ test("isBlockedNotification: auto pause exits as blocked", () => { isBlockedNotification({ type: "extension_ui_request", method: "notify", - message: "Auto-mode paused (Escape).", + message: "Autonomous mode paused (Escape).", }), true, ); @@ -287,7 +291,7 @@ test("isAutoResumeScheduledNotification detects provider auto-resume notices", ( isAutoResumeScheduledNotification({ type: "extension_ui_request", method: "notify", - message: "Auto-mode paused (Escape). Type to interact.", + message: "Autonomous mode paused (Escape). Type to interact.", }), false, ); @@ -309,7 +313,7 @@ test("isPauseNotification detects pause banners separately from auto-resume noti isPauseNotification({ type: "extension_ui_request", method: "notify", - message: "Auto-mode paused (Escape). Type to interact.", + message: "Autonomous mode paused (Escape). Type to interact.", }), true, ); diff --git a/src/tests/headless-progress.test.ts b/src/tests/headless-progress.test.ts index 50a9362b9..6bdbb58f7 100644 --- a/src/tests/headless-progress.test.ts +++ b/src/tests/headless-progress.test.ts @@ -236,12 +236,12 @@ describe("formatProgress", () => { { type: "extension_ui_request", method: "notify", - message: "Auto-mode started", + message: "Autonomous mode started", }, ctx(), ); assert.ok(result); - assert.ok(result.includes("Auto-mode started")); + assert.ok(result.includes("Autonomous mode started")); }); it("bolds important notifications", () => { diff --git a/src/tests/headless-v2-migration.test.ts b/src/tests/headless-v2-migration.test.ts index de0d7a71f..9f0deca99 100644 --- a/src/tests/headless-v2-migration.test.ts +++ b/src/tests/headless-v2-migration.test.ts @@ -34,7 +34,7 @@ function mapStatusToExitCode(status: string): number { // ─── Extracted terminal detection (mirrors headless-events.ts) ────────────── -const TERMINAL_PREFIXES = ["auto-mode stopped", "step-mode stopped"]; +const TERMINAL_PREFIXES = ["autonomous mode stopped", "assisted mode stopped"]; function isTerminalNotification(event: Record<string, unknown>): boolean { if (event.type !== "extension_ui_request" || event.method !== "notify") @@ -113,7 +113,7 @@ function handleExtensionUIRequest( case "select": { const title = String(event.title ?? ""); let selected = event.options?.[0] ?? ""; - if (title.includes("Auto-mode is running") && event.options) { + if (title.includes("Autonomous mode is running") && event.options) { const forceOption = event.options.find((o) => o.toLowerCase().includes("force start"), ); @@ -288,7 +288,7 @@ test("v1 fallback: terminal notification still triggers completion", () => { type: "extension_ui_request", method: "notify", id: "n1", - message: "Auto-mode stopped — all slices complete", + message: "Autonomous mode stopped — all slices complete", }, state, client, @@ -312,7 +312,7 @@ test("v1 fallback: blocked notification sets blocked flag", () => { type: "extension_ui_request", method: "notify", id: "n1", - message: "Auto-mode stopped (Blocked: plan invalid)", + message: "Autonomous mode stopped (Blocked: plan invalid)", }, state, client, @@ -350,7 +350,7 @@ test("string-matching fallback works when execution_complete never received", () type: "extension_ui_request", method: "notify", id: "n1", - message: "Step-mode stopped — done", + message: "Assisted mode stopped — done", }, state, client, @@ -640,7 +640,7 @@ test("multi-turn commands still complete via terminal notification", () => { type: "extension_ui_request", method: "notify", id: "n1", - message: "Auto-mode stopped — all slices complete", + message: "Autonomous mode stopped — all slices complete", }, state, client, @@ -673,7 +673,7 @@ test("multi-turn commands detect blocked via terminal notification", () => { type: "extension_ui_request", method: "notify", id: "n2", - message: "Auto-mode stopped (Blocked: plan rejected)", + message: "Autonomous mode stopped (Blocked: plan rejected)", }, state, client, diff --git a/src/tests/integration/e2e-headless.test.ts b/src/tests/integration/e2e-headless.test.ts index b0d9f3ddd..1cab16cfc 100644 --- a/src/tests/integration/e2e-headless.test.ts +++ b/src/tests/integration/e2e-headless.test.ts @@ -191,7 +191,7 @@ test("headless --output-format json emits a single HeadlessJsonResult on stdout" assert.ok(!result.timedOut, "test harness should not time out"); // Non-zero exit expected (no API key / timeout), but process may exit 0 - // if auto-mode detects a conflict and completes immediately. + // if autonomous mode detects a conflict and completes immediately. assert.ok(result.code !== null, "process should exit with a code"); const stdout = result.stdout.trim(); @@ -309,7 +309,7 @@ test("headless exits with code 11 after SIGINT", async (_t) => { const stderr = stripAnsi(result.stderr); // In environments where the process completes before SIGINT arrives - // (e.g., existing auto-mode session causes immediate conflict exit), + // (e.g., existing autonomous mode session causes immediate conflict exit), // exit code may be 0 or 1 instead of 11. The test verifies the // handler's behavior when it can be observed. if (stderr.includes("Interrupted")) { @@ -321,7 +321,7 @@ test("headless exits with code 11 after SIGINT", async (_t) => { ); } else { // Process exited before SIGINT arrived — acceptable in environments - // with running sf sessions that cause auto-mode conflict. + // with running sf sessions that cause autonomous mode conflict. // Verify it at least didn't crash. const combined = stripAnsi(result.stdout + result.stderr); assertNoCrashMarkers(combined); @@ -361,7 +361,7 @@ test("headless --output-format stream-json emits NDJSON on stdout", async (_t) = assert.ok(!result.timedOut, "test harness should not time out"); // Non-zero exit expected (no API key / timeout), but 0 is acceptable - // if auto-mode completes immediately (session conflict). + // if autonomous mode completes immediately (session conflict). assert.ok(result.code !== null, "process should exit with a code"); const stdout = result.stdout.trim(); diff --git a/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts b/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts index e2b235a7a..8460400de 100644 --- a/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts +++ b/src/tests/integration/web-auto-dashboard-lock-reconciliation.test.ts @@ -1,5 +1,5 @@ /** - * Regression test for #2705: Web UI shows "Start auto" even while auto mode is + * Regression test for #2705: Web UI shows "Start auto" even while autonomous mode is * already running. * * Root cause: collectAuthoritativeAutoDashboardData spawns a subprocess that diff --git a/src/tests/native-search.test.ts b/src/tests/native-search.test.ts index 2f76e97c3..a03f092dd 100644 --- a/src/tests/native-search.test.ts +++ b/src/tests/native-search.test.ts @@ -1312,7 +1312,7 @@ test("resolveSearchProvider returns minimax when MINIMAX_API_KEY is set and pref delete process.env.TAVILY_API_KEY; process.env.MINIMAX_API_KEY = "test-minimax-key"; - // With no Tavily key, minimax should be selected in auto mode + // With no Tavily key, minimax should be selected in autonomous mode const result = resolveSearchProvider(); assert.equal( result, @@ -1321,7 +1321,7 @@ test("resolveSearchProvider returns minimax when MINIMAX_API_KEY is set and pref ); }); -test("resolveSearchProvider prefers tavily over minimax in auto mode", async (_t) => { +test("resolveSearchProvider prefers tavily over minimax in autonomous mode", async (_t) => { const original = process.env.TAVILY_API_KEY; const original2 = process.env.MINIMAX_API_KEY; afterEach(() => { @@ -1332,12 +1332,12 @@ test("resolveSearchProvider prefers tavily over minimax in auto mode", async (_t process.env.TAVILY_API_KEY = "test-tavily-key"; process.env.MINIMAX_API_KEY = "test-minimax-key"; - // Tavily should be preferred in auto mode + // Tavily should be preferred in autonomous mode const result = resolveSearchProvider(); assert.equal( result, "tavily", - "Should prefer tavily over minimax in auto mode", + "Should prefer tavily over minimax in autonomous mode", ); }); diff --git a/src/tests/read-tool-offset-clamp.test.ts b/src/tests/read-tool-offset-clamp.test.ts index 76d3593b1..32d31e2cc 100644 --- a/src/tests/read-tool-offset-clamp.test.ts +++ b/src/tests/read-tool-offset-clamp.test.ts @@ -3,7 +3,7 @@ * * When offset exceeds file length, the read tool should clamp to the * last line instead of throwing, preventing downstream JSON parse errors - * in auto-mode milestone completion. + * in autonomous mode milestone completion. */ import assert from "node:assert/strict"; diff --git a/src/tests/schedule-cli-noninteractive.test.ts b/src/tests/schedule-cli-noninteractive.test.ts index 13fbff0e6..ee213bf46 100644 --- a/src/tests/schedule-cli-noninteractive.test.ts +++ b/src/tests/schedule-cli-noninteractive.test.ts @@ -37,7 +37,7 @@ test("cli.ts routes top-level schedule before interactive TUI", () => { "loader schedule banner must stay quiet for top-level schedule commands", ); assert.ok( - loaderSource.includes("scheduled auto-dispatch item"), + loaderSource.includes("scheduled autonomous dispatch item"), "loader banner must distinguish autonomous schedule entries from passive reminders", ); }); diff --git a/src/tests/trace-export.test.ts b/src/tests/trace-export.test.ts index 54079c88a..bc9299bb3 100644 --- a/src/tests/trace-export.test.ts +++ b/src/tests/trace-export.test.ts @@ -4,7 +4,7 @@ * Tests the trace collector's SF_TRACE_ENABLED gating, lifecycle (init/flush), * span creation (root/unit/tool), event recording, error recording, and JSON * file export — all exercised via direct module imports without requiring a - * live LLM or auto-mode session. + * live LLM or autonomous mode session. * * Run with: * SF_TRACE_ENABLED=1 npm run test:unit -- trace-export @@ -605,7 +605,7 @@ test("trace contains root span + unit span + error event for failed unit scenari return; } - // Simulate auto-mode with a milestone that has no plan (synthetic failure) + // Simulate autonomous mode with a milestone that has no plan (synthetic failure) initTraceCollector(tmpDir, "no-plan-session", "auto"); const milestoneSpan = startUnitSpan("milestone", "M003", { unitType: "milestone", diff --git a/src/traces.ts b/src/traces.ts index b7a4b3d14..f051baa2f 100644 --- a/src/traces.ts +++ b/src/traces.ts @@ -1,8 +1,8 @@ /** - * traces.ts — Structured trace data model and export utilities for auto-mode execution. + * traces.ts — Structured trace data model and export utilities for autonomous mode execution. * * Purpose: provide a lightweight, hierarchical span model that captures the - * full lifecycle of an auto-mode session (session → units → tools) so that + * full lifecycle of an autonomous mode session (session → units → tools) so that * post-hoc analysis, debugging, and cost attribution can be done from a * single JSON artifact instead of piecing together scattered logs. * @@ -126,7 +126,7 @@ export interface Span { * The top-level trace container. * * Purpose: hold the root span and session metadata so that a single file - * contains everything needed to replay or analyse an auto-mode session. + * contains everything needed to replay or analyse an autonomous mode session. * * Consumer: headless.ts (creates and finalizes), exportTrace/exportTraceToProject * (serializes), and external trace consumers. @@ -192,7 +192,7 @@ export function endSpan(span: Span, status: SpanStatus = "ok"): Span { * switches, gate completions) inside an existing span without mutating the * span's own fields. * - * Consumer: trace-collector.ts during auto-mode phase transitions. + * Consumer: trace-collector.ts during autonomous mode phase transitions. */ export function addEvent( span: Span, @@ -238,7 +238,7 @@ export function addError(span: Span, message: string, stack?: string): void { * Purpose: establish the top-level trace container and its root session span * in one call so that headless.ts never creates a trace without a valid root. * - * Consumer: headless.ts at the start of an auto-mode session. + * Consumer: headless.ts at the start of an autonomous mode session. */ export function createTrace( projectRoot: string, @@ -350,7 +350,7 @@ export function exportTrace(trace: Trace, path: string): void { * Filename: trace-<timestamp>.json * * Purpose: provide the standard, project-local trace sink so that every - * auto-mode session leaves a discoverable artifact in a known location. + * autonomous mode session leaves a discoverable artifact in a known location. * * Consumer: headless.ts in the normal exit path and signal handlers. */ diff --git a/src/welcome-screen.ts b/src/welcome-screen.ts index 94a696c2f..2e1ebf0e5 100644 --- a/src/welcome-screen.ts +++ b/src/welcome-screen.ts @@ -2,7 +2,7 @@ * SF Welcome Screen * * Two-panel bar layout: full-width accent bars at top/bottom (matching the - * auto-mode progress widget style), logo left (fixed width), info right. + * autonomous mode progress widget style), logo left (fixed width), info right. * Falls back to simple text on narrow terminals (<70 cols) or non-TTY. */ @@ -130,7 +130,7 @@ export function printWelcomeScreen(opts: WelcomeScreenOptions): void { // ── Render ────────────────────────────────────────────────────────────────── const out: string[] = [""]; - // Top bar — full-width accent separator, matches auto-mode widget ui.bar() + // Top bar — full-width accent separator, matches autonomous mode widget ui.bar() out.push(chalk.cyan(H.repeat(termWidth))); for (let i = 0; i < 8; i++) {