Trailing instrumentation from the discovery investigation. The error
catch still swallows non-fatal failures during --discover, just no
longer prints to stderr.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier commit (44fcfb643) incorrectly disabled phrase on repo-root
because I thought phrase retriever hung on full-workspace scope. After
clearing the corrupted cache (left by killing a mid-build vector process),
testing confirms:
- bm25 alone on repo root: works, 1m 50s cold, instant warm
- phrase alone on repo root: works after cache clear
- bm25+phrase on repo root: works after cache clear
- vector on scoped paths: works after cache build
The "hang" was from a corrupted/stale cache, not a sift bug.
.siftignore is properly excluding files (146K→2,660 indexed).
Revert chooseSiftRetrievers back to bm25,phrase for repo-root.
Tests: 184 files / 1974 tests pass.
Type check: clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Today's discovery cache stored only model IDs (string[]). Downstream
isZeroCost(model?.cost) check evaluated against undefined for any
dynamically-discovered model, so OpenRouter's zero-cost-but-not-:free
entries (owl-alpha, lyria-3-pro-preview, lyria-3-clip-preview,
openrouter/free) got silently blocked by the built-in provider policy.
Cache entry shape now: {id, cost?, contextWindow?} per model.
parseDiscoveredModels extracts pricing from OpenRouter's
/api/v1/models response (pricing.prompt/completion/input_cache_read/
input_cache_write → numeric cost.{input,output,cacheRead,cacheWrite}).
Other providers stay {id}-only — their /v1/models endpoints don't
ship pricing.
Migration: on first read of a legacy string[] cache, entries are
converted in-place to {id} objects and the file is rewritten. No cost
backfill (data wasn't there before), but the new readers handle them.
Cost wired into policy: isModelAllowedByBuiltInProviderPolicy calls
lookupDiscoveredModelCost("openrouter", modelId) as a fallback when
the static model registry has no cost data.
Plus: cli.ts --discover now eagerly refreshes SF-managed providers
(opencode, opencode-go, kimi-coding, xiaomi) that the SDK's adapter
doesn't cover — so they populate cache on first --discover instead
of waiting for a session-start lazy refresh.
Tests: 13 new across 5 groups (pricing extraction, round-trip, legacy
migration, policy gate happy/sad paths, Google provider compat).
Full suite: 184 files / 1971 tests, zero regressions.
Real-world result: openrouter/owl-alpha, google/lyria-3-pro-preview,
google/lyria-3-clip-preview, openrouter/free, plus any future
zero-cost models now pass the policy filter on the next discovery
refresh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add vitest.config.ts with forks pool, v8 coverage, and package aliases.
Run migrate-to-vitest.mjs to replace `from "node:test"` imports with
`from 'vitest'` across 749 test files, converting mock.fn→vi.fn and
mock.timers→vi fake timers where needed.
💘 Generated with Crush
Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit captures uncommitted modifications that accumulated in the
working tree across multiple in-progress workstreams. It is a snapshot
to clear the deck before sf v3 work begins; individual workstreams
should land separately on top of this.
Notable additions:
- trace-collector.ts, traces.ts, src/tests/trace-export.test.ts —
trace export plumbing
- biome.json — Biome linter configuration
- .gitignore — exclude native/npm/**/*.node compiled binaries
The bulk of the diff is across src/resources/extensions/sf/ (301 files)
and src/resources/extensions/sf/tests/ (277 files), reflecting the
ongoing sf extension work. Specific feature commits should follow this
snapshot rather than being archaeology'd out of it.
The 76MB native/npm/linux-x64-gnu/forge_engine.node compiled binary
was left out of the commit — it's now gitignored and built locally.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8 fixes from 3rd-pass scan:
1. web/components/sf/tempCodeRunnerFile.tsx: remove orphan VS Code
'Code Runner' artifact (850+ lines duplicated from shell-terminal.tsx).
Unreferenced but compiled into tsc project.
2. sf/phase-anchor.ts: writePhaseAnchor used plain writeFileSync — a crash
mid-write would corrupt the handoff checkpoint that readPhaseAnchor then
silently returns null for, losing cross-phase context. Switched to
atomicWriteSync (already used by sibling files).
3. sf/forensics.ts: same non-atomic writeFileSync on active-forensics.json
marker. Race with a concurrent reader produces an empty object and the
forensics session is lost. Switched to atomicWriteSync.
4. web/auto-dashboard-service.ts: paused-session.json existence was the
intended signal but a corrupt body silently dropped the paused flag so
the UI showed active. Now reports paused on file existence regardless
of body integrity, and warns on corruption.
5. sf/visualizer-data.ts: doctor-history.jsonl parser did .map(JSON.parse)
inside an outer catch. One corrupt line discarded 19 valid entries.
Per-line try/catch preserves the valid rows.
6. sf/files.ts: three parseInt calls without radix (step, total_steps,
totalSteps) — also missing || 0 fallback for NaN.
7. cli.ts: parseInt(process.versions.node) without radix. Split on '.' and
use radix 10 explicitly.
8. sf/slice-parallel-orchestrator.ts: silent 'catch {}' around spawn()
masked worker-spawn failures as 'no workers available'. Matches sibling
parallel-orchestrator.ts pattern — now logs via logWarning.
Skipped from the scan (need a real lock mechanism, not safe as a one-line
fix):
- sf/auto-dispatch.ts:164 (UAT counter race)
- sf/captures.ts:107 (CAPTURES.md append race)
Deferred (low-value):
- preferences-models.ts, key-manager.ts, auto-timers.ts silent catches
- dead variable in visualizer-data.ts
- google-gemini-cli.ts maxTokens clamp interaction
tsc --noEmit green at root.
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>
Followup to 828c5edf6. Swarm review flagged default=true as a latent
footgun: any SDK consumer of createAgentSession() that forgets to pass
persistModelChanges would silently mutate ~/.gsd/agent/settings.json.
Flip the default to false so persistence is opt-in. Interactive CLI
entry points now explicitly pass persistModelChanges: true:
- src/cli.ts interactive createAgentSession call
- packages/pi-coding-agent/src/main.ts: persistModelChanges = isInteractive
Print/rpc/mcp stay at the safe default. Tests updated (9/9 green).
`gsd -p --model X "msg"` was silently overwriting defaultProvider/
defaultModel in settings.json. One-shot verification runs must use the
model for that invocation only.
Adds an AgentSessionConfig.persistModelChanges flag (default true so
interactive behavior is unchanged), forwards it through createAgentSession,
and sets it false in main.ts when !isInteractive and in src/cli.ts print
mode. The gsd wrapper also skips validateConfiguredModel when --model is
explicitly passed, so a CLI-provided model can't trigger a fallback repair
that writes the wrong default back.
Three settings.json write sinks audited: agent-session._applyModelChange
(gated on flag), model-selector.ts (interactive only, unreachable in
print), startup-model-validation (gated by !cliFlags.model in print).
Regression: 8 source-assertion tests in
agent-session-print-mode-persist.test.ts.