Commit graph

3033 commits

Author SHA1 Message Date
Mikael Hugo
1a681caa86 fix(auto): repair retries reuse session context instead of starting cold
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>
2026-05-10 02:50:57 +02:00
Mikael Hugo
b464f2a78e fix: auto-fallback to ready provider instead of stopping autonomous mode
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>
2026-05-10 02:33:23 +02:00
Mikael Hugo
7c970088f1 fix: skip missing-checkpoint repair loop when runUnit is cancelled
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>
2026-05-10 02:29:41 +02:00
Mikael Hugo
d6bd49d0b6 fix: sfdb-doctor agent partial - lazy imports in agent-end-recovery, db-tools uses milestone-ids.js
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-10 02:18:55 +02:00
Mikael Hugo
a3f2479a4c fix: remove stale M001/M002 milestone dirs; fix dispatch-guard circular dep; fix telemetry normalization
- 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>
2026-05-10 02:12:13 +02:00
Mikael Hugo
ea360f6ad2 feat: add circular dep detection tool + fix duplicate milestone dirs + fix metrics NULL
- 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>
2026-05-10 01:56:08 +02:00
Mikael Hugo
15185c2e7d sf snapshot: uncommitted changes after 60m inactivity 2026-05-10 01:29:08 +02:00
Mikael Hugo
f66555456f sf snapshot: uncommitted changes after 72m inactivity 2026-05-10 00:28:55 +02:00
Mikael Hugo
6f174cabc1 sf snapshot: uncommitted changes after 59m inactivity 2026-05-09 23:16:14 +02:00
Mikael Hugo
d895cf2a16 fix: silence OpenTelemetry diag and LogTape meta startup warnings
- 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>
2026-05-09 21:54:26 +02:00
Mikael Hugo
024485f050 feat(traceability): append SF-Session id to autonomous commit messages
- 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>
2026-05-09 21:10:02 +02:00
Mikael Hugo
692328ad45 feat(memory): TTL expiry — supersede stale memories after 28/90 days
- 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>
2026-05-09 21:09:53 +02:00
Mikael Hugo
d2eda0cc12 feat(yolo): bypass all sandboxing — iteration limit, memory gates, guard breaks
YOLO = all guardrails off. When s.isYolo() is true the loop:
- Skips MAX_LOOP_ITERATIONS stop (logs warning, keeps going)
- Skips memory pressure stop (logs warning, accepts OOM risk)
- Bypasses guard breaks (logs warning, continues to next unit)

Build mode respects all these gates. YOLO does not.

Also fix notify messages: YOLO = no sandboxing, not just 'no prompts'
(autonomous mode already skips prompts — YOLO removes the safety net).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 20:00:56 +02:00
Mikael Hugo
6c132d5db0 fix(modes): clarify Build vs YOLO — Build can still pause; YOLO = no stops
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>
2026-05-09 19:57:56 +02:00
Mikael Hugo
b9ea000341 feat(modes): Ask mode gates autonomous start with Build mode confirmation
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>
2026-05-09 19:56:24 +02:00
Mikael Hugo
0712577f85 refactor(modes): collapse to Ask/Build; YOLO is a flag not a mode
- Remove 'plan' preset — ask covers discussion + planning, build covers execution
- Shift+Tab now cycles Ask ↔ Build (two stops, no awkward middle)
- YOLO (Ctrl+Y) forces Build mode if in Ask, then slams autonomous+deep+unrestricted
- Notify message shows 'switched to Build' when YOLO triggers a mode change
- YOLO off restores the pre-YOLO mode as before

Flow: Ask (user drives) → Build (SF drives) → Ctrl+Y (full send, no stops)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 19:53:22 +02:00
Mikael Hugo
fc60de80f5 fix(modes): presets own permissionProfile; build=unrestricted; default=normal
- 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>
2026-05-09 19:46:57 +02:00
Mikael Hugo
b93409cfa4 feat(headless): add -y / --yolo CLI flag to sf headless
- 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>
2026-05-09 19:05:32 +02:00
Mikael Hugo
995a57335b fix(surfaces): stamp correct surface in AutoSession + /mode yolo headless command
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>
2026-05-09 17:03:33 +02:00
Mikael Hugo
38a654d5e4 fix(ux): exit YOLO before Shift+Tab or /mode preset switch
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>
2026-05-09 16:56:14 +02:00
Mikael Hugo
f7381781fa feat(ux): Ask/Plan/Build mode presets + YOLO full-autonomy
- 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>
2026-05-09 16:47:14 +02:00
Mikael Hugo
6fb411df90 refactor(commands): eliminate dead handlers and catalog duplicates
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>
2026-05-09 16:36:04 +02:00
Mikael Hugo
aca13d1d9b fix(build): fix build:core — native tsconfig types, inventory sync, compat alias catalog
- 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>
2026-05-09 16:18:11 +02:00
Mikael Hugo
29d2750687 feat(db): metrics ledger → DB-first unit_metrics table (schema v54)
- 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>
2026-05-09 16:05:06 +02:00
Mikael Hugo
830a259630 chore: delete superseded esbuild test-compile scripts
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>
2026-05-09 16:04:41 +02:00
Mikael Hugo
9df46d2d88 feat(db): routing-history → DB-first (schema v53)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 16:02:47 +02:00
Mikael Hugo
bd0c612993 refactor(retire): drop JSONL fallback from judgment-log + delete one-shot migration scripts
- 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>
2026-05-09 15:55:10 +02:00
Mikael Hugo
a70004cf2a refactor(db-first): migrate triage outputs and runtime counters to sf.db
- 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>
2026-05-09 15:47:38 +02:00
Mikael Hugo
3b249c4144 feat(deploy): vision-to-production pipeline — deploy/smoke/release/rollback/challenge
- sf-db.js: ensureDeployTables() adds deploy_runs, smoke_results, release_records,
  rollback_runs (schema v51); migration block follows sleeptime v50
- preferences.js: deploy block merged (target, command, url, auto_release,
  release_type, publish_channel, adversarial_review)
- auto-prompts.js: buildDeployPrompt, buildSmokeProductionPrompt,
  buildReleasePrompt, buildRollbackPrompt, buildChallengePrompt
- auto-dispatch.js: 5 new rules — completing-milestone→challenge,
  completing-milestone→release, release-done→deploy,
  deploy-done→smoke-production, smoke-failed→rollback
- prompts/: deploy.md, smoke-production.md, release.md, rollback.md, challenge.md
- sf-db-migration test: bump expected schema version 49→51

The autonomous loop can now carry a milestone from complete-milestone all the
way to a live, smoke-verified, tagged release. Each stage is gated by prefs
(auto_release, deploy.target, deploy.url) so projects opt in per stage.
Challenge (adversarial review) runs before release when adversarial_review is set.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 15:25:47 +02:00
Mikael Hugo
00dc1ece89 feat(uok): 8-role swarm topology + DB-first sleeptime consolidation queue
- 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>
2026-05-09 15:11:19 +02:00
Mikael Hugo
5dbd318a76 refactor(uok): rename scheduler-v2 and plan-v2 to drop v2 suffix
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>
2026-05-09 14:45:02 +02:00
Mikael Hugo
9450b4a11d feat(sf): Tier 4 — ASK_USER_ELICITATION, CONFIGURE_COPILOT_AGENT, BACKGROUND_SESSIONS, MULTI_TURN_AGENTS, marketplace Enter install
- ask_user_elicitation tool: structured select/input form when flag is on
- spawn_agent tool: persistent named sub-agent via file-backed .sf/agents/<name>/history.jsonl
- /configure-agent command: list/add/remove MCP servers in .mcp.json (CONFIGURE_COPILOT_AGENT flag)
- Ctrl+Alt+B: opens bg session switcher overlay from .sf/sessions-queue.json (BACKGROUND_SESSIONS flag)
- openBgSessionSwitcher(): TUI ctx.ui.select picker for session switching
- marketplace.js: Enter key triggers installExtensionNpm (EXTENSIONS flag); footer hint updated
- Fix require() → ESM-safe imports in sf-tui/index.js (spawn, execSync, platform from static imports)
- catalog.js: /configure-agent entry added

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 07:30:33 +02:00
Mikael Hugo
3017663a69 fix(sf): inline extractBodyAfterFrontmatter — it is not exported from commands-prefs-wizard
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>
2026-05-09 05:37:26 +02:00
Mikael Hugo
b34f5997eb feat(sf): Tier 3 — /rubber-duck, /delegate, /share, /ask, /resume, /sidekicks
handlers/core.js:
- /ask <question> — ephemeral side question via ctx.fork (graceful
  fallback if fork unavailable)
- /resume [id] — session listing via ctx.listSessions; falls back to
  ~/.sf/sessions/ file listing with upgrade hint for BACKGROUND_SESSIONS

handlers/ops.js:
- /rubber-duck [topic] — constructive review subagent gated on
  RUBBER_DUCK experimental flag; routes via ctx.sendMessage
- /delegate [title] — GitHub PR creation via gh pr create --web;
  shows recent commits for context
- /share [md] — export session transcript to ~/sf-session-<ts>.md;
  copies path to clipboard (pbcopy / xclip / xsel)

catalog.js:
- Add /rubber-duck, /delegate, /share, /ask, /resume to TOP_LEVEL_SUBCOMMANDS

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 05:35:54 +02:00
Mikael Hugo
c1c3195f75 feat(sf): Tier 2 — SHOW_FILE tool, STATUS_LINE runner, /keep-alive, /sidekicks, Ctrl+G/T/X keybindings
sf-tui/index.js:
- Import getExperimentalFlag / setExperimentalFlag from experimental.js
- Ctrl+G — open project root in $EDITOR
- Ctrl+T — toggle show_reasoning experimental flag
- Ctrl+Alt+B — open /tasks background surface
- Ctrl+Alt+O — open last URL from agent output in browser
- STATUS_LINE runner: setInterval 5s, execFile user script, pipe stdout to ctx.ui.setStatus
- SHOW_FILE tool: pi.registerTool({name:'show_file',...}) gated on show_file flag; reads file slice, renders as fenced code block

handlers/ops.js:
- /keep-alive [off] — spawns caffeinate (macOS) or systemd-inhibit (Linux) as detached process; /keep-alive off kills it

handlers/core.js:
- /sidekicks — reads .sf/parallel/ subdirs, shows STATUS per worker

catalog.js:
- Add /sidekicks and /keep-alive to TOP_LEVEL_SUBCOMMANDS

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 05:33:24 +02:00
Mikael Hugo
eaf7165893 feat(sf): Copilot CLI feature parity — /experimental, /diff, /theme, /rename, /streamer-mode, /statusline, /search, /chronicle, /rewind, /instructions
Add experimental feature flag system and 10 new slash commands matching
Copilot CLI's experimental surface.

experimental.js:
- EXPERIMENTAL_FLAGS map (status_line, show_file, ask_elicitation,
  multi_turn_agents, extensions, configure_agent, background_sessions,
  rubber_duck, prompt_frame, streamer_mode)
- getExperimentalFlag / setExperimentalFlag / setAllExperimentalFlags
- Reads/writes project .sf/PREFERENCES.md via prefs frontmatter helpers

handlers/core.js:
- /experimental show|on|off|on <flag>|off <flag>
- /diff [--staged] — git diff HEAD or staged changes
- /theme [dark|light|dim|auto] — get/set UI theme in prefs
- /rename <name> — session name + OSC 2 terminal title
- /streamer-mode [on|off] — mask model names for screen sharing
- /statusline script <path>|off — configure footer status line script
- /search /find <query> — search session timeline entries
- /chronicle — git log + session events overview
- /rewind — revert last turn (ctx.rewind() with graceful fallback)
- /instructions — list all instruction files and their load status

catalog.js: add all 12 new commands to TOP_LEVEL_SUBCOMMANDS for autocomplete

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 05:30:25 +02:00
Mikael Hugo
9441022909 feat(tui): mode badge in normal footer + paused state indicator
- 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>
2026-05-09 04:41:00 +02:00
Mikael Hugo
848ac0dd99 feat(swarm): UOK-based swarm with PersistentAgent, AgentSwarm, and SwarmDispatchLayer
- PersistentAgent: stable identity across restarts, 3-tier memory (core
  blocks / recall / archival), durable SQLite inbox, sendAndWait request-
  reply, broadcast — all backed by UokCoordinationStore + MessageBus
- AgentSwarm: Letta-style group topology with ManagerType enum
  (round_robin, supervisor, dynamic, sleeptime), tag-based routing,
  shared agent_directory block, persist/load round-trip
- Role agents: CoordinatorAgent, WorkerAgent, ScoutAgent, ReviewerAgent
  extending PersistentAgent with preset tags + createDefaultSwarm factory
  (1 coordinator, 2 workers, 1 scout, 1 reviewer)
- SwarmDispatchLayer: routes UOK DispatchEnvelopes by workMode/unitType
  to the correct role agent, module-level cache, swarmDispatch() convenience fn
- 15 tests passing (identity persistence, messaging, registry, topology,
  dispatch routing) using real SQLite in tmp dirs
- Fix: tsconfig.resources.json — add types:[node] for TypeScript 6 compat

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 04:04:42 +02:00
Mikael Hugo
1c9b69b57e feat(skills): locked enforcement + workflow skill injection into agent context
Phase 1 — Skill Integrity:
- buildSkillRecord now maps locked: true frontmatter → record.locked
- discoverAllSkills builds locked name set (workflow always locked, bundled if
  frontmatter declares locked: true) and silently drops project/user skills
  that collide with a locked skill name (shadow protection)
- loader.js enforces locked=true unconditionally for workflow source skills
- getUserInvocableSkills now hides locked + workflow skills from /skills catalog
- loadSkills defaults includeWorkflow: true for production context

Phase 2 — Workflow Skill Wiring:
- buildWorkflowConstraintsBlock: loads workflow skills, filters by permission
  profile + work mode triggers, caps at 5, formats as ## Active Workflow
  Constraints block (behavioral guidelines, not invocable tools)
- buildSkillActivationBlock now appends workflow constraints block after the
  user skill_activation block — injected into every agent dispatch prompt
- getAutoSession provides workMode + permissionProfile; fallback to build/normal

Tests: 18 skills tests + 1 auto-prompts test pass (was 15)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 03:28:24 +02:00
Mikael Hugo
03e1f808bc feat: two-tier skill architecture with 8 workflow-internal skills
- Add src/resources/workflow-skills/ directory with 8 internal skills
  enforcing the 20 cross-cutting agent patterns from the styleguide:
  P0: observe-first, vertical-slice, context-lean
  P1: irreversible-ops, error-routing, assumption-log
  P2: handoff-readability, state-discipline
- Update skills/directory.js: WORKFLOW_SKILL_DIR constant, workflow
  source in discoverAllSkills, exported all constants inline
- Update skills/loader.js: workflow source forces userInvocable: false;
  loadSkills() defaults to includeWorkflow: true for production use;
  getUserInvocableSkills excludes workflow source
- Update skills/index.js barrel to export WORKFLOW_SKILL_DIR
- Update install-pi-global.js / uninstall-pi-global.js for workflow-skills
- Fix skills.test.mjs: pass includeWorkflow: false in 4 project-scope
  tests to isolate them from the 8 bundled workflow skills
- Remove genai-proxy extension (unused, replaced by direct provider integration)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 02:55:16 +02:00
Mikael Hugo
9875812c1b sf snapshot: uncommitted changes after 131m inactivity 2026-05-09 02:53:47 +02:00
Mikael Hugo
5188b93ddc feat: Shift+Tab cycles work modes, Ctrl+T cycles thinking level
- 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>
2026-05-09 00:42:41 +02:00
Mikael Hugo
22cbd83675 fix: update test snapshots for queryInstruction and complete /sf prefix Phase 2 deprecation
- Fix memory-embeddings-llm-gateway tests: add queryInstruction field to
  expected config objects after loadGatewayConfigFromEnv was updated to
  return it
- Add STYLEGUIDE.md: SF code standards adapted from ace-coder patterns
  (purpose doctrine, principles, anti-patterns STY001-012, thresholds,
  naming, patterns, documentation sections)
- Phase 2 /sf prefix removal: update all web components, browser dispatch,
  and tests to use direct commands (/autonomous, /stop, /next, /discuss,
  /init, /new-milestone) instead of /sf-prefixed forms
  - workflow-actions.ts: all command strings updated
  - chat-mode.tsx: SF_ACTIONS array updated
  - project-welcome.tsx: primaryCommand values updated
  - command-surface.tsx: fallback display updated
  - remaining-command-panels.tsx: usage examples updated
  - browser-slash-command-dispatch.ts: add stop/new-milestone/init to
    SF_PASSTHROUGH_COMMANDS so they route correctly to the extension
  - recovery-diagnostics-service.ts: suggestion commands updated
  - welcome-screen.ts: hint text updated
  - All affected tests updated to match new command strings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 00:17:47 +02:00
Mikael Hugo
e4c951ff0c feat: improve sf runtime self-reload and safeguards 2026-05-08 23:52:35 +02:00
Mikael Hugo
c5e9e4f9c8 fix: guard completeValidationRun and drop dead superseded_by column
- 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>
2026-05-08 21:45:58 +02:00
Mikael Hugo
6e6363da0d feat: migrate src/ core TS files to LogTape structured logging
Migrate 5 non-test TS files in src/ from console.* to LogTape:
- src/env.ts → getLogger('sf.core.env')
- src/resource-loader.ts → getLogger('sf.core.resource-loader')
- src/web/undo-service.ts → getLogger('sf.web.undo-service')
- src/web/cleanup-service.ts → getLogger('sf.web.cleanup-service')
- src/web/auto-dashboard-service.ts → getLogger('sf.web.auto-dashboard-service')

console.error(err) → log.error(msg, {error: err})
console.warn(msg) → log.warn(msg)

All CLI-facing output preserved. typecheck, lint pass.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-05-08 21:01:08 +02:00
Mikael Hugo
a46cbcbe40 Add more untracked runtime extension files 2026-05-08 20:51:18 +02:00
Mikael Hugo
fd06629f06 feat: add centralized LogTape logger module with dev/autonomous modes, PII redaction, and per-session file rotation
- Install @logtape/logtape, @logtape/pretty, @logtape/file, @logtape/redaction
- Create src/logger.ts with configureLogger() and getLogger() exports
- Dev mode: pretty console output with debug level
- Autonomous mode: JSON console + rotating file sink in .sf/logs/{sessionId}/
- PII redaction for API keys (sk-*, key-*, Bearer *) and home directory paths
- Category hierarchy: sf.core, sf.uok, sf.autonomous, sf.extension, sf.web
- Comprehensive tests in src/tests/logger.test.ts (10 tests)
- Wire configureLogger() into src/cli.ts startup path

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-05-08 19:58:11 +02:00
Mikael Hugo
8f02524fd7 Add untracked runtime extension files to git 2026-05-08 19:55:39 +02:00
Mikael Hugo
c3b202dd4c fix: use IS for NULL-safe equality in validation run queries
Consistent with latest_validation_state view. The verbose
(slice_id = :param OR (slice_id IS NULL AND :param IS NULL))
pattern is functionally equivalent to slice_id IS :param in SQLite.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-05-08 19:15:31 +02:00