- 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>
When the agent is already streaming (system-triggered turn, e.g. autonomous
dispatch at startup) and the user sends a message without an explicit
streamingBehavior, default to followUp instead of steer.
Steer injects mid-stream into the current turn. FollowUp queues the
message as a clean new turn after the system work finishes — which is
what the user expects when they type their first message at startup.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace 'Use steer() or followUp()' with plain language guidance.
Users see this when sending a message while the agent is still working.
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>
extractBodyAfterFrontmatter is a private function in commands-prefs-wizard.js.
Inline a local copy in experimental.js and handleThemeCommand (core.js) rather
than importing a non-existent export.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- renderFooter: add mode badge (compact at <80 cols, full at ≥80 cols)
to right side so active mode is always visible, not only during auto
- renderAutoFooter: refactor to use shared renderModeBadge instead of
duplicating badge logic inline
- renderModeBadge: handle paused state — all badge parts dim, 'P!' prefix
shown in compact form, 'paused ·' prefix shown in full form
- getMode(): surface session.paused as a field on the returned mode object
so badge renderers can reflect paused state without inspecting session directly
- Export renderModeBadge from header.js; footer imports it via FOOTER_THEME adapter
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Shift+Tab: cycles work mode (chat→plan→build→review→repair→research)
when idle; opens steerable panel during autonomous execution
- Ctrl+T: cycles thinking level (replaces shift+tab binding)
- Removed toggleThinking from default Ctrl+T (superseded by cycleThinkingLevel)
- Drop hint for toggleThinking from interactive mode help text
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix memory-embeddings-llm-gateway tests: add queryInstruction field to
expected config objects after loadGatewayConfigFromEnv was updated to
return it
- Add STYLEGUIDE.md: SF code standards adapted from ace-coder patterns
(purpose doctrine, principles, anti-patterns STY001-012, thresholds,
naming, patterns, documentation sections)
- Phase 2 /sf prefix removal: update all web components, browser dispatch,
and tests to use direct commands (/autonomous, /stop, /next, /discuss,
/init, /new-milestone) instead of /sf-prefixed forms
- workflow-actions.ts: all command strings updated
- chat-mode.tsx: SF_ACTIONS array updated
- project-welcome.tsx: primaryCommand values updated
- command-surface.tsx: fallback display updated
- remaining-command-panels.tsx: usage examples updated
- browser-slash-command-dispatch.ts: add stop/new-milestone/init to
SF_PASSTHROUGH_COMMANDS so they route correctly to the extension
- recovery-diagnostics-service.ts: suggestion commands updated
- welcome-screen.ts: hint text updated
- All affected tests updated to match new command strings
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- completeValidationRun now checks status='running' in WHERE clause and
throws if no row was updated (catches double-complete and invalid runId)
- Remove unused superseded_by column from v46 CREATE TABLE DDL
- Add migration v47 to DROP COLUMN superseded_by from existing DBs
- Bump SCHEMA_VERSION to 47
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>