diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d48201a..d5d887910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,56 @@ # Changelog +## [1.37.0.0] - 2026-05-14 + +## **Split-engine gbrain: remote MCP for brain, local PGLite for code.** +## **Symbol-aware code search now coexists with cross-machine knowledge.** + +Path 4 (Remote MCP) setup gets a new opt-in at Step 4.5: a tiny local PGLite (~30s, ~120 MB) for `gbrain code-def`, `code-refs`, `code-callers` per worktree. The remote brain keeps holding artifacts, transcripts, and cross-machine queries. The two engines stay independent. Transcripts route to the artifacts repo on remote-MCP machines, the brain admin's pull job indexes them, and the local PGLite stays code-only with no transcript pollution. A new `gbrain_local_status` field on `gstack-gbrain-detect` distinguishes ok / no-cli / missing-config / broken-config / broken-db; `/sync-gbrain` and the sync orchestrator both gate on it so a dead Postgres URL gives a clear remediation message instead of two stages of ERR output. + +`/setup-gbrain` Step 1.5 (new) detects a broken local engine on re-run and offers four options: Retry the probe, Switch to PGLite (one-way, .bak rollback on failure), Switch brain mode (fall through to Step 2's path picker), or Quit. `/sync-gbrain` Step 1.5 (new) STOPs cleanly on broken-config / broken-db with a remediation message and SKIPs code+memory in `missing-config + remote-http` so the brain-sync push to the artifacts repo still runs. + +### The numbers that matter + +Source: `bun test test/gbrain-local-status.test.ts test/gbrain-detect-shape.test.ts test/gbrain-sync-skip.test.ts test/gbrain-init-rollback.test.ts test/gstack-upgrade-migration-v1_37_0_0.test.ts` — 5 new gate-tier test files, 27 cases, all green in ~5s. Periodic-tier E2E `test/skill-e2e-setup-gbrain-path4-local-pglite.test.ts` runs the full Path 4 + Step 4.5 Yes flow against a stub MCP and passes in 280s. + +| Surface | Before | After | +|---|---|---| +| Path 4 + `/sync-gbrain --full` output (Garry's broken-db state) | `ERR code source registration failed: gbrain not configured (run /setup-gbrain)` + `ERR memory gbrain import exited 1: Cannot connect to database` | `SKIP code skipped — local engine broken-db — config points at unreachable DB; see /setup-gbrain Step 1.5` + brain-sync runs normally | +| `bin/gstack-gbrain-detect` runtime | bash + jq, single-purpose probe | TypeScript shebang script sharing the `localEngineStatus()` classifier with the orchestrator. 10 JSON fields, 9 existing keys byte-compat; one new `gbrain_local_status` enum. Memoized resolvers cut ~400ms of duplicate fork-exec per skill preamble. | +| Status probe cost | `gbrain doctor --json` without `--fast` could hang up to 5s on dead DB | `gbrain doctor --json --fast` (3s ceiling) + DB-reachability via `gbrain sources list --json` stderr classification (~80ms steady), 60s TTL cache keyed on `{HOME, PATH, gbrain bin, gbrain version, config mtime}` | +| Path 4 user discovers code search | Hidden — only `/sync-gbrain` errors hint at it | `/gstack-upgrade` migration v1.37.0.0 prints a one-time notice when `gbrain_mcp_mode == remote-http` AND `gbrain_local_status == missing-config`. `gstack-config set local_code_index_offered true` to silence. | +| Transcripts indexed in remote brain | Local-only `gbrain import` writes to the LOCAL engine, polluting PGLite if user opts into Step 4.5 | `gstack-memory-ingest` detects remote-http MCP, persists staged markdown to `~/.gstack/transcripts/run--/` instead of tmpdir, skips local `gbrain import`. `bin/gstack-brain-sync` allowlist now covers `transcripts/run-*/*.md`; brain admin pulls and indexes. | + +### Itemized changes + +#### Added + +- `lib/gbrain-local-status.ts` — shared 5-state engine status classifier (`ok` / `no-cli` / `missing-config` / `broken-config` / `broken-db`) with 60s TTL cache and `--no-cache` flag. Probes via `gbrain sources list --json` + stderr classification reusing the exact patterns from `lib/gbrain-sources.ts:66-67`. +- `/setup-gbrain` Step 1.5 — broken-db remediation with 4 options (Retry / Switch to PGLite / Switch brain mode / Quit). PGLite switch is rollback-safe: `mv ~/.gbrain/config.json` to a timestamped `.bak`, `gbrain init --pglite`, on non-zero exit restore the .bak verbatim. +- `/setup-gbrain` Step 4.5 — Path 4 opt-in for local PGLite code search. Yes path runs `gstack-gbrain-install` (idempotent) + `gbrain init --pglite --json` with the same rollback semantics. No path keeps Path 4 as remote-MCP-only. +- `/sync-gbrain` Step 1.5 — pre-flight local engine status check. STOPs on broken-config / broken-db with remediation, SKIPs code+memory in `missing-config + remote-http` so brain-sync still runs. +- `gstack-upgrade/migrations/v1.37.0.0.sh` — one-time discoverability notice for existing Path 4 users whose machine has no local engine yet. +- `bin/gstack-brain-sync` allowlist — `transcripts/run-*/*.md` so remote-MCP transcripts persisted to `~/.gstack/transcripts/` reach the artifacts repo. +- New test files (gate-tier, all mocked, no real gbrain): `gbrain-local-status.test.ts` (11 cases), `gbrain-detect-shape.test.ts` (8 cases), `gbrain-sync-skip.test.ts` (5 cases), `gbrain-init-rollback.test.ts` (3 cases), `gstack-upgrade-migration-v1_37_0_0.test.ts` (5 cases). +- Periodic-tier E2E `skill-e2e-setup-gbrain-path4-local-pglite.test.ts` for the full Path 4 + Step 4.5 Yes flow. + +#### Changed + +- `bin/gstack-gbrain-detect` — rewritten bash → TypeScript shebang script. Filename unchanged so existing skill preamble callers shell out without edits. 9 existing JSON fields preserve name + type + semantics; new `gbrain_local_status` field added. Documented dependency: requires `bun` on PATH (the gstack installer already provides this). +- `bin/gstack-gbrain-sync.ts` — `runCodeImport()` + `runMemoryIngest()` return `{ran: false, summary: "skipped — local engine ; remote MCP unaffected"}` when `localEngineStatus() != 'ok'`. Brain-sync stage continues regardless. +- `bin/gstack-memory-ingest.ts` — when `gbrain_mcp_mode === 'remote-http'`, persists staged transcripts to `~/.gstack/transcripts/run--/` and skips local `gbrain import` entirely. +- `bin/gstack-artifacts-init` — extends the managed `.brain-allowlist` to include `transcripts/run-*/*.md` and `transcripts/run-*/**/*.md` (privacy class: behavioral). +- `sync-gbrain/SKILL.md.tmpl` Step 1 — corrects misleading prose about memory stage "routing through MCP." Memory stage always shells out to local `gbrain import`; in remote-http mode it persists markdown instead. + +#### Fixed + +- Pre-existing flake in `test/gstack-next-version.test.ts` — bumped per-test timeout from default 5s to 15s. Spawned `gstack-next-version` CLI takes 4-5s wall time on M-series Macs under suite load and tipped over 5001ms intermittently. + +#### For contributors + +- New shared classifier pattern: `lib/gbrain-local-status.ts` exports `localEngineStatus()`, `resolveGbrainBin()`, `readGbrainVersion()`. The latter two are memoized per-process keyed on PATH so detect + classifier share fork-exec results. +- 13 architectural decisions captured in plan file `~/.claude/plans/the-real-product-fix-squishy-galaxy.md` — including Codex outside-voice findings (4 became structural decisions: keep proactive setup question, route transcripts via artifacts repo, SKIP+brain-sync on broken engine, retry-first repair menu). + ## [1.34.1.0] - 2026-05-13 ## **`gstack-update-check` resolves remote VERSION via a SHA-pinned URL.** diff --git a/VERSION b/VERSION index 1e7be731a..f24ab8298 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.34.1.0 +1.37.0.0 diff --git a/gstack-upgrade/migrations/v1.35.0.0.sh b/gstack-upgrade/migrations/v1.37.0.0.sh similarity index 91% rename from gstack-upgrade/migrations/v1.35.0.0.sh rename to gstack-upgrade/migrations/v1.37.0.0.sh index 10ee84886..b173f5844 100755 --- a/gstack-upgrade/migrations/v1.35.0.0.sh +++ b/gstack-upgrade/migrations/v1.37.0.0.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Migration: v1.35.0.0 — split-engine gbrain (remote MCP brain + optional +# Migration: v1.37.0.0 — split-engine gbrain (remote MCP brain + optional # local PGLite for code search per worktree). # # Per plan D5: prints a ONE-TIME discoverability notice for existing @@ -17,20 +17,20 @@ # When silent: anything else (Path 1/2/3 users, anyone already on PGLite, # anyone who opted out, anyone without remote-http MCP). # -# Idempotency: writes a touchfile at ~/.gstack/.migrations/v1.35.0.0.done +# Idempotency: writes a touchfile at ~/.gstack/.migrations/v1.37.0.0.done # on completion. Re-running this script is silent if the touchfile exists, # OR if local_code_index_offered=true. set -euo pipefail if [ -z "${HOME:-}" ]; then - echo " [v1.35.0.0] HOME is unset — skipping migration." >&2 + echo " [v1.37.0.0] HOME is unset — skipping migration." >&2 exit 0 fi GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" MIGRATIONS_DIR="$GSTACK_HOME/.migrations" -DONE_TOUCH="$MIGRATIONS_DIR/v1.35.0.0.done" +DONE_TOUCH="$MIGRATIONS_DIR/v1.37.0.0.done" CONFIG_BIN="$HOME/.claude/skills/gstack/bin/gstack-config" CLAUDE_JSON="$HOME/.claude.json" GBRAIN_CONFIG="$HOME/.gbrain/config.json" @@ -73,7 +73,7 @@ if is_remote_http_mcp && is_local_engine_missing; then cat <<'NOTICE' ┌──────────────────────────────────────────────────────────────────┐ - │ gstack v1.35.0.0 — split-engine gbrain │ + │ gstack v1.37.0.0 — split-engine gbrain │ │ │ │ Symbol-aware code search is now available on this machine. │ │ Your remote brain at gbrain MCP keeps working as today; you can │ diff --git a/package.json b/package.json index 23ad0bad7..571d13851 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.34.1.0", + "version": "1.37.0.0", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/test/gstack-upgrade-migration-v1_35_0_0.test.ts b/test/gstack-upgrade-migration-v1_37_0_0.test.ts similarity index 96% rename from test/gstack-upgrade-migration-v1_35_0_0.test.ts rename to test/gstack-upgrade-migration-v1_37_0_0.test.ts index 134e9994d..8e4993f5f 100644 --- a/test/gstack-upgrade-migration-v1_35_0_0.test.ts +++ b/test/gstack-upgrade-migration-v1_37_0_0.test.ts @@ -1,5 +1,5 @@ /** - * Unit tests for gstack-upgrade/migrations/v1.35.0.0.sh — split-engine notice. + * Unit tests for gstack-upgrade/migrations/v1.37.0.0.sh — split-engine notice. * * Per plan D5: print a one-time discoverability notice for existing Path 4 * (remote-http MCP) users who don't yet have a local engine, so they @@ -31,7 +31,7 @@ const MIGRATION = join( "..", "gstack-upgrade", "migrations", - "v1.35.0.0.sh", + "v1.37.0.0.sh", ); interface MigEnv { @@ -100,7 +100,7 @@ exit 0 tmp, home, gstackHome, - doneTouch: join(gstackHome, ".migrations", "v1.35.0.0.done"), + doneTouch: join(gstackHome, ".migrations", "v1.37.0.0.done"), claudeJson, gbrainConfig, configBin, @@ -127,7 +127,7 @@ function runMigration(env: MigEnv): { stdout: string; stderr: string; exitCode: }; } -describe("gstack-upgrade/migrations/v1.35.0.0.sh", () => { +describe("gstack-upgrade/migrations/v1.37.0.0.sh", () => { it("STATE MATCH: remote-http MCP + no local config → notice printed, touchfile written", () => { const env = makeEnv({ remoteHttpMcp: true, hasLocalConfig: false }); try {