- sf-db.js: add backfillUatVerdicts(basePath) that scans ASSESSMENT/UAT_RESULT
files for slices with no uat_verdict in DB and populates them on open
- dynamic-tools.js: call backfillUatVerdicts after openDatabase succeeds so
all 3 repos with existing verdict files are covered on next launch
- workflow-tool-executors.js: call setSliceUatVerdict when saving ASSESSMENT
at slice scope so future verdicts are written directly to DB
- workflow-helpers.js: remove all file fallbacks from checkNeedsRunUat;
verdict check is DB-only (backfill guarantees DB is populated on open)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
checkNeedsRunUat only checked for UAT_RESULT file, but the autonomous
runner writes ASSESSMENT files. This caused run-uat to dispatch 5x with
no verdict when only an ASSESSMENT (with verdict: PASS) existed.
Now ASSESSMENT file with any verdict counts as a completed UAT result,
stopping the infinite dispatch loop.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously required /autonomous first. Now any slash command (/next, /chat,
/clear etc.) caches the ExtensionCommandContext, so Ctrl+Y YOLO shortcut
works on first press after any command interaction.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Shortcut handlers (registerShortcut) receive ExtensionContext which has
no newSession(). This caused autonomous mode started via Ctrl+Y to
always crash with 'newSession is not a function'.
- AutoSession.lastCommandCtx: new field that persists across stopAuto/reset
so shortcut handlers can fall back to the last valid command context
- startAuto(): cache valid command ctx; fall back and notify user if ctx
has no newSession; return early with actionable message if no cache yet
- dispatchHookUnit(): same guard — resolve hookCtx before s.cmdCtx = ctx
- run-unit.js: last-resort guard before newSession() call returns clean
error category instead of TypeError
- steerable-autonomous-extension.js: rename ctrl+y → ctrl+alt+y to avoid
conflict with terminal yank built-in
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rewrite all 13 renamed tool descriptions to follow Copilot tool conventions:
- Imperative verb opening
- One sentence on what it returns
- One sentence on when to use it
- No internal jargon or SF-specific acronyms
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- fix(compaction): tokensBefore undefined crash on reload
compaction-orchestrator now falls back to preparation.totalTokens when
extension returns tokensBefore: undefined; compaction-summary-message
guards with ?? 0 defensively
- feat(exec): inline truncation notice in sf_exec digest
appends [stdout truncated — read full output: <path>] when
stdout_truncated=true so agent knows to use sf_exec_search
- feat(exec): wire onUpdate progress for sf_exec
calls onUpdate before execution starts with status/command so TUI
shows live feedback during long-running commands
- feat(security): prompt injection defense for external content
new sanitize-external-content.js utility: strips HTML comments,
detects 15 injection patterns (instruction override, role reassignment,
fake system messages, encoded payloads); wired into exec-tool digest
- feat(tools): sf_session_todo tool (persisted cross-compaction)
add/check/list ops; persists to .sf/session_todo.json; pending todos
injected into compaction summary block for context continuity
- feat(hooks): shell hooks surface (.sf/hooks/pre-tool/*.sh, post-tool/*.sh)
pre-tool hooks block tool execution (exit≠0 = block with stdout reason)
post-tool hooks fire-and-forget; JSON context piped to stdin; 5s timeout
- fix(db): WAL autocheckpoint disabled to prevent corruption
PRAGMA wal_autocheckpoint=0 in initSchema(); explicit checkpointWal()
after successful finalize verification — the only safe checkpoint point
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- unit-runtime: fall back to STATE.md for nextActionAdvanced when DB is
unavailable (restores test compat for reconcileDurableCompleteUnitRuntime-
Records; DB path still preferred in production)
- browser-slash-command-dispatch: remove 'stop' from SF_PASSTHROUGH_COMMANDS
so /stop correctly returns { kind: 'reject' } in browser mode (was falling
through to prompt/rpc instead of builtin-reject)
- bg-events: export MAX_PENDING_ALERTS so process-manager can re-export it;
satisfies session-memory-leaks contract test
- commands-handlers: guard effectiveScope assignment — only use requestedScope
when mode=audit AND requestedScope is truthy (avoids undefined propagation)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove setFooter(hideFooter) calls in auto-start.js and auto.js that were
overriding the sf-tui footer with a near-invisible stub. The sf-tui footer
already checks isAutoActive() and routes to renderAutoFooter — no override
needed. Also remove now-unused hideFooter import from auto.js.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The repair loop was classifying agent reports of 'tool unavailable' as
'checkpoint-tool-unavailable' even when sf_autonomous_checkpoint IS
registered in the manifest. This caused a self-referential loop: the
repair prompt re-requested the same tool call, the agent re-reported
unavailability, and the cycle repeated (4 repair attempts).
Fix: before classifying as 'checkpoint-tool-unavailable', verify the tool
is in the manifest. If it IS registered, reclassify as
'mentioned-checkpoint-without-tool' — the tool exists, the agent just
didn't call it. Also added existsSync to the ES module fs import in
autonomous-solver.js.
Test: new case in autonomous-solver.test.mjs verifies the reclassification
when tool IS in manifest.
When the autonomous solver fails to produce a checkpoint and enters the
repair loop, subsequent retries previously called newSession() each time,
wiping the conversation history. The agent restarted cold with no memory
of what it had tried, what tools it had called, or why it failed — making
meaningful repair nearly impossible.
This change adds a keepSession option to runUnit(). When true, the
newSession() call and session-switch guard logic are skipped; the repair
prompt is sent as a follow-up in the existing conversation. The agent can
now see its prior tool calls, file reads, and failure context when deciding
how to fix the issue.
Policy:
- First attempt at each unit: keepSession=false (clean session, correct
for independent slice boundaries — system prompt carries project state)
- Repair retries within the same unit: keepSession=true (agent carries
full context of what it already tried)
- Next unit after success/failure: keepSession=false (clean boundary)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the selected model's provider is not request-ready:
1. Pre-flight check before runUnit: find any ready provider, switch to it
and continue. Only stop if no ready provider exists.
2. Post-runUnit cancelled handler: same logic — reselect + return 'continue'
instead of silently breaking.
3. Both paths now emit a visible ctx.ui.notify so the user can see what
happened ('provider X not ready — retrying with Y/model').
Previously: cancelled instantly, all 4 repair attempts also cancelled,
paused with misleading solver-missing-checkpoint and no user notification.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When runUnit() returns status='cancelled' (provider not ready, session
failed, timeout), there is no checkpoint to repair. Previously the code
called assessAutonomousSolverTurn() which saw no checkpoint and entered
the 4-attempt repair loop — all of which also cancelled instantly,
burning retries before pausing with a misleading solver-missing-checkpoint
reason instead of surfacing the real provider/session error.
Now: cancelled result short-circuits to { action: 'none' }, skipping the
repair loop and falling through to the existing cancelled handler which
correctly surfaces provider-not-ready, timeout, and session-failed errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove stale .sf/milestones/M001/ and M002/ (not in DB, were blocking dispatch)
- dispatch-guard.js: import findMilestoneIds from milestone-ids.js directly (not
via guided-flow.js, which is in the circular-dep cluster)
- auto.js: normalize 'Cannot dispatch' → prior-slice-blocker, 'SF resources updated'
→ resources-stale, 'Stuck:' → stuck in telemetry (was silently bucketing as 'other')
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add scripts/check-circular-deps.mjs using madge; npm run check:circular
and check:circular:ext scan src/ and the SF extension respectively
- findMilestoneIds() is now DB-first: reads from milestones table when DB is
open so stale/duplicate filesystem dirs (M001/ and M001-6377a4/) are never
returned; falls back to fs scan only during early bootstrap
- milestone-id-utils.js was a stale duplicate; replaced with re-exports from
canonical milestone-ids.js
- metrics-central.js: guard null/undefined counter/gauge/histogram values
with ?? 0 to prevent NOT NULL constraint failure on metrics.value
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Align google-gemini-cli-provider's @google/gemini-cli-core dep from
0.40.1 → 0.41.2 to match root; npm deduplicates to a single module
instance, so diag.setLogger is called only once (no 'overwritten' warn)
- Add logtape.meta logger config at 'warning' level to suppress LogTape's
own 'loggers are configured' info message on every startup
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- git-service.js autoCommit() accepts optional sessionId param
- Appends 'SF-Session: <id>' trailer to commit message when present
- Falls through cleanly when sessionId is undefined (quick tasks, templates)
- worktree.js autoCommitCurrentBranch() forwards sessionId
- auto-post-unit.js autoCommitUnit() reads session ID from getAutoSession()
via s.cmdCtx?.sessionManager?.getSessionId?.() — same pattern as auto.js
Mirrors Copilot's session logs linked to each commit for cross-session traceability.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add expireStaleMemories(unstartedTtlDays=28, maxTtlDays=90) to sf-db.js
- Never-accessed (hit_count=0) memories expire after 28 days
- All memories expire after 90 days regardless of hit_count
- Marks superseded_by='ttl-expired' (non-destructive, same as CAP_EXCEEDED pattern)
- Returns count of expired memories (non-fatal on failure)
- Call from auto-start.js after DB opens at autonomous session start
- Logs warning with count if any memories expired
- Catches errors silently — TTL failure never blocks autonomous start
Mirrors Copilot Memory's 28-day TTL model learned from research.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Build mode: autonomous + broad permissions, may still pause at gates or
risky operations.
YOLO: Build + deep model + no stops, no confirmations at all.
- Fix Ask→Build confirm dialog message (was wrongly saying 'no further prompts')
- Fix YOLO notify messages to be accurate about what YOLO uniquely adds
- YOLO-off message clarifies Build may still pause
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When SF would start autonomous execution (startAuto) and the session is
in Ask mode (runControl=manual), it shows a confirm dialog:
'Switch to Build mode? SF will execute without further prompts.'
[Switch to Build] [Stay in Ask]
- On confirm: atomically applies the build preset (autonomous +
unrestricted), then proceeds with execution.
- On decline: returns without starting — user stays in Ask.
- skipModeGate option available for callers that already handle this
(e.g., explicit /autonomous command after user intent is clear).
This covers all startAuto callers: checkAutoStartAfterDiscuss, guided
flow action buttons, /next, and /autonomous.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Each preset now declares its own permissionProfile:
ask → normal (conversational, can read/run safe commands)
plan → normal (structuring, not executing)
build → unrestricted (go do it, no permission prompts)
- setMode() calls for Shift+Tab and /mode now include permissionProfile
so switching preset atomically sets all four axes.
- inferPresetName() includes permissionProfile in the match so status
display shows 'build mode' only when permissions are also unrestricted.
- AutoSession default permissionProfile: 'restricted' → 'normal'
(restricted was too conservative even for ask/chat use).
Flow: Ask (discuss) → Plan (structure) → Build (autonomous+unrestricted)
YOLO (Ctrl+Y) = build + autonomous + deep + unrestricted (turbo on top).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- HeadlessOptions.yolo added
- parseHeadlessArgs handles --yolo and -y (short form)
- SF_YOLO=1 is injected into the RPC child env when flag is set
- AutoSession._loadPersistedModeState() checks SF_YOLO=1 and
auto-activates YOLO mode (build+autonomous+deep+unrestricted)
on session startup
Usage:
sf headless -y autonomous # YOLO + autonomous mode
sf headless --yolo next # YOLO + run next unit
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Surface stamp:
- AutoSession._loadPersistedModeState() now calls detectSurface() to stamp
the correct surface (headless/web/tui) from env vars on every startup.
Persisted surface value was the previous launch's surface — wrong when
switching between TUI and headless on the same project.
SF_HEADLESS=1 → 'headless', SF_WEB_BRIDGE_TUI=1 → 'web', else 'tui'.
/mode yolo:
- handleModeCommand now recognises 'yolo' as a toggleable special case.
Headless callers can now run: sf headless --command '/mode yolo'
Same behaviour as Ctrl+Y: full-autonomy slam + settingsManager bypass.
/mode catalog description updated to list 'yolo' as an option.
Documentation:
- headless.ts /query and /doctor short-circuits annotated as intentional
architecture trade-offs with a note to keep them in sync with the extension.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ghost state bug: pressing Shift+Tab or /mode while YOLO was active left
session.yolo=true and settingsManager bypass ON even though mode changed.
- Shift+Tab handler calls s.toggleYolo() + settingsManager.toggleYOLO()
before cycling to the next preset when YOLO is active
- handleModeCommand does the same before applying a named preset
This keeps yolo flag, status display ('SF — 🚀 YOLO'), and safe-git bypass
in sync with the actual running mode at all times.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add SF_MODE_PRESETS (ask/plan/build) to operating-model.js
ask = chat | manual | fast
plan = plan | assisted | smart
build = build | autonomous | smart
- Shift+Tab cycles Ask → Plan → Build presets instead of raw workModes
- /mode ask|plan|build sets all three axes atomically
- formatModeState shows preset name when current mode matches a preset
YOLO (Ctrl+Y):
- session.toggleYolo() slams all axes to build+autonomous+deep+unrestricted
and saves pre-YOLO mode for restore on toggle-off
- Terminal title shows 🚀 badge when YOLO is active
- Status line shows 'SF — 🚀 YOLO' when active
- Also calls settingsManager.toggleYOLO() for safe-git prompt bypass
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Dead code removed:
- ops.js: second 'rate' handler block (lines 248-256) — unreachable because
the top-level import block at line 187 fires first and returns true
- autonomous.js: 'stop' handler (trimmed === 'stop') — /stop is in
BASE_RUNTIME_COMMANDS, platform intercepts it before SF extension sees it
- core.js: 'session-rename' handler block — /rename is the canonical command;
alias added zero value and created confusion
Catalog duplicates fixed:
- 'plan' appeared twice (line 85 + 248) with contradictory descriptions;
merged into single entry describing both phase-trigger and artifact-promotion
- 'steer' appeared twice (line 72 + 167); removed the TUI-panel shortcut
entry (Shift+Tab is a keyboard binding, not a slash command)
Discoverability fix:
- 'recover' was handled in ops.js but absent from catalog and manifest;
added to both with accurate description (reconstruct DB hierarchy from
markdown on disk)
- 'session-rename' removed from catalog and manifest; users use /rename
Check script improvements:
- HIDDEN_OR_ALIAS_SUBCOMMANDS now filters both directions of the catalog
↔ handler consistency check (was only filtering 'handled but missing from
catalog', not 'catalog but no SF handler')
- Added 'stop' to HIDDEN_OR_ALIAS_SUBCOMMANDS with comment explaining it is
platform-intercepted; removed 'recover' (now properly in catalog)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- packages/native/tsconfig.json: add types:["node"] so Buffer/process/
__dirname resolve correctly (root tsconfig has no lib/types for node)
- scripts/check-sf-extension-inventory.mjs: add footer-config, undo-turn,
review-code to HIDDEN_OR_ALIAS_SUBCOMMANDS (they are aliases for statusline,
rewind, rubber-duck)
- src/resources/extensions/sf/commands/catalog.js: add session-rename entry
(real command handled in core.js, was missing from TOP_LEVEL_SUBCOMMANDS)
- src/resources/extensions/sf/extension-manifest.json: add 19 commands that
exist in catalog but were absent from provides.commands
- src/resources/extensions/sf/guided-flow.js: remove showSmartEntry compat alias
(no live imports — only a comment reference in headless-context.ts)
- src/resources/extensions/sf/graph.js: remove graphFromDefinition compat alias
build:core now passes end-to-end.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add unit_metrics and project_metrics_meta tables in schema v54
- Export upsertUnitMetrics, getAllUnitMetrics, pruneUnitMetrics,
getProjectStartedAt, setProjectStartedAt from sf-db.js
- Rewrite metrics.js disk I/O: remove json-persistence/paths imports,
replace saveJsonFile/loadJsonFile with DB calls
- Public API surface unchanged: loadLedgerFromDisk, getLedger,
pruneMetricsLedger all return same shapes
- Update schema version assertion in sf-db-migration.test.mjs to 54
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
compile-tests.mjs and dist-test-resolve.mjs were for an older esbuild+node
--test approach. The project now uses Vitest end-to-end. Dead code.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- judgment-log.js: DB is always available; strip appendFileSync/readFileSync
JSONL fallback paths and resolveJudgmentLogPath export. Non-fatal on DB
failure is preserved — agent loop must never be disrupted.
- Delete scripts/migrate-to-vitest{,-all}.mjs and fix-vitest-api.mjs —
one-shot migration tools that have already run; no longer needed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- sf-db.js v52: triage_runs/evals/items/skills, runtime_counters,
validation_attention_markers tables + CRUD functions
- commands-todo.js: write triage evals/items/skills to DB instead of JSONL;
keep markdown report as human artifact
- auto-dispatch.js: rewrite-count + uat-count use runtime_counters table
with file fallback; validation attention markers use DB with file fallback
- migration test: bump expected schema version 51 → 52
- jsonl-schema-versioning.test.mjs: update triage test to assert DB rows
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- VALID_ROLES: coordinator/worker/scout/reviewer/planner/verifier/scribe/adversary (dropped architect)
- swarm-roles.js: PlannerAgent, VerifierAgent, ScribeAgent, AdversaryAgent + createDefaultSwarm wires all 8
- agent-swarm.js: route() maps plan/verify/document/challenge to new roles; _deriveWorkMode() covers all unitType patterns; getTopology() exposes all 8 role buckets; sleeptime case is now non-blocking (INSERT to DB queue instead of blocking memoryAgent.receive())
- sf-db.js: sleeptime_consolidation_queue table (schema v50) — id, conversation_agent, memory_agent, content, status, created_at, processed_at, result
- auto/loop.js: drainSleeptimeQueue() runs between every autonomous unit; reads pending queue rows, runs consolidation via PersistentAgent, marks done/error in DB
- core.js: workModes list includes verify/document/challenge
- skills/loader.js: isSkillRelevant() handles verify→review and document→docs trigger aliases
- swarm.test.mjs: updated topology assertions for 9-agent swarm
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
v1 no longer exists — the suffix is just noise. Update all import sites
and rename the test file to match.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>