Same class of bug as the service_tier fix: preference fields declared
in SFPreferences type and consumed by feature code, but never copied
into the validated output, so they silently become undefined when set
in PREFERENCES.md.
Found by diffing validated.<field> vs the interface declarations:
- forensics_dedup (boolean) — /sf forensics issue de-dup opt-in
- stale_commit_threshold_minutes (number) — doctor safety-commit cadence
- widget_mode ("full"|"small"|"min"|"off") — dashboard widget sizing
- slice_parallel ({ enabled?, max_workers? }) — slice-level parallelism
- modelOverrides (Record) — per-model capability patches
- safety_harness ({ enabled?, evidence_collection?, ... }) — LLM safety
Validation is kind-appropriate: primitives get type + range checks,
nested objects get object-shape guards with pass-through for now.
Consumer sites already treat missing fields as optional, so landing
shallow validation first is safe.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
validatePreferences() is a strict allow-list — it copies only explicitly
handled fields from input to output. service_tier was in
KNOWN_PREFERENCE_KEYS (no unknown-key warning) but was never copied into
the validated output, so users setting service_tier: priority or flex in
PREFERENCES.md silently got undefined.
This was a latent bug from before today's work: the new "off" value hit
it first because I verified end-to-end, but priority/flex had the same
issue. /sf fast on writes "priority" via writeGlobalServiceTier —
correctly — and then the next read drops it on the floor.
Now: service_tier is validated against {priority, flex, off} and copied
through. Invalid values raise an error rather than being silently lost.
Verified: dr-repo's service_tier: "off" in .sf/PREFERENCES.md now loads
correctly via loadEffectiveSFPreferences().preferences.service_tier.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three TUI-only decorations were running their full session-lifecycle
handlers even in headless mode, where there is no footer to render
into. Most visibly, the emoji extension's AI auto-assign path made a
real LLM call to pick a 🚀/✨/🎯 that nothing would ever see.
- sf-tui/emoji.ts: session_start and agent_start handlers early-return
when !ctx.hasUI. Commands stay registered so /emoji still works if
someone runs it, but the lifecycle work (state loading, AI emoji
selection, setStatus emission) is skipped.
- sf-tui/color-band.ts: session_start and session_switch handlers
early-return when !ctx.hasUI. Avoids unnecessary state-file writes
and resize-listener attachment in headless runs.
- sf-permissions/index.ts:setLevel: guards the setStatus("authority",
…) call behind ctx.hasUI. The existing session_start path was
already gated — this closes the permission-change code path.
Headless stderr was already filtering these keys, so the user-visible
output is unchanged. This eliminates the underlying RPC traffic and
— more importantly — stops spending LLM tokens on decorative emoji
selection in headless runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an explicit disable state (service_tier: "off" in PREFERENCES.md)
that short-circuits every service-tier surface:
- No setStatus("sf-fast", …) footer events — RPC traffic stops, not
just the stderr filter masking it.
- No service_tier field ever injected into before_provider_request
payloads, regardless of model.
- /sf fast on and /sf fast flex refuse to write a tier while "off" is
set, instructing the user to clear the preference first.
- /sf fast status shows "(service_tier: \"off\" in preferences)" so
the explicit disable is visible at a glance.
Rationale: setups that never run gpt-5.4 (Claude / Kimi / MiniMax /
GLM / Gemini-only shops) have no use for the feature. "off" lets them
fully turn it off rather than relying on model-support gates to
silence it.
6 regression tests added in service-tier.test.ts covering the new
isServiceTierDisabled export, hook short-circuit ordering, and the
/sf fast command refusal. 52 / 52 service-tier tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the bundled Ollama extension probed http://localhost:11434
on every session_start, which was wasted work for users who never run
Ollama locally. It also registered slash commands, loaded the
ollama_manage tool, and (in interactive mode) set a "[phase] ollama"
status indicator that leaked into headless stderr.
Now the default export short-circuits immediately when OLLAMA_HOST is
not set — no probe, no command registration, no tool loading, no
status indicator. probeAndRegister also double-checks so any direct
caller stays consistent.
ollama-cloud is unaffected: set OLLAMA_HOST=https://ollama.com and
OLLAMA_API_KEY=<key> and everything runs as before. Self-hosted local
Ollama is likewise unaffected — set OLLAMA_HOST=http://localhost:11434
explicitly to re-enable the old behavior.
3 new regression tests cover the opt-in guard. All 138 ollama tests
pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an optional model param to SubagentParams, TaskItem, and ChainItem so
callers can override the agent's default model at dispatch time. This is
the primitive that ace-coder's Task() tool exposes via its `model` arg —
SF's subagent tool previously ignored model at the tool level, picking it
up only from the named agent's .md frontmatter.
- SubagentParams.model applies to single mode, or as a batch-level default
for tasks/chain steps that don't set their own.
- TaskItem.model and ChainItem.model override per-task / per-step.
- runSingleAgent and runSingleAgentInCmuxSplit gain a trailing
modelOverride parameter that flows into buildSubagentProcessArgs.
- buildSubagentProcessArgs uses modelOverride ?? agent.model when picking
the --model arg for the child process.
Side benefit: retroactively fixes the latent bug where
reactive_execution.subagent_model was threaded into prompt instructions
but ignored by the actual tool.
9 regression tests added in subagent/tests/model-override.test.ts.
All 53 subagent-related tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 6 new skills under src/resources/extensions/sf/skills/
- Revert broken dispatch_model extension from auto-prompts.ts — the subagent
tool has no model-override param; skills stay as pure text injection
- Fix discuss-headless.md: advisory-partner section now correctly describes
that independent review runs via gate-evaluate/validate-milestone (Q3/Q4,
MV01-MV04) with the validation model, not inline self-review
- Include pm-planning, codebase-analysis, architecture-planning, and
feature-gap-analysis skill activations in discuss-headless Active Skills
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
P1 (phase-timeout mutation race): withPhaseTimeout now stores the still-running
phase promise in _danglingPhasePromise when a timeout fires. Each loop iteration
drains that promise (with try/catch) before starting new work, preventing the
timed-out phase from mutating state concurrently with the next iteration.
P2 (verification_status backfill): Schema migration v17 now runs a backfill UPDATE
after adding the new column, deriving verification_status from existing
verification_evidence rows. Projects upgraded mid-slice will have correct
all_pass/partial/all_fail values immediately rather than empty strings that
bypass the prior-task guard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements all fixes from the auto-hardening audit plan:
P1-A: Per-phase timeout watchdog — withPhaseTimeout() wraps preDispatch/dispatch/finalize;
on timeout emits warning, increments consecutiveFinalizeTimeouts, continues loop.
Configurable via preferences.auto_supervisor.phase_timeout_minutes (default: 10).
P1-B: Verified already wired (MAX_COOLDOWN_RETRIES → stopAuto+break). No change needed.
P1-C: Worker timeout in parallel orchestrator — kills workers running beyond
parallel.worker_timeout_minutes (default: 120 min) in refreshWorkerStatuses().
P2-A: Memory injection into dispatch prompts — buildMemoriesBlock() appended to
plan-milestone inlined[] context and added as memoriesSection in execute-task.
P2-B: Memory extraction retry — one 2s-delayed retry in the catch block of
extractMemoriesFromUnit(); second failure is silently swallowed (non-fatal).
P3-A: Partial verification state in DB — verificationStatus ("all_pass"/"partial"/"all_fail")
derived from verificationEvidence.exitCode array and stored in new tasks column.
New dispatch rule blocks next task when prior task has all_fail status.
P3-B: Gate omission rationale enforcement — minOmissionWords added to GateDefinition
(Q3=20, Q5=15, Q6=10, Q7=15). Short rationale upgrades verdict "omitted" → "flag".
P4-A: Doctor issues → reassess escalation — pre-dispatch health check in loop.ts detects
issues referencing slice IDs and queues reassess-roadmap sidecar instead of pausing.
P4-B: File overlap preemption — analyzeParallelEligibility() sets eligible:false when
the overlapping milestone is currently running (not just eligible/queued).
P5-A: Deferred requirement tracking — parseDeferredRequirements() added to files.ts;
completing-milestone rule warns (via logWarning) when deferred reqs targeting
the milestone were not validated before completion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cherry-pick of gsd-build/gsd-2@0b7a05491 adapted for sf/ paths:
- Expand RATE_LIMIT_RE to cover quota-window phrasing (hit your limit, usage limit, quota reached)
- Rate-limit errors bypass transient-deferral early return so model fallback executes
- Add setCurrentDispatchedModelId() to keep AUTO dashboard label in sync after fallback switch
- 4 regression tests for classifier coverage and structural guards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RTK seam tests: SF_RTK_PATH was set then immediately deleted in withFakeRtk
due to copy-paste duplication from the GSD→SF rename — fake RTK binary was
never injected, so all 5 seam tests ran the raw command instead of the
rewritten one.
Remaining 21 fixes from the GSD→SF rebrand:
- initial-gsd-header-filter.test.ts: import renamed filterInitialSfHeader
- dist-redirect.mjs: doubled scope prefix @singularity-forge/@singularity-forge/*
→ @singularity-forge/* (5 specifiers affected)
- forensics-issue-routing.test.ts: regex used sf-build/sf-2, prompt says
singularity-forge/sf-run — align regex to match the actual prompt
- key-manager.test.ts: GROQ_API_KEY set in dev env made empty-key test
report configured:true — isolate with save/delete/restore
- create-gsd-extension-paths.test.ts: skill dir doesn't exist in this repo,
skip both tests gracefully with t.skip()
- sf-usage-bar/index.ts: replace execSync(`which ${cmd}`) with spawnSync to
fix unescaped shell interpolation static analysis failure
- sf-notify/index.ts: convert enum to const object — strip-only TS mode
does not support enums
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allows tool input to be passed to vibe setter for future context-aware
customization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Consolidated into unified sf-tui extension. Removed old git-footer
extension in favor of sf-tui footer implementation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Integrates footer rendering, working vibes, prompt history stash,
and git status tracking into unified TUI enhancement extension.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Provides shared stash management functions and overlay UI for browsing
and selecting previous prompts. Supports 1-9 quick picking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Displays git branch status, dirty state, extension statuses, model info,
cost, and context usage percentage in the footer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Provides context-aware working status messages based on prompt keywords
and tool names, with random emoji for visual interest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Provides GitStatus interface and refreshGitStatus function for displaying
repository branch, dirty state, untracked files, and commit counts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- registers hook to trigger vibe state based on tool name and input
- enables working-vibes extension to respond to tool invocations
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- genai-proxy: disable full proxy implementation due to auth bootstrap
limitations at package boundary; throw clear error instead
- proxy-command: add try-catch error handling around startProxy
- prompt-history: new extension with Ctrl+Alt+H (or Ctrl+Shift+H fallback)
to navigate and insert previously-stashed prompts. Stash limited to 20
entries in ~/.sf/agent/prompt-history.json
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Updates workflow tool names, documentation references, and internal naming
conventions across MCP server, CLI, tests, and web components to complete
the singularity-forge rebrand from gsd to sf.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Updates channel prefixes, log messages, comments, and configuration values
across daemon, mcp-server, and related packages to complete the rebrand from
gsd to sf-run naming.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Final rebrand: rename remaining Rust source file to complete the gsd → forge
transition. All parser references already use forge_parser after earlier commits.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Accept deletion of gsd-phase-state.ts (renamed to forge-phase-state.ts earlier)
- Accept deletion of create-gsd-extension/ (renamed to create-forge-extension/ earlier)
- These renames were part of the rebrand and are preserved in commit history
Stabilize git state after restoration operations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Print mode was constructing DefaultResourceLoader directly, which
bypassed the GSD extension registry filter and let disabled bundled
extensions leak through. With the community @0xkobold/pi-ollama
installed, every `gsd -p` invocation printed an /ollama command
conflict because the bundled ollama extension (explicitly disabled
in ~/.gsd/extensions/registry.json) was still being loaded.
- Add extension-manifest.json for the bundled ollama extension so the
registry's id-keyed disable entry can actually target it.
- Extend buildResourceLoader() with an options bag for print-mode
callers (additionalExtensionPaths, appendSystemPrompt).
- Switch print mode to buildResourceLoader() so the registry filter
(extensionPathsTransform) runs in both TUI and print paths.
Also fix a stderr leak in the GSD codebase-generator: execSync("git
ls-files") was inheriting stderr to the parent, so running gsd from a
non-repo cwd (e.g. $HOME) printed "fatal: not a git repository" before
the catch silently returned []. Pipe stderr so it lands in the thrown
Error instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Analyzes completed milestone artifacts (PLAN.md, SUMMARY.md, and
optionally VERIFICATION.md + UAT.md) and dispatches an LLM turn that
extracts institutional knowledge into four categories — Decisions,
Lessons, Patterns, Surprises — with source attribution per item.
Output: .gsd/milestones/<id>/<id>-LEARNINGS.md with YAML frontmatter
(counts per category, list of missing optional artifacts). Running
twice overwrites the previous file. Integrates with capture_thought
when available; silently skips if not.
New files:
- commands-extract-learnings.ts — handler + pure helpers
- tests/commands-extract-learnings.test.ts — 32 unit tests (TDD)