mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 14:38:59 +02:00
feat(setup-gbrain): carve the branch-exclusive install paths into sections
Brain-init (Paths 1/2/3/4 bodies), engine remediation, transcript gate, and CLAUDE.md persist load on demand — at most one install route ever runs. Skeleton 75.3KB -> 57.0KB; the Step 1 detect and Step 2 path dispatch stay always-loaded. New buildSetupGbrainFixture helper gives the periodic E2Es extract-don't-copy fixtures with a non-empty guard; the voyage-code-3 gate counts scan the tmpl union (the third init site lives in engine-remediation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
6bb1996004
commit
cdfe2d9074
+35
-476
@@ -420,6 +420,20 @@ implemented as a dispatcher binary.
|
||||
|
||||
---
|
||||
|
||||
## Section index — Read each section when its situation applies
|
||||
|
||||
This skill is a decision-tree skeleton. The steps below point to on-demand
|
||||
sections. Read a section in full before doing its step; do not work from memory.
|
||||
|
||||
| When | Read this section |
|
||||
|------|-------------------|
|
||||
| running the Step 1.5 broken-engine remediation — Step 1's detect returned `gbrain_local_status` of `broken-db` or `broken-config` and no shortcut flag was passed | `sections/engine-remediation.md` |
|
||||
| initializing the brain in Step 4 — run ONLY the procedure for the path picked in Step 2 (Paths 1/2a/2b/3/4 or Switch; also holds the PAT scope disclosure that `--cleanup-orphans` re-uses) | `sections/brain-init.md` |
|
||||
| running the Step 7.5 transcript & memory ingest gate on Paths 1, 2a, 2b, or 3 (Path 4 skips this section entirely — see the skeleton's skip note) | `sections/transcript-gate.md` |
|
||||
| persisting the Step 8 `## GBrain Configuration` block to CLAUDE.md (and the Search Guidance block after Step 9 passes) | `sections/claude-md-persist.md` |
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Detect current state
|
||||
|
||||
```bash
|
||||
@@ -453,77 +467,15 @@ invocation flags here and skip to the matching step.
|
||||
|
||||
Read `gbrain_local_status` from the Step 1 detect output. **If it's `broken-db`
|
||||
or `broken-config` AND no shortcut flag was passed**, the user has a
|
||||
non-working local engine (Garry's repro: `~/.gbrain/config.json` points at a
|
||||
dead Postgres URL). Fire a targeted AskUserQuestion BEFORE Step 2:
|
||||
|
||||
> D# — Your local gbrain engine isn't responding. How do you want to fix it?
|
||||
> Project/branch/task: <one-sentence grounding using detected slug + branch>
|
||||
> ELI10: gbrain has a config at `~/.gbrain/config.json` but the engine it points
|
||||
> at isn't reachable. That could be a transient outage (Postgres container
|
||||
> stopped, Tailscale down) OR a stale config you want to abandon. Different
|
||||
> remediation for each case.
|
||||
> Stakes if we pick wrong: "Switch to PGLite" overwrites your existing config
|
||||
> (one-way door if the user actually wanted the broken engine). "Retry" preserves
|
||||
> existing state for transient cases.
|
||||
> Recommendation: A (Retry) — always try the cheap option first; if engine is
|
||||
> just temporarily down it'll come back without any destructive change.
|
||||
> Note: options differ in kind, not coverage — no completeness score.
|
||||
> A) Retry — re-probe the engine (recommended; ~80ms)
|
||||
> ✅ Cheapest test: re-runs `gbrain sources list` to see if engine is back
|
||||
> ✅ Zero side effects; existing config preserved
|
||||
> ❌ If engine is permanently dead, retries forever; user must choose another option
|
||||
> B) Switch to local PGLite (one-way — moves existing config to .bak)
|
||||
> ✅ Fastest path to a working local engine if user has abandoned the old one
|
||||
> ✅ ~30s; no accounts; private to this machine
|
||||
> ❌ Destructive — existing config moved to ~/.gbrain/config.json.gstack-bak-{ts}
|
||||
> C) Switch brain mode (continue to Step 2 path picker)
|
||||
> ✅ Lets user pick Path 1/2/3/4 to re-init from scratch
|
||||
> ✅ Preserves existing config until they explicitly init the new one
|
||||
> ❌ Longer flow if user just wants to repair to PGLite
|
||||
> D) Quit (do nothing)
|
||||
> ✅ No cons — this is a hard-stop choice
|
||||
> ❌ N/A
|
||||
> Net: A is the right starting move; B/C are explicit destructive paths; D bails.
|
||||
|
||||
**If A (Retry)**: re-run `~/.claude/skills/gstack/bin/gstack-gbrain-detect`
|
||||
with `GSTACK_DETECT_NO_CACHE=1` (busts the 60s cache). If the new
|
||||
`gbrain_local_status` is `ok`, continue to Step 2. If still `broken-db` or
|
||||
`broken-config`, fire the same AskUserQuestion again (the user picks again).
|
||||
|
||||
**If B (Switch to PGLite)** — execute the rollback-safe init sequence (plan D7):
|
||||
|
||||
```bash
|
||||
BACKUP="$HOME/.gbrain/config.json.gstack-bak-$(date +%s)"
|
||||
mv "$HOME/.gbrain/config.json" "$BACKUP"
|
||||
# gstack default: voyage-code-3 (1024d) when VOYAGE_API_KEY is set — best for
|
||||
# code retrieval. Without the key, fall back to gbrain's own auto-selected
|
||||
# embedding provider chain (OpenAI 1536d when OPENAI_API_KEY is present, etc.).
|
||||
# Never select gbrain's legacy zeroentropyai recipe for a new brain: the hosted
|
||||
# API sunsets September 4, 2026 (#2365); the wireup helper warns existing installs.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
if ! gbrain init --pglite --json "$@"; then
|
||||
# Restore on failure
|
||||
mv "$BACKUP" "$HOME/.gbrain/config.json"
|
||||
echo "gbrain init failed. Your previous config was restored at $HOME/.gbrain/config.json." >&2
|
||||
echo "PGLite directory at ~/.gbrain/pglite/ may be in a partial state — \`rm -rf ~/.gbrain/pglite\` if needed before retrying." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Switched to local PGLite. Previous config saved at $BACKUP — review before deleting."
|
||||
```
|
||||
|
||||
Then jump to Step 5a (MCP registration; the new PGLite engine is registered as
|
||||
local-stdio).
|
||||
|
||||
**If C (Switch brain mode)**: continue to Step 2's normal path picker.
|
||||
|
||||
**If D (Quit)**: STOP the skill cleanly.
|
||||
non-working local engine — run the remediation below BEFORE Step 2.
|
||||
|
||||
For `gbrain_local_status` values of `no-cli` or `missing-config`, do NOT fire
|
||||
Step 1.5 — fall through to Step 2 (where `no-cli` triggers Step 3 install and
|
||||
`missing-config` triggers Step 4 init).
|
||||
`missing-config` triggers Step 4 init). Do not read the remediation section in
|
||||
that case.
|
||||
|
||||
> **STOP.** Before running the Step 1.5 broken-engine remediation — Step 1's detect returned `gbrain_local_status` of `broken-db` or `broken-config` and no shortcut flag was passed, Read `~/.claude/skills/gstack/setup-gbrain/sections/engine-remediation.md` and execute it
|
||||
> in full. Do not work from memory — that section is the source of truth for this step.
|
||||
|
||||
---
|
||||
|
||||
@@ -632,273 +584,12 @@ continue the skill — the environment is broken until the user fixes PATH.
|
||||
|
||||
## Step 4: Initialize the brain
|
||||
|
||||
Path-specific.
|
||||
Path-specific. The init procedure for the path picked in Step 2 — Paths 1, 2a,
|
||||
2b, 3, 4 (4a-4e), and the Switch migration flow — lives in the brain-init
|
||||
section. Run ONLY the sub-section for the picked path.
|
||||
|
||||
### Path 1 (Supabase, existing URL)
|
||||
|
||||
Source the secret-read helper, collect URL with `read -s` + redacted preview:
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env GBRAIN_POOLER_URL "Paste Session Pooler URL: " \
|
||||
--echo-redacted 's#://[^@]*@#://***@#'
|
||||
```
|
||||
|
||||
Then validate structurally:
|
||||
|
||||
```bash
|
||||
printf '%s' "$GBRAIN_POOLER_URL" | ~/.claude/skills/gstack/bin/gstack-gbrain-supabase-verify -
|
||||
```
|
||||
|
||||
If the verify exit code is 3 (direct-connection URL), the verifier's own
|
||||
message explains the fix; surface it and re-prompt for a Session Pooler URL.
|
||||
|
||||
On success, hand off to gbrain via env var (D10, never argv):
|
||||
|
||||
```bash
|
||||
GBRAIN_DATABASE_URL="$GBRAIN_POOLER_URL" gbrain init --non-interactive --json
|
||||
```
|
||||
|
||||
Then `unset GBRAIN_POOLER_URL GBRAIN_DATABASE_URL` immediately. The URL is
|
||||
now persisted in `~/.gbrain/config.json` at mode 0600 by gbrain itself.
|
||||
|
||||
### Path 2a (Supabase, auto-provision — D7)
|
||||
|
||||
Show the D11 PAT scope disclosure verbatim BEFORE collecting the token:
|
||||
|
||||
> *This Supabase Personal Access Token grants full read/write/delete access
|
||||
> to every project in your Supabase account, not just the `gbrain` one we're
|
||||
> about to create. Supabase doesn't currently support scoped tokens. We use
|
||||
> this PAT only to: create one project, poll it until healthy, read the
|
||||
> Session Pooler URL — then discard it from process memory. The token
|
||||
> remains valid on Supabase's side until you manually revoke it at
|
||||
> https://supabase.com/dashboard/account/tokens — we recommend revoking
|
||||
> immediately after setup completes.*
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env SUPABASE_ACCESS_TOKEN "Paste PAT: "
|
||||
```
|
||||
|
||||
Ask the D17 tier prompt via AskUserQuestion: "Which Supabase tier?" Present
|
||||
Free (2-project limit, pauses after 7d inactivity) vs Pro ($25/mo, no
|
||||
pauses, recommended for real use). Explain that tier is **org-level** (per
|
||||
the Management API contract) — user picks their org based on its current
|
||||
tier. Pro may require them to upgrade the org first at supabase.com.
|
||||
|
||||
List orgs, pick one (AskUserQuestion if multiple):
|
||||
|
||||
```bash
|
||||
orgs=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision list-orgs --json)
|
||||
```
|
||||
|
||||
If the `.orgs` array is empty, surface: "Your Supabase account has no
|
||||
organizations. Create one at https://supabase.com/dashboard, then re-run
|
||||
`/setup-gbrain`." STOP.
|
||||
|
||||
Ask the user for a region (default `us-east-1`; valid values are the 18
|
||||
enum values in the Supabase Management API — list a few common ones, let
|
||||
them pick "Other" for a full list).
|
||||
|
||||
Generate the DB password (never shown to the user):
|
||||
|
||||
```bash
|
||||
export DB_PASS=$(openssl rand -base64 24)
|
||||
```
|
||||
|
||||
Set up a SIGINT trap (D12 basic recovery):
|
||||
|
||||
```bash
|
||||
trap 'echo ""; echo "gstack-gbrain: interrupted. In-flight ref: $INFLIGHT_REF"; \
|
||||
echo "Resume: /setup-gbrain --resume-provision $INFLIGHT_REF"; \
|
||||
echo "Delete: https://supabase.com/dashboard/project/$INFLIGHT_REF"; \
|
||||
unset SUPABASE_ACCESS_TOKEN DB_PASS; exit 130' INT TERM
|
||||
```
|
||||
|
||||
Create + wait + fetch:
|
||||
|
||||
```bash
|
||||
result=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision \
|
||||
create gbrain "$REGION" "$ORG_SLUG" --json)
|
||||
INFLIGHT_REF=$(echo "$result" | jq -r .ref)
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision wait "$INFLIGHT_REF" --json
|
||||
pooler=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision \
|
||||
pooler-url "$INFLIGHT_REF" --json)
|
||||
GBRAIN_DATABASE_URL=$(echo "$pooler" | jq -r .pooler_url)
|
||||
export GBRAIN_DATABASE_URL
|
||||
gbrain init --non-interactive --json
|
||||
unset SUPABASE_ACCESS_TOKEN DB_PASS GBRAIN_DATABASE_URL INFLIGHT_REF
|
||||
trap - INT TERM
|
||||
```
|
||||
|
||||
After success, emit the PAT revocation reminder:
|
||||
|
||||
> "Setup complete. Revoke the PAT you pasted at
|
||||
> https://supabase.com/dashboard/account/tokens — we've already discarded
|
||||
> it from memory and don't need it again. The gbrain project will continue
|
||||
> working because it uses its own embedded database password."
|
||||
|
||||
### Path 2b (Supabase, manual)
|
||||
|
||||
Walk the user through the supabase.com steps:
|
||||
1. Login at https://supabase.com/dashboard
|
||||
2. Click "New Project," name it `gbrain`, pick a region, copy the generated
|
||||
database password (you'll need it for paste-back? no — it's embedded in
|
||||
the pooler URL we collect next)
|
||||
3. Wait ~2 min for the project to initialize
|
||||
4. Settings → Database → Connection Pooler → Session → copy the URL (port
|
||||
6543)
|
||||
|
||||
Then follow the same secret-read + verify + init flow as Path 1.
|
||||
|
||||
### Path 3 (PGLite local)
|
||||
|
||||
```bash
|
||||
# gstack default: voyage-code-3 (1024d) when VOYAGE_API_KEY is set — code
|
||||
# retrieval beats general-purpose embeddings on real code queries (validated
|
||||
# A/B). Without the key, gbrain auto-selects (OpenAI 1536d when available).
|
||||
# Never select gbrain's legacy zeroentropyai recipe for a new brain: the hosted
|
||||
# API sunsets September 4, 2026 (#2365); the wireup helper warns existing installs.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
gbrain init --pglite --json "$@"
|
||||
```
|
||||
|
||||
Done. No network, no secrets (beyond Voyage embedding API calls during sync, if
|
||||
`VOYAGE_API_KEY` is set — ~$0.18 per 1M tokens, pennies per repo).
|
||||
|
||||
### Path 4 (Remote gbrain MCP — HTTP transport with bearer token)
|
||||
|
||||
For users whose brain runs on another machine (Tailscale, ngrok, internal
|
||||
LAN, or a teammate's server). No local gbrain CLI install, no local DB.
|
||||
This skill registers the remote MCP and stops; ingestion + indexing happens
|
||||
on the brain host.
|
||||
|
||||
**4a. Collect MCP URL.** Prompt the user:
|
||||
|
||||
```
|
||||
Paste your gbrain MCP URL (e.g. https://wintermute.tail554574.ts.net:3131/mcp):
|
||||
```
|
||||
|
||||
Read with plain `read -r` (no secret hygiene needed — the URL alone isn't
|
||||
a credential). Validate it starts with `https://` (require TLS for any
|
||||
non-loopback host); refuse `http://` for non-localhost.
|
||||
|
||||
**4b. Collect bearer token via the secret-read helper (D10, never argv).**
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env GBRAIN_MCP_TOKEN "Paste bearer token: " \
|
||||
--echo-redacted 's/.\{6\}$/***REDACTED***/'
|
||||
```
|
||||
|
||||
**4c. Verify via gstack-gbrain-mcp-verify.** Run the helper; capture the
|
||||
classified JSON output:
|
||||
|
||||
```bash
|
||||
verify_json=$(GBRAIN_MCP_TOKEN="$GBRAIN_MCP_TOKEN" \
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-mcp-verify "$MCP_URL")
|
||||
status=$(echo "$verify_json" | jq -r .status)
|
||||
```
|
||||
|
||||
If `status != "success"`, the helper has already classified the failure
|
||||
into NETWORK / AUTH / MALFORMED and emitted a one-line remediation hint.
|
||||
Surface the hint above the raw error from `error_text` and **STOP** with
|
||||
a clear "fix and re-run /setup-gbrain" message. Do NOT continue to Step 5a
|
||||
on a failed verify — partial registration would leave the user with a
|
||||
half-broken state.
|
||||
|
||||
Capture two values from the verify output for downstream steps:
|
||||
- `SERVER_VERSION` (e.g., `0.27.1`) — written to the CLAUDE.md block in Step 8.
|
||||
- `URL_FORM_SUPPORTED` (`true|false`) — passed to `gstack-artifacts-init` in
|
||||
Step 7 to control which form of the brain-admin hookup command is printed.
|
||||
|
||||
**4d. (Path 4) Offer local PGLite for code search.** Per plan D10/D11, ask:
|
||||
|
||||
> D# — Want symbol-aware code search on this machine?
|
||||
> Project/branch/task: <one-sentence grounding using detected slug + branch>
|
||||
> ELI10: The remote brain at `<MCP_URL>` is great for cross-machine knowledge,
|
||||
> but symbol queries like `gbrain code-def` / `code-refs` / `code-callers` need
|
||||
> a local index of THIS machine's code. We can spin up a tiny isolated PGLite
|
||||
> database (~30 seconds, no accounts, ~120 MB disk) just for code, separate
|
||||
> from your remote brain. Transcripts and artifacts continue routing through
|
||||
> the artifacts repo to the remote brain — local PGLite stays code-only.
|
||||
> Stakes: without it, semantic code search in this repo's worktrees falls
|
||||
> back to Grep.
|
||||
> Recommendation: A — 30 seconds, no ongoing cost, unlocks the symbol tools.
|
||||
> Completeness: A=10/10 (full split-engine), B=7/10 (remote-only).
|
||||
> A) Yes, set up local PGLite for code (recommended)
|
||||
> ✅ Unlocks `gbrain code-def`, `code-refs`, `code-callers` per worktree
|
||||
> ✅ Independent engine — won't disturb remote brain or share transcripts
|
||||
> B) No, remote MCP only
|
||||
> ✅ Zero local state — only `~/.claude.json` MCP registration
|
||||
> ❌ Symbol code queries fall back to Grep in this repo's worktrees
|
||||
> Net: A = full split-engine; B = remote-only.
|
||||
|
||||
**If A (Yes)**: install + init local PGLite with rollback-safe semantics (D7):
|
||||
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-install || exit $?
|
||||
# At this point the local gbrain CLI is on PATH. Init PGLite, but back up any
|
||||
# existing ~/.gbrain/config.json first (rollback if init fails).
|
||||
if [ -f "$HOME/.gbrain/config.json" ]; then
|
||||
BACKUP="$HOME/.gbrain/config.json.gstack-bak-$(date +%s)"
|
||||
mv "$HOME/.gbrain/config.json" "$BACKUP"
|
||||
fi
|
||||
# gstack default for local code-search PGLite: voyage-code-3 (1024d) when
|
||||
# VOYAGE_API_KEY is set. It wins the A/B over voyage-4-large and OpenAI
|
||||
# text-embedding-3-large on this codebase's symbol queries. Falls back to
|
||||
# gbrain's auto-selected provider when the key isn't present.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
if ! gbrain init --pglite --json "$@"; then
|
||||
if [ -n "${BACKUP:-}" ] && [ -f "$BACKUP" ]; then mv "$BACKUP" "$HOME/.gbrain/config.json"; fi
|
||||
echo "gbrain init failed. Existing config (if any) was restored. PGLite at ~/.gbrain/pglite/ may be in a partial state — \`rm -rf ~/.gbrain/pglite\` to reset." >&2
|
||||
echo "Continuing setup without local code search; you can re-run /setup-gbrain to retry." >&2
|
||||
fi
|
||||
```
|
||||
|
||||
Then continue to Step 5a. The remote-http MCP registration in 5a runs as
|
||||
today; the local PGLite is independent of MCP registration (Claude Code talks
|
||||
to the remote brain via MCP for queries; `gbrain` CLI talks to local PGLite
|
||||
for code-def/refs/callers).
|
||||
|
||||
**If B (No)**: skip the install + init. The local engine stays absent.
|
||||
`gbrain_local_status` will be `missing-config` (or `no-cli` if gbrain isn't
|
||||
installed). `/sync-gbrain` will SKIP the code stage cleanly per plan D12.
|
||||
|
||||
**4e. Skip Steps 3, 4 (other paths) and 5 (local doctor) when B was picked.**
|
||||
When A was picked, Step 3 already ran (via gstack-gbrain-install) and Step 4
|
||||
already ran (via `gbrain init --pglite`); jump straight to Step 5a. When B
|
||||
was picked, Steps 3/4/5 are no-ops; also skip Step 7.5 (transcript ingest)
|
||||
since memory-stage routes through the artifacts pipeline in remote-http mode
|
||||
per plan D11.
|
||||
|
||||
The bearer token (`GBRAIN_MCP_TOKEN`) stays in process env until Step 5a's
|
||||
`claude mcp add --header` consumes it; then `unset GBRAIN_MCP_TOKEN`
|
||||
immediately. Token security trade-off documented in
|
||||
`setup-gbrain/memory.md`: brief argv exposure during `claude mcp add`,
|
||||
resting state in `~/.claude.json` mode 0600.
|
||||
|
||||
### Switch (from detect's existing-engine state)
|
||||
|
||||
```bash
|
||||
# Going PGLite → Supabase, collect URL first (Path 1 flow), then:
|
||||
timeout 180s gbrain migrate --to supabase --url "$URL" --json
|
||||
# Going Supabase → PGLite:
|
||||
timeout 180s gbrain migrate --to pglite --json
|
||||
```
|
||||
|
||||
If `timeout` returns 124 (exit code for timeout): surface D9 message
|
||||
("Migration didn't complete in 3 minutes — another gstack session may be
|
||||
holding a lock on the source brain. Close other workspaces and re-run
|
||||
`/setup-gbrain --switch`. Your original brain is untouched."). STOP.
|
||||
> **STOP.** Before initializing the brain in Step 4 — run ONLY the procedure for the path picked in Step 2 (Paths 1/2a/2b/3/4 or Switch; also holds the PAT scope disclosure that `--cleanup-orphans` re-uses), Read `~/.claude/skills/gstack/setup-gbrain/sections/brain-init.md` and execute it
|
||||
> in full. Do not work from memory — that section is the source of truth for this step.
|
||||
|
||||
---
|
||||
|
||||
@@ -1095,154 +786,21 @@ this machine's transcripts indexed, they pull from your `gstack-artifacts-$USER`
|
||||
repo (set up in Step 7) on whatever schedule they prefer. Set
|
||||
`gstack-config set transcript_ingest_mode off` and continue to Step 8.
|
||||
|
||||
For Paths 1, 2a, 2b, 3:
|
||||
For Paths 1, 2a, 2b, 3, run the ingest gate:
|
||||
|
||||
After memory sync is wired (Step 7) but before persisting the CLAUDE.md
|
||||
config (Step 8), offer to bring this Mac's coding-agent transcripts +
|
||||
curated `~/.gstack/` artifacts into gbrain so the retrieval surface
|
||||
(per-skill manifests, salience block) has data to surface.
|
||||
|
||||
Run the probe to size the operation:
|
||||
```bash
|
||||
bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe
|
||||
```
|
||||
|
||||
Read the output. If `Total files in window: 0`, skip — there's nothing
|
||||
to ingest. Set `gstack-config set transcript_ingest_mode incremental`
|
||||
silently and continue to Step 8.
|
||||
|
||||
If `New (never ingested)` is < 200 AND total bytes are < 100MB: silent
|
||||
bulk via `bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet`. Set
|
||||
`transcript_ingest_mode=incremental` and continue.
|
||||
|
||||
Otherwise (the "many transcripts on disk" path): AskUserQuestion with
|
||||
the exact counts AND the value promise. Default scope is **current repo
|
||||
only, last 90 days**:
|
||||
|
||||
> "Found <N_repo> transcripts in THIS repo (<repo-slug>) over the last
|
||||
> 90 days, plus <N_other> across other repos on this machine (<bytes>
|
||||
> total if all ingested). Ingest THIS repo's transcripts into gbrain?
|
||||
>
|
||||
> What you get after this: every gstack skill auto-loads recent salience
|
||||
> from your past sessions in this repo, so the agent finds your prior
|
||||
> work without you describing it. You can query 'what was I doing on
|
||||
> day X' and get a real answer. Per-session pages are searchable,
|
||||
> taggable, and deletable. Secret scanning runs before any push.
|
||||
>
|
||||
> What stays the same: nothing leaves your machine unless gbrain sync
|
||||
> is enabled (Step 7). Per-repo trust policies still apply.
|
||||
>
|
||||
> Multi-Mac note: if you HAVE enabled brain sync (Step 7), these
|
||||
> transcript pages will sync across your Macs. Caveat: deleting a
|
||||
> transcript page later removes it from gbrain but git history retains
|
||||
> it in prior commits. Use `gstack-transcript-prune` to delete in bulk;
|
||||
> use `git filter-repo` on the brain remote for hard-delete from
|
||||
> history."
|
||||
|
||||
Options:
|
||||
- A) Yes — this repo, last 90 days (recommended; ~est min)
|
||||
- B) Yes — this repo, ALL history
|
||||
- C) Yes — this repo + other repos on this machine
|
||||
- D) Skip historical, track new from now (`transcript_ingest_mode=incremental`)
|
||||
- E) Never ingest transcripts (`transcript_ingest_mode=off`)
|
||||
|
||||
After answer:
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <choice>
|
||||
bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --full --no-brain-sync
|
||||
```
|
||||
(`--no-brain-sync` because Step 7 already wired that path; this just
|
||||
runs the code import + memory ingest stages. Brain-sync will run on the
|
||||
next preamble hook.)
|
||||
|
||||
If A/D/E, ingest is incremental from this point on; preamble-boundary
|
||||
hook runs `bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet` on every skill
|
||||
start (cheap mtime fast-path).
|
||||
|
||||
Reference doc for users: `setup-gbrain/memory.md` (linked from CLAUDE.md
|
||||
Step 8).
|
||||
> **STOP.** Before running the Step 7.5 transcript & memory ingest gate on Paths 1, 2a, 2b, or 3 (Path 4 skips this section entirely — see the skeleton's skip note), Read `~/.claude/skills/gstack/setup-gbrain/sections/transcript-gate.md` and execute it
|
||||
> in full. Do not work from memory — that section is the source of truth for this step.
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Persist `## GBrain Configuration` in CLAUDE.md
|
||||
|
||||
Find-and-replace (or append) the section. Block format depends on mode:
|
||||
CLAUDE.md is the audit trail: after a successful setup, persist the
|
||||
configuration block. The exact block formats (remote-http vs local-stdio) and
|
||||
the post-Step-9 Search Guidance write live in the claude-md-persist section.
|
||||
|
||||
### Path 4 (Remote MCP)
|
||||
|
||||
```markdown
|
||||
## GBrain Configuration (configured by /setup-gbrain)
|
||||
- Mode: remote-http
|
||||
- MCP URL: {MCP_URL}
|
||||
- Server version: gbrain v{SERVER_VERSION} (from Step 4c verify)
|
||||
- Setup date: {today}
|
||||
- MCP registered: yes (user scope)
|
||||
- Token: stored in ~/.claude.json (do not commit; never written to CLAUDE.md)
|
||||
- Artifacts repo: {gstack_artifacts_remote URL or "none"}
|
||||
- Artifacts sync: {off|artifacts-only|full}
|
||||
- Current repo policy: {read-write|read-only|deny|unset}
|
||||
```
|
||||
|
||||
The bearer token is **never** written to CLAUDE.md (CLAUDE.md is checked
|
||||
in to git in many projects). It lives only in `~/.claude.json` where
|
||||
`claude mcp add` placed it.
|
||||
|
||||
### Paths 1, 2a, 2b, 3 (Local stdio)
|
||||
|
||||
```markdown
|
||||
## GBrain Configuration (configured by /setup-gbrain)
|
||||
- Mode: local-stdio
|
||||
- Engine: {pglite|postgres}
|
||||
- Config file: ~/.gbrain/config.json (mode 0600)
|
||||
- Setup date: {today}
|
||||
- MCP registered: {yes/no}
|
||||
- Artifacts sync: {off|artifacts-only|full}
|
||||
- Current repo policy: {read-write|read-only|deny|unset}
|
||||
```
|
||||
|
||||
**After Step 9 (smoke test) passes, also write the `## GBrain Search Guidance`
|
||||
block** so the coding agent learns when to prefer `gbrain` over Grep. This
|
||||
block is gated on the smoke test passing — write the Configuration block
|
||||
first (so the user knows what state they're in even if the smoke test fails),
|
||||
then return here after Step 9 and write the guidance block only if smoke
|
||||
test succeeded.
|
||||
|
||||
When Step 9 passes, find-and-replace (or append) this block. Use HTML-comment
|
||||
delimiters so removal regex is unambiguous and never eats user content. The
|
||||
block content is machine-AGNOSTIC — no engine type, no page counts, no
|
||||
last-sync time. Machine state stays in the Configuration block above.
|
||||
|
||||
```markdown
|
||||
## GBrain Search Guidance (configured by /sync-gbrain)
|
||||
<!-- gstack-gbrain-search-guidance:start -->
|
||||
|
||||
GBrain is set up and synced on this machine. The agent should prefer gbrain
|
||||
over Grep when the question is semantic or when you don't know the exact
|
||||
identifier yet. Two indexed corpora available via the `gbrain` CLI:
|
||||
- This repo's code (registered as `gstack-code-<repo>` source).
|
||||
- `~/.gstack/` curated memory (registered as `gstack-brain-<user>` source via
|
||||
the existing federation pipeline).
|
||||
|
||||
Prefer gbrain when:
|
||||
- "Where is X handled?" / semantic intent, no exact string yet:
|
||||
`gbrain search "<terms>"` or `gbrain query "<question>"`
|
||||
- "Where is symbol Y defined?" / symbol-based code questions:
|
||||
`gbrain code-def <symbol>` or `gbrain code-refs <symbol>`
|
||||
- "What calls Y?" / "What does Y depend on?":
|
||||
`gbrain code-callers <symbol>` / `gbrain code-callees <symbol>`
|
||||
- "What did we decide last time?" / past plans, retros, learnings:
|
||||
`gbrain search "<terms>" --source gstack-brain-<user>`
|
||||
|
||||
Grep is still right for known exact strings, regex, multiline patterns, and
|
||||
file globs. The brain auto-syncs incrementally on every gstack skill start.
|
||||
Run `/sync-gbrain` to force-refresh, `/sync-gbrain --full` for full reindex.
|
||||
|
||||
<!-- gstack-gbrain-search-guidance:end -->
|
||||
```
|
||||
|
||||
If Step 9 smoke test fails, skip the guidance block write entirely. The user's
|
||||
next `/sync-gbrain` run will re-evaluate capability and write the block when
|
||||
the round-trip works.
|
||||
> **STOP.** Before persisting the Step 8 `## GBrain Configuration` block to CLAUDE.md (and the Search Guidance block after Step 9 passes), Read `~/.claude/skills/gstack/setup-gbrain/sections/claude-md-persist.md` and execute it
|
||||
> in full. Do not work from memory — that section is the source of truth for this step.
|
||||
|
||||
---
|
||||
|
||||
@@ -1433,7 +991,8 @@ as markdown + git, recoverable manually via `gbrain import` from a clone.
|
||||
|
||||
## `/setup-gbrain --cleanup-orphans` (D20)
|
||||
|
||||
Re-collect a PAT (Step 4 path-2a scope disclosure), then:
|
||||
Re-collect a PAT (show the Path 2a PAT scope disclosure — it lives in the
|
||||
brain-init section; read that section if it isn't already loaded), then:
|
||||
|
||||
```bash
|
||||
# List user's Supabase projects (user has to pipe this through their own
|
||||
|
||||
+21
-476
@@ -56,6 +56,10 @@ implemented as a dispatcher binary.
|
||||
|
||||
---
|
||||
|
||||
{{SECTION_INDEX:setup-gbrain}}
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Detect current state
|
||||
|
||||
```bash
|
||||
@@ -89,77 +93,14 @@ invocation flags here and skip to the matching step.
|
||||
|
||||
Read `gbrain_local_status` from the Step 1 detect output. **If it's `broken-db`
|
||||
or `broken-config` AND no shortcut flag was passed**, the user has a
|
||||
non-working local engine (Garry's repro: `~/.gbrain/config.json` points at a
|
||||
dead Postgres URL). Fire a targeted AskUserQuestion BEFORE Step 2:
|
||||
|
||||
> D# — Your local gbrain engine isn't responding. How do you want to fix it?
|
||||
> Project/branch/task: <one-sentence grounding using detected slug + branch>
|
||||
> ELI10: gbrain has a config at `~/.gbrain/config.json` but the engine it points
|
||||
> at isn't reachable. That could be a transient outage (Postgres container
|
||||
> stopped, Tailscale down) OR a stale config you want to abandon. Different
|
||||
> remediation for each case.
|
||||
> Stakes if we pick wrong: "Switch to PGLite" overwrites your existing config
|
||||
> (one-way door if the user actually wanted the broken engine). "Retry" preserves
|
||||
> existing state for transient cases.
|
||||
> Recommendation: A (Retry) — always try the cheap option first; if engine is
|
||||
> just temporarily down it'll come back without any destructive change.
|
||||
> Note: options differ in kind, not coverage — no completeness score.
|
||||
> A) Retry — re-probe the engine (recommended; ~80ms)
|
||||
> ✅ Cheapest test: re-runs `gbrain sources list` to see if engine is back
|
||||
> ✅ Zero side effects; existing config preserved
|
||||
> ❌ If engine is permanently dead, retries forever; user must choose another option
|
||||
> B) Switch to local PGLite (one-way — moves existing config to .bak)
|
||||
> ✅ Fastest path to a working local engine if user has abandoned the old one
|
||||
> ✅ ~30s; no accounts; private to this machine
|
||||
> ❌ Destructive — existing config moved to ~/.gbrain/config.json.gstack-bak-{ts}
|
||||
> C) Switch brain mode (continue to Step 2 path picker)
|
||||
> ✅ Lets user pick Path 1/2/3/4 to re-init from scratch
|
||||
> ✅ Preserves existing config until they explicitly init the new one
|
||||
> ❌ Longer flow if user just wants to repair to PGLite
|
||||
> D) Quit (do nothing)
|
||||
> ✅ No cons — this is a hard-stop choice
|
||||
> ❌ N/A
|
||||
> Net: A is the right starting move; B/C are explicit destructive paths; D bails.
|
||||
|
||||
**If A (Retry)**: re-run `~/.claude/skills/gstack/bin/gstack-gbrain-detect`
|
||||
with `GSTACK_DETECT_NO_CACHE=1` (busts the 60s cache). If the new
|
||||
`gbrain_local_status` is `ok`, continue to Step 2. If still `broken-db` or
|
||||
`broken-config`, fire the same AskUserQuestion again (the user picks again).
|
||||
|
||||
**If B (Switch to PGLite)** — execute the rollback-safe init sequence (plan D7):
|
||||
|
||||
```bash
|
||||
BACKUP="$HOME/.gbrain/config.json.gstack-bak-$(date +%s)"
|
||||
mv "$HOME/.gbrain/config.json" "$BACKUP"
|
||||
# gstack default: voyage-code-3 (1024d) when VOYAGE_API_KEY is set — best for
|
||||
# code retrieval. Without the key, fall back to gbrain's own auto-selected
|
||||
# embedding provider chain (OpenAI 1536d when OPENAI_API_KEY is present, etc.).
|
||||
# Never select gbrain's legacy zeroentropyai recipe for a new brain: the hosted
|
||||
# API sunsets September 4, 2026 (#2365); the wireup helper warns existing installs.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
if ! gbrain init --pglite --json "$@"; then
|
||||
# Restore on failure
|
||||
mv "$BACKUP" "$HOME/.gbrain/config.json"
|
||||
echo "gbrain init failed. Your previous config was restored at $HOME/.gbrain/config.json." >&2
|
||||
echo "PGLite directory at ~/.gbrain/pglite/ may be in a partial state — \`rm -rf ~/.gbrain/pglite\` if needed before retrying." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Switched to local PGLite. Previous config saved at $BACKUP — review before deleting."
|
||||
```
|
||||
|
||||
Then jump to Step 5a (MCP registration; the new PGLite engine is registered as
|
||||
local-stdio).
|
||||
|
||||
**If C (Switch brain mode)**: continue to Step 2's normal path picker.
|
||||
|
||||
**If D (Quit)**: STOP the skill cleanly.
|
||||
non-working local engine — run the remediation below BEFORE Step 2.
|
||||
|
||||
For `gbrain_local_status` values of `no-cli` or `missing-config`, do NOT fire
|
||||
Step 1.5 — fall through to Step 2 (where `no-cli` triggers Step 3 install and
|
||||
`missing-config` triggers Step 4 init).
|
||||
`missing-config` triggers Step 4 init). Do not read the remediation section in
|
||||
that case.
|
||||
|
||||
{{SECTION:engine-remediation}}
|
||||
|
||||
---
|
||||
|
||||
@@ -268,273 +209,11 @@ continue the skill — the environment is broken until the user fixes PATH.
|
||||
|
||||
## Step 4: Initialize the brain
|
||||
|
||||
Path-specific.
|
||||
Path-specific. The init procedure for the path picked in Step 2 — Paths 1, 2a,
|
||||
2b, 3, 4 (4a-4e), and the Switch migration flow — lives in the brain-init
|
||||
section. Run ONLY the sub-section for the picked path.
|
||||
|
||||
### Path 1 (Supabase, existing URL)
|
||||
|
||||
Source the secret-read helper, collect URL with `read -s` + redacted preview:
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env GBRAIN_POOLER_URL "Paste Session Pooler URL: " \
|
||||
--echo-redacted 's#://[^@]*@#://***@#'
|
||||
```
|
||||
|
||||
Then validate structurally:
|
||||
|
||||
```bash
|
||||
printf '%s' "$GBRAIN_POOLER_URL" | ~/.claude/skills/gstack/bin/gstack-gbrain-supabase-verify -
|
||||
```
|
||||
|
||||
If the verify exit code is 3 (direct-connection URL), the verifier's own
|
||||
message explains the fix; surface it and re-prompt for a Session Pooler URL.
|
||||
|
||||
On success, hand off to gbrain via env var (D10, never argv):
|
||||
|
||||
```bash
|
||||
GBRAIN_DATABASE_URL="$GBRAIN_POOLER_URL" gbrain init --non-interactive --json
|
||||
```
|
||||
|
||||
Then `unset GBRAIN_POOLER_URL GBRAIN_DATABASE_URL` immediately. The URL is
|
||||
now persisted in `~/.gbrain/config.json` at mode 0600 by gbrain itself.
|
||||
|
||||
### Path 2a (Supabase, auto-provision — D7)
|
||||
|
||||
Show the D11 PAT scope disclosure verbatim BEFORE collecting the token:
|
||||
|
||||
> *This Supabase Personal Access Token grants full read/write/delete access
|
||||
> to every project in your Supabase account, not just the `gbrain` one we're
|
||||
> about to create. Supabase doesn't currently support scoped tokens. We use
|
||||
> this PAT only to: create one project, poll it until healthy, read the
|
||||
> Session Pooler URL — then discard it from process memory. The token
|
||||
> remains valid on Supabase's side until you manually revoke it at
|
||||
> https://supabase.com/dashboard/account/tokens — we recommend revoking
|
||||
> immediately after setup completes.*
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env SUPABASE_ACCESS_TOKEN "Paste PAT: "
|
||||
```
|
||||
|
||||
Ask the D17 tier prompt via AskUserQuestion: "Which Supabase tier?" Present
|
||||
Free (2-project limit, pauses after 7d inactivity) vs Pro ($25/mo, no
|
||||
pauses, recommended for real use). Explain that tier is **org-level** (per
|
||||
the Management API contract) — user picks their org based on its current
|
||||
tier. Pro may require them to upgrade the org first at supabase.com.
|
||||
|
||||
List orgs, pick one (AskUserQuestion if multiple):
|
||||
|
||||
```bash
|
||||
orgs=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision list-orgs --json)
|
||||
```
|
||||
|
||||
If the `.orgs` array is empty, surface: "Your Supabase account has no
|
||||
organizations. Create one at https://supabase.com/dashboard, then re-run
|
||||
`/setup-gbrain`." STOP.
|
||||
|
||||
Ask the user for a region (default `us-east-1`; valid values are the 18
|
||||
enum values in the Supabase Management API — list a few common ones, let
|
||||
them pick "Other" for a full list).
|
||||
|
||||
Generate the DB password (never shown to the user):
|
||||
|
||||
```bash
|
||||
export DB_PASS=$(openssl rand -base64 24)
|
||||
```
|
||||
|
||||
Set up a SIGINT trap (D12 basic recovery):
|
||||
|
||||
```bash
|
||||
trap 'echo ""; echo "gstack-gbrain: interrupted. In-flight ref: $INFLIGHT_REF"; \
|
||||
echo "Resume: /setup-gbrain --resume-provision $INFLIGHT_REF"; \
|
||||
echo "Delete: https://supabase.com/dashboard/project/$INFLIGHT_REF"; \
|
||||
unset SUPABASE_ACCESS_TOKEN DB_PASS; exit 130' INT TERM
|
||||
```
|
||||
|
||||
Create + wait + fetch:
|
||||
|
||||
```bash
|
||||
result=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision \
|
||||
create gbrain "$REGION" "$ORG_SLUG" --json)
|
||||
INFLIGHT_REF=$(echo "$result" | jq -r .ref)
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision wait "$INFLIGHT_REF" --json
|
||||
pooler=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision \
|
||||
pooler-url "$INFLIGHT_REF" --json)
|
||||
GBRAIN_DATABASE_URL=$(echo "$pooler" | jq -r .pooler_url)
|
||||
export GBRAIN_DATABASE_URL
|
||||
gbrain init --non-interactive --json
|
||||
unset SUPABASE_ACCESS_TOKEN DB_PASS GBRAIN_DATABASE_URL INFLIGHT_REF
|
||||
trap - INT TERM
|
||||
```
|
||||
|
||||
After success, emit the PAT revocation reminder:
|
||||
|
||||
> "Setup complete. Revoke the PAT you pasted at
|
||||
> https://supabase.com/dashboard/account/tokens — we've already discarded
|
||||
> it from memory and don't need it again. The gbrain project will continue
|
||||
> working because it uses its own embedded database password."
|
||||
|
||||
### Path 2b (Supabase, manual)
|
||||
|
||||
Walk the user through the supabase.com steps:
|
||||
1. Login at https://supabase.com/dashboard
|
||||
2. Click "New Project," name it `gbrain`, pick a region, copy the generated
|
||||
database password (you'll need it for paste-back? no — it's embedded in
|
||||
the pooler URL we collect next)
|
||||
3. Wait ~2 min for the project to initialize
|
||||
4. Settings → Database → Connection Pooler → Session → copy the URL (port
|
||||
6543)
|
||||
|
||||
Then follow the same secret-read + verify + init flow as Path 1.
|
||||
|
||||
### Path 3 (PGLite local)
|
||||
|
||||
```bash
|
||||
# gstack default: voyage-code-3 (1024d) when VOYAGE_API_KEY is set — code
|
||||
# retrieval beats general-purpose embeddings on real code queries (validated
|
||||
# A/B). Without the key, gbrain auto-selects (OpenAI 1536d when available).
|
||||
# Never select gbrain's legacy zeroentropyai recipe for a new brain: the hosted
|
||||
# API sunsets September 4, 2026 (#2365); the wireup helper warns existing installs.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
gbrain init --pglite --json "$@"
|
||||
```
|
||||
|
||||
Done. No network, no secrets (beyond Voyage embedding API calls during sync, if
|
||||
`VOYAGE_API_KEY` is set — ~$0.18 per 1M tokens, pennies per repo).
|
||||
|
||||
### Path 4 (Remote gbrain MCP — HTTP transport with bearer token)
|
||||
|
||||
For users whose brain runs on another machine (Tailscale, ngrok, internal
|
||||
LAN, or a teammate's server). No local gbrain CLI install, no local DB.
|
||||
This skill registers the remote MCP and stops; ingestion + indexing happens
|
||||
on the brain host.
|
||||
|
||||
**4a. Collect MCP URL.** Prompt the user:
|
||||
|
||||
```
|
||||
Paste your gbrain MCP URL (e.g. https://wintermute.tail554574.ts.net:3131/mcp):
|
||||
```
|
||||
|
||||
Read with plain `read -r` (no secret hygiene needed — the URL alone isn't
|
||||
a credential). Validate it starts with `https://` (require TLS for any
|
||||
non-loopback host); refuse `http://` for non-localhost.
|
||||
|
||||
**4b. Collect bearer token via the secret-read helper (D10, never argv).**
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env GBRAIN_MCP_TOKEN "Paste bearer token: " \
|
||||
--echo-redacted 's/.\{6\}$/***REDACTED***/'
|
||||
```
|
||||
|
||||
**4c. Verify via gstack-gbrain-mcp-verify.** Run the helper; capture the
|
||||
classified JSON output:
|
||||
|
||||
```bash
|
||||
verify_json=$(GBRAIN_MCP_TOKEN="$GBRAIN_MCP_TOKEN" \
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-mcp-verify "$MCP_URL")
|
||||
status=$(echo "$verify_json" | jq -r .status)
|
||||
```
|
||||
|
||||
If `status != "success"`, the helper has already classified the failure
|
||||
into NETWORK / AUTH / MALFORMED and emitted a one-line remediation hint.
|
||||
Surface the hint above the raw error from `error_text` and **STOP** with
|
||||
a clear "fix and re-run /setup-gbrain" message. Do NOT continue to Step 5a
|
||||
on a failed verify — partial registration would leave the user with a
|
||||
half-broken state.
|
||||
|
||||
Capture two values from the verify output for downstream steps:
|
||||
- `SERVER_VERSION` (e.g., `0.27.1`) — written to the CLAUDE.md block in Step 8.
|
||||
- `URL_FORM_SUPPORTED` (`true|false`) — passed to `gstack-artifacts-init` in
|
||||
Step 7 to control which form of the brain-admin hookup command is printed.
|
||||
|
||||
**4d. (Path 4) Offer local PGLite for code search.** Per plan D10/D11, ask:
|
||||
|
||||
> D# — Want symbol-aware code search on this machine?
|
||||
> Project/branch/task: <one-sentence grounding using detected slug + branch>
|
||||
> ELI10: The remote brain at `<MCP_URL>` is great for cross-machine knowledge,
|
||||
> but symbol queries like `gbrain code-def` / `code-refs` / `code-callers` need
|
||||
> a local index of THIS machine's code. We can spin up a tiny isolated PGLite
|
||||
> database (~30 seconds, no accounts, ~120 MB disk) just for code, separate
|
||||
> from your remote brain. Transcripts and artifacts continue routing through
|
||||
> the artifacts repo to the remote brain — local PGLite stays code-only.
|
||||
> Stakes: without it, semantic code search in this repo's worktrees falls
|
||||
> back to Grep.
|
||||
> Recommendation: A — 30 seconds, no ongoing cost, unlocks the symbol tools.
|
||||
> Completeness: A=10/10 (full split-engine), B=7/10 (remote-only).
|
||||
> A) Yes, set up local PGLite for code (recommended)
|
||||
> ✅ Unlocks `gbrain code-def`, `code-refs`, `code-callers` per worktree
|
||||
> ✅ Independent engine — won't disturb remote brain or share transcripts
|
||||
> B) No, remote MCP only
|
||||
> ✅ Zero local state — only `~/.claude.json` MCP registration
|
||||
> ❌ Symbol code queries fall back to Grep in this repo's worktrees
|
||||
> Net: A = full split-engine; B = remote-only.
|
||||
|
||||
**If A (Yes)**: install + init local PGLite with rollback-safe semantics (D7):
|
||||
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-install || exit $?
|
||||
# At this point the local gbrain CLI is on PATH. Init PGLite, but back up any
|
||||
# existing ~/.gbrain/config.json first (rollback if init fails).
|
||||
if [ -f "$HOME/.gbrain/config.json" ]; then
|
||||
BACKUP="$HOME/.gbrain/config.json.gstack-bak-$(date +%s)"
|
||||
mv "$HOME/.gbrain/config.json" "$BACKUP"
|
||||
fi
|
||||
# gstack default for local code-search PGLite: voyage-code-3 (1024d) when
|
||||
# VOYAGE_API_KEY is set. It wins the A/B over voyage-4-large and OpenAI
|
||||
# text-embedding-3-large on this codebase's symbol queries. Falls back to
|
||||
# gbrain's auto-selected provider when the key isn't present.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
if ! gbrain init --pglite --json "$@"; then
|
||||
if [ -n "${BACKUP:-}" ] && [ -f "$BACKUP" ]; then mv "$BACKUP" "$HOME/.gbrain/config.json"; fi
|
||||
echo "gbrain init failed. Existing config (if any) was restored. PGLite at ~/.gbrain/pglite/ may be in a partial state — \`rm -rf ~/.gbrain/pglite\` to reset." >&2
|
||||
echo "Continuing setup without local code search; you can re-run /setup-gbrain to retry." >&2
|
||||
fi
|
||||
```
|
||||
|
||||
Then continue to Step 5a. The remote-http MCP registration in 5a runs as
|
||||
today; the local PGLite is independent of MCP registration (Claude Code talks
|
||||
to the remote brain via MCP for queries; `gbrain` CLI talks to local PGLite
|
||||
for code-def/refs/callers).
|
||||
|
||||
**If B (No)**: skip the install + init. The local engine stays absent.
|
||||
`gbrain_local_status` will be `missing-config` (or `no-cli` if gbrain isn't
|
||||
installed). `/sync-gbrain` will SKIP the code stage cleanly per plan D12.
|
||||
|
||||
**4e. Skip Steps 3, 4 (other paths) and 5 (local doctor) when B was picked.**
|
||||
When A was picked, Step 3 already ran (via gstack-gbrain-install) and Step 4
|
||||
already ran (via `gbrain init --pglite`); jump straight to Step 5a. When B
|
||||
was picked, Steps 3/4/5 are no-ops; also skip Step 7.5 (transcript ingest)
|
||||
since memory-stage routes through the artifacts pipeline in remote-http mode
|
||||
per plan D11.
|
||||
|
||||
The bearer token (`GBRAIN_MCP_TOKEN`) stays in process env until Step 5a's
|
||||
`claude mcp add --header` consumes it; then `unset GBRAIN_MCP_TOKEN`
|
||||
immediately. Token security trade-off documented in
|
||||
`setup-gbrain/memory.md`: brief argv exposure during `claude mcp add`,
|
||||
resting state in `~/.claude.json` mode 0600.
|
||||
|
||||
### Switch (from detect's existing-engine state)
|
||||
|
||||
```bash
|
||||
# Going PGLite → Supabase, collect URL first (Path 1 flow), then:
|
||||
timeout 180s gbrain migrate --to supabase --url "$URL" --json
|
||||
# Going Supabase → PGLite:
|
||||
timeout 180s gbrain migrate --to pglite --json
|
||||
```
|
||||
|
||||
If `timeout` returns 124 (exit code for timeout): surface D9 message
|
||||
("Migration didn't complete in 3 minutes — another gstack session may be
|
||||
holding a lock on the source brain. Close other workspaces and re-run
|
||||
`/setup-gbrain --switch`. Your original brain is untouched."). STOP.
|
||||
{{SECTION:brain-init}}
|
||||
|
||||
---
|
||||
|
||||
@@ -731,154 +410,19 @@ this machine's transcripts indexed, they pull from your `gstack-artifacts-$USER`
|
||||
repo (set up in Step 7) on whatever schedule they prefer. Set
|
||||
`gstack-config set transcript_ingest_mode off` and continue to Step 8.
|
||||
|
||||
For Paths 1, 2a, 2b, 3:
|
||||
For Paths 1, 2a, 2b, 3, run the ingest gate:
|
||||
|
||||
After memory sync is wired (Step 7) but before persisting the CLAUDE.md
|
||||
config (Step 8), offer to bring this Mac's coding-agent transcripts +
|
||||
curated `~/.gstack/` artifacts into gbrain so the retrieval surface
|
||||
(per-skill manifests, salience block) has data to surface.
|
||||
|
||||
Run the probe to size the operation:
|
||||
```bash
|
||||
bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe
|
||||
```
|
||||
|
||||
Read the output. If `Total files in window: 0`, skip — there's nothing
|
||||
to ingest. Set `gstack-config set transcript_ingest_mode incremental`
|
||||
silently and continue to Step 8.
|
||||
|
||||
If `New (never ingested)` is < 200 AND total bytes are < 100MB: silent
|
||||
bulk via `bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet`. Set
|
||||
`transcript_ingest_mode=incremental` and continue.
|
||||
|
||||
Otherwise (the "many transcripts on disk" path): AskUserQuestion with
|
||||
the exact counts AND the value promise. Default scope is **current repo
|
||||
only, last 90 days**:
|
||||
|
||||
> "Found <N_repo> transcripts in THIS repo (<repo-slug>) over the last
|
||||
> 90 days, plus <N_other> across other repos on this machine (<bytes>
|
||||
> total if all ingested). Ingest THIS repo's transcripts into gbrain?
|
||||
>
|
||||
> What you get after this: every gstack skill auto-loads recent salience
|
||||
> from your past sessions in this repo, so the agent finds your prior
|
||||
> work without you describing it. You can query 'what was I doing on
|
||||
> day X' and get a real answer. Per-session pages are searchable,
|
||||
> taggable, and deletable. Secret scanning runs before any push.
|
||||
>
|
||||
> What stays the same: nothing leaves your machine unless gbrain sync
|
||||
> is enabled (Step 7). Per-repo trust policies still apply.
|
||||
>
|
||||
> Multi-Mac note: if you HAVE enabled brain sync (Step 7), these
|
||||
> transcript pages will sync across your Macs. Caveat: deleting a
|
||||
> transcript page later removes it from gbrain but git history retains
|
||||
> it in prior commits. Use `gstack-transcript-prune` to delete in bulk;
|
||||
> use `git filter-repo` on the brain remote for hard-delete from
|
||||
> history."
|
||||
|
||||
Options:
|
||||
- A) Yes — this repo, last 90 days (recommended; ~est min)
|
||||
- B) Yes — this repo, ALL history
|
||||
- C) Yes — this repo + other repos on this machine
|
||||
- D) Skip historical, track new from now (`transcript_ingest_mode=incremental`)
|
||||
- E) Never ingest transcripts (`transcript_ingest_mode=off`)
|
||||
|
||||
After answer:
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <choice>
|
||||
bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --full --no-brain-sync
|
||||
```
|
||||
(`--no-brain-sync` because Step 7 already wired that path; this just
|
||||
runs the code import + memory ingest stages. Brain-sync will run on the
|
||||
next preamble hook.)
|
||||
|
||||
If A/D/E, ingest is incremental from this point on; preamble-boundary
|
||||
hook runs `bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet` on every skill
|
||||
start (cheap mtime fast-path).
|
||||
|
||||
Reference doc for users: `setup-gbrain/memory.md` (linked from CLAUDE.md
|
||||
Step 8).
|
||||
{{SECTION:transcript-gate}}
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Persist `## GBrain Configuration` in CLAUDE.md
|
||||
|
||||
Find-and-replace (or append) the section. Block format depends on mode:
|
||||
CLAUDE.md is the audit trail: after a successful setup, persist the
|
||||
configuration block. The exact block formats (remote-http vs local-stdio) and
|
||||
the post-Step-9 Search Guidance write live in the claude-md-persist section.
|
||||
|
||||
### Path 4 (Remote MCP)
|
||||
|
||||
```markdown
|
||||
## GBrain Configuration (configured by /setup-gbrain)
|
||||
- Mode: remote-http
|
||||
- MCP URL: {MCP_URL}
|
||||
- Server version: gbrain v{SERVER_VERSION} (from Step 4c verify)
|
||||
- Setup date: {today}
|
||||
- MCP registered: yes (user scope)
|
||||
- Token: stored in ~/.claude.json (do not commit; never written to CLAUDE.md)
|
||||
- Artifacts repo: {gstack_artifacts_remote URL or "none"}
|
||||
- Artifacts sync: {off|artifacts-only|full}
|
||||
- Current repo policy: {read-write|read-only|deny|unset}
|
||||
```
|
||||
|
||||
The bearer token is **never** written to CLAUDE.md (CLAUDE.md is checked
|
||||
in to git in many projects). It lives only in `~/.claude.json` where
|
||||
`claude mcp add` placed it.
|
||||
|
||||
### Paths 1, 2a, 2b, 3 (Local stdio)
|
||||
|
||||
```markdown
|
||||
## GBrain Configuration (configured by /setup-gbrain)
|
||||
- Mode: local-stdio
|
||||
- Engine: {pglite|postgres}
|
||||
- Config file: ~/.gbrain/config.json (mode 0600)
|
||||
- Setup date: {today}
|
||||
- MCP registered: {yes/no}
|
||||
- Artifacts sync: {off|artifacts-only|full}
|
||||
- Current repo policy: {read-write|read-only|deny|unset}
|
||||
```
|
||||
|
||||
**After Step 9 (smoke test) passes, also write the `## GBrain Search Guidance`
|
||||
block** so the coding agent learns when to prefer `gbrain` over Grep. This
|
||||
block is gated on the smoke test passing — write the Configuration block
|
||||
first (so the user knows what state they're in even if the smoke test fails),
|
||||
then return here after Step 9 and write the guidance block only if smoke
|
||||
test succeeded.
|
||||
|
||||
When Step 9 passes, find-and-replace (or append) this block. Use HTML-comment
|
||||
delimiters so removal regex is unambiguous and never eats user content. The
|
||||
block content is machine-AGNOSTIC — no engine type, no page counts, no
|
||||
last-sync time. Machine state stays in the Configuration block above.
|
||||
|
||||
```markdown
|
||||
## GBrain Search Guidance (configured by /sync-gbrain)
|
||||
<!-- gstack-gbrain-search-guidance:start -->
|
||||
|
||||
GBrain is set up and synced on this machine. The agent should prefer gbrain
|
||||
over Grep when the question is semantic or when you don't know the exact
|
||||
identifier yet. Two indexed corpora available via the `gbrain` CLI:
|
||||
- This repo's code (registered as `gstack-code-<repo>` source).
|
||||
- `~/.gstack/` curated memory (registered as `gstack-brain-<user>` source via
|
||||
the existing federation pipeline).
|
||||
|
||||
Prefer gbrain when:
|
||||
- "Where is X handled?" / semantic intent, no exact string yet:
|
||||
`gbrain search "<terms>"` or `gbrain query "<question>"`
|
||||
- "Where is symbol Y defined?" / symbol-based code questions:
|
||||
`gbrain code-def <symbol>` or `gbrain code-refs <symbol>`
|
||||
- "What calls Y?" / "What does Y depend on?":
|
||||
`gbrain code-callers <symbol>` / `gbrain code-callees <symbol>`
|
||||
- "What did we decide last time?" / past plans, retros, learnings:
|
||||
`gbrain search "<terms>" --source gstack-brain-<user>`
|
||||
|
||||
Grep is still right for known exact strings, regex, multiline patterns, and
|
||||
file globs. The brain auto-syncs incrementally on every gstack skill start.
|
||||
Run `/sync-gbrain` to force-refresh, `/sync-gbrain --full` for full reindex.
|
||||
|
||||
<!-- gstack-gbrain-search-guidance:end -->
|
||||
```
|
||||
|
||||
If Step 9 smoke test fails, skip the guidance block write entirely. The user's
|
||||
next `/sync-gbrain` run will re-evaluate capability and write the block when
|
||||
the round-trip works.
|
||||
{{SECTION:claude-md-persist}}
|
||||
|
||||
---
|
||||
|
||||
@@ -1069,7 +613,8 @@ as markdown + git, recoverable manually via `gbrain import` from a clone.
|
||||
|
||||
## `/setup-gbrain --cleanup-orphans` (D20)
|
||||
|
||||
Re-collect a PAT (Step 4 path-2a scope disclosure), then:
|
||||
Re-collect a PAT (show the Path 2a PAT scope disclosure — it lives in the
|
||||
brain-init section; read that section if it isn't already loaded), then:
|
||||
|
||||
```bash
|
||||
# List user's Supabase projects (user has to pipe this through their own
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
<!-- AUTO-GENERATED from brain-init.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
Path-specific. Run ONLY the sub-section below for the path picked in Step 2
|
||||
(or the Switch flow when Step 2 chose engine migration).
|
||||
|
||||
### Path 1 (Supabase, existing URL)
|
||||
|
||||
Source the secret-read helper, collect URL with `read -s` + redacted preview:
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env GBRAIN_POOLER_URL "Paste Session Pooler URL: " \
|
||||
--echo-redacted 's#://[^@]*@#://***@#'
|
||||
```
|
||||
|
||||
Then validate structurally:
|
||||
|
||||
```bash
|
||||
printf '%s' "$GBRAIN_POOLER_URL" | ~/.claude/skills/gstack/bin/gstack-gbrain-supabase-verify -
|
||||
```
|
||||
|
||||
If the verify exit code is 3 (direct-connection URL), the verifier's own
|
||||
message explains the fix; surface it and re-prompt for a Session Pooler URL.
|
||||
|
||||
On success, hand off to gbrain via env var (D10, never argv):
|
||||
|
||||
```bash
|
||||
GBRAIN_DATABASE_URL="$GBRAIN_POOLER_URL" gbrain init --non-interactive --json
|
||||
```
|
||||
|
||||
Then `unset GBRAIN_POOLER_URL GBRAIN_DATABASE_URL` immediately. The URL is
|
||||
now persisted in `~/.gbrain/config.json` at mode 0600 by gbrain itself.
|
||||
|
||||
### Path 2a (Supabase, auto-provision — D7)
|
||||
|
||||
Show the D11 PAT scope disclosure verbatim BEFORE collecting the token:
|
||||
|
||||
> *This Supabase Personal Access Token grants full read/write/delete access
|
||||
> to every project in your Supabase account, not just the `gbrain` one we're
|
||||
> about to create. Supabase doesn't currently support scoped tokens. We use
|
||||
> this PAT only to: create one project, poll it until healthy, read the
|
||||
> Session Pooler URL — then discard it from process memory. The token
|
||||
> remains valid on Supabase's side until you manually revoke it at
|
||||
> https://supabase.com/dashboard/account/tokens — we recommend revoking
|
||||
> immediately after setup completes.*
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env SUPABASE_ACCESS_TOKEN "Paste PAT: "
|
||||
```
|
||||
|
||||
Ask the D17 tier prompt via AskUserQuestion: "Which Supabase tier?" Present
|
||||
Free (2-project limit, pauses after 7d inactivity) vs Pro ($25/mo, no
|
||||
pauses, recommended for real use). Explain that tier is **org-level** (per
|
||||
the Management API contract) — user picks their org based on its current
|
||||
tier. Pro may require them to upgrade the org first at supabase.com.
|
||||
|
||||
List orgs, pick one (AskUserQuestion if multiple):
|
||||
|
||||
```bash
|
||||
orgs=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision list-orgs --json)
|
||||
```
|
||||
|
||||
If the `.orgs` array is empty, surface: "Your Supabase account has no
|
||||
organizations. Create one at https://supabase.com/dashboard, then re-run
|
||||
`/setup-gbrain`." STOP.
|
||||
|
||||
Ask the user for a region (default `us-east-1`; valid values are the 18
|
||||
enum values in the Supabase Management API — list a few common ones, let
|
||||
them pick "Other" for a full list).
|
||||
|
||||
Generate the DB password (never shown to the user):
|
||||
|
||||
```bash
|
||||
export DB_PASS=$(openssl rand -base64 24)
|
||||
```
|
||||
|
||||
Set up a SIGINT trap (D12 basic recovery):
|
||||
|
||||
```bash
|
||||
trap 'echo ""; echo "gstack-gbrain: interrupted. In-flight ref: $INFLIGHT_REF"; \
|
||||
echo "Resume: /setup-gbrain --resume-provision $INFLIGHT_REF"; \
|
||||
echo "Delete: https://supabase.com/dashboard/project/$INFLIGHT_REF"; \
|
||||
unset SUPABASE_ACCESS_TOKEN DB_PASS; exit 130' INT TERM
|
||||
```
|
||||
|
||||
Create + wait + fetch:
|
||||
|
||||
```bash
|
||||
result=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision \
|
||||
create gbrain "$REGION" "$ORG_SLUG" --json)
|
||||
INFLIGHT_REF=$(echo "$result" | jq -r .ref)
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision wait "$INFLIGHT_REF" --json
|
||||
pooler=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision \
|
||||
pooler-url "$INFLIGHT_REF" --json)
|
||||
GBRAIN_DATABASE_URL=$(echo "$pooler" | jq -r .pooler_url)
|
||||
export GBRAIN_DATABASE_URL
|
||||
gbrain init --non-interactive --json
|
||||
unset SUPABASE_ACCESS_TOKEN DB_PASS GBRAIN_DATABASE_URL INFLIGHT_REF
|
||||
trap - INT TERM
|
||||
```
|
||||
|
||||
After success, emit the PAT revocation reminder:
|
||||
|
||||
> "Setup complete. Revoke the PAT you pasted at
|
||||
> https://supabase.com/dashboard/account/tokens — we've already discarded
|
||||
> it from memory and don't need it again. The gbrain project will continue
|
||||
> working because it uses its own embedded database password."
|
||||
|
||||
### Path 2b (Supabase, manual)
|
||||
|
||||
Walk the user through the supabase.com steps:
|
||||
1. Login at https://supabase.com/dashboard
|
||||
2. Click "New Project," name it `gbrain`, pick a region, copy the generated
|
||||
database password (you'll need it for paste-back? no — it's embedded in
|
||||
the pooler URL we collect next)
|
||||
3. Wait ~2 min for the project to initialize
|
||||
4. Settings → Database → Connection Pooler → Session → copy the URL (port
|
||||
6543)
|
||||
|
||||
Then follow the same secret-read + verify + init flow as Path 1.
|
||||
|
||||
### Path 3 (PGLite local)
|
||||
|
||||
```bash
|
||||
# gstack default: voyage-code-3 (1024d) when VOYAGE_API_KEY is set — code
|
||||
# retrieval beats general-purpose embeddings on real code queries (validated
|
||||
# A/B). Without the key, gbrain auto-selects (OpenAI 1536d when available).
|
||||
# Never select gbrain's legacy zeroentropyai recipe for a new brain: the hosted
|
||||
# API sunsets September 4, 2026 (#2365); the wireup helper warns existing installs.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
gbrain init --pglite --json "$@"
|
||||
```
|
||||
|
||||
Done. No network, no secrets (beyond Voyage embedding API calls during sync, if
|
||||
`VOYAGE_API_KEY` is set — ~$0.18 per 1M tokens, pennies per repo).
|
||||
|
||||
### Path 4 (Remote gbrain MCP — HTTP transport with bearer token)
|
||||
|
||||
For users whose brain runs on another machine (Tailscale, ngrok, internal
|
||||
LAN, or a teammate's server). No local gbrain CLI install, no local DB.
|
||||
This skill registers the remote MCP and stops; ingestion + indexing happens
|
||||
on the brain host.
|
||||
|
||||
**4a. Collect MCP URL.** Prompt the user:
|
||||
|
||||
```
|
||||
Paste your gbrain MCP URL (e.g. https://wintermute.tail554574.ts.net:3131/mcp):
|
||||
```
|
||||
|
||||
Read with plain `read -r` (no secret hygiene needed — the URL alone isn't
|
||||
a credential). Validate it starts with `https://` (require TLS for any
|
||||
non-loopback host); refuse `http://` for non-localhost.
|
||||
|
||||
**4b. Collect bearer token via the secret-read helper (D10, never argv).**
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env GBRAIN_MCP_TOKEN "Paste bearer token: " \
|
||||
--echo-redacted 's/.\{6\}$/***REDACTED***/'
|
||||
```
|
||||
|
||||
**4c. Verify via gstack-gbrain-mcp-verify.** Run the helper; capture the
|
||||
classified JSON output:
|
||||
|
||||
```bash
|
||||
verify_json=$(GBRAIN_MCP_TOKEN="$GBRAIN_MCP_TOKEN" \
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-mcp-verify "$MCP_URL")
|
||||
status=$(echo "$verify_json" | jq -r .status)
|
||||
```
|
||||
|
||||
If `status != "success"`, the helper has already classified the failure
|
||||
into NETWORK / AUTH / MALFORMED and emitted a one-line remediation hint.
|
||||
Surface the hint above the raw error from `error_text` and **STOP** with
|
||||
a clear "fix and re-run /setup-gbrain" message. Do NOT continue to Step 5a
|
||||
on a failed verify — partial registration would leave the user with a
|
||||
half-broken state.
|
||||
|
||||
Capture two values from the verify output for downstream steps:
|
||||
- `SERVER_VERSION` (e.g., `0.27.1`) — written to the CLAUDE.md block in Step 8.
|
||||
- `URL_FORM_SUPPORTED` (`true|false`) — passed to `gstack-artifacts-init` in
|
||||
Step 7 to control which form of the brain-admin hookup command is printed.
|
||||
|
||||
**4d. (Path 4) Offer local PGLite for code search.** Per plan D10/D11, ask:
|
||||
|
||||
> D# — Want symbol-aware code search on this machine?
|
||||
> Project/branch/task: <one-sentence grounding using detected slug + branch>
|
||||
> ELI10: The remote brain at `<MCP_URL>` is great for cross-machine knowledge,
|
||||
> but symbol queries like `gbrain code-def` / `code-refs` / `code-callers` need
|
||||
> a local index of THIS machine's code. We can spin up a tiny isolated PGLite
|
||||
> database (~30 seconds, no accounts, ~120 MB disk) just for code, separate
|
||||
> from your remote brain. Transcripts and artifacts continue routing through
|
||||
> the artifacts repo to the remote brain — local PGLite stays code-only.
|
||||
> Stakes: without it, semantic code search in this repo's worktrees falls
|
||||
> back to Grep.
|
||||
> Recommendation: A — 30 seconds, no ongoing cost, unlocks the symbol tools.
|
||||
> Completeness: A=10/10 (full split-engine), B=7/10 (remote-only).
|
||||
> A) Yes, set up local PGLite for code (recommended)
|
||||
> ✅ Unlocks `gbrain code-def`, `code-refs`, `code-callers` per worktree
|
||||
> ✅ Independent engine — won't disturb remote brain or share transcripts
|
||||
> B) No, remote MCP only
|
||||
> ✅ Zero local state — only `~/.claude.json` MCP registration
|
||||
> ❌ Symbol code queries fall back to Grep in this repo's worktrees
|
||||
> Net: A = full split-engine; B = remote-only.
|
||||
|
||||
**If A (Yes)**: install + init local PGLite with rollback-safe semantics (D7):
|
||||
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-install || exit $?
|
||||
# At this point the local gbrain CLI is on PATH. Init PGLite, but back up any
|
||||
# existing ~/.gbrain/config.json first (rollback if init fails).
|
||||
if [ -f "$HOME/.gbrain/config.json" ]; then
|
||||
BACKUP="$HOME/.gbrain/config.json.gstack-bak-$(date +%s)"
|
||||
mv "$HOME/.gbrain/config.json" "$BACKUP"
|
||||
fi
|
||||
# gstack default for local code-search PGLite: voyage-code-3 (1024d) when
|
||||
# VOYAGE_API_KEY is set. It wins the A/B over voyage-4-large and OpenAI
|
||||
# text-embedding-3-large on this codebase's symbol queries. Falls back to
|
||||
# gbrain's auto-selected provider when the key isn't present.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
if ! gbrain init --pglite --json "$@"; then
|
||||
if [ -n "${BACKUP:-}" ] && [ -f "$BACKUP" ]; then mv "$BACKUP" "$HOME/.gbrain/config.json"; fi
|
||||
echo "gbrain init failed. Existing config (if any) was restored. PGLite at ~/.gbrain/pglite/ may be in a partial state — \`rm -rf ~/.gbrain/pglite\` to reset." >&2
|
||||
echo "Continuing setup without local code search; you can re-run /setup-gbrain to retry." >&2
|
||||
fi
|
||||
```
|
||||
|
||||
Then continue to Step 5a. The remote-http MCP registration in 5a runs as
|
||||
today; the local PGLite is independent of MCP registration (Claude Code talks
|
||||
to the remote brain via MCP for queries; `gbrain` CLI talks to local PGLite
|
||||
for code-def/refs/callers).
|
||||
|
||||
**If B (No)**: skip the install + init. The local engine stays absent.
|
||||
`gbrain_local_status` will be `missing-config` (or `no-cli` if gbrain isn't
|
||||
installed). `/sync-gbrain` will SKIP the code stage cleanly per plan D12.
|
||||
|
||||
**4e. Skip Steps 3, 4 (other paths) and 5 (local doctor) when B was picked.**
|
||||
When A was picked, Step 3 already ran (via gstack-gbrain-install) and Step 4
|
||||
already ran (via `gbrain init --pglite`); jump straight to Step 5a. When B
|
||||
was picked, Steps 3/4/5 are no-ops; also skip Step 7.5 (transcript ingest)
|
||||
since memory-stage routes through the artifacts pipeline in remote-http mode
|
||||
per plan D11.
|
||||
|
||||
The bearer token (`GBRAIN_MCP_TOKEN`) stays in process env until Step 5a's
|
||||
`claude mcp add --header` consumes it; then `unset GBRAIN_MCP_TOKEN`
|
||||
immediately. Token security trade-off documented in
|
||||
`setup-gbrain/memory.md`: brief argv exposure during `claude mcp add`,
|
||||
resting state in `~/.claude.json` mode 0600.
|
||||
|
||||
### Switch (from detect's existing-engine state)
|
||||
|
||||
```bash
|
||||
# Going PGLite → Supabase, collect URL first (Path 1 flow), then:
|
||||
timeout 180s gbrain migrate --to supabase --url "$URL" --json
|
||||
# Going Supabase → PGLite:
|
||||
timeout 180s gbrain migrate --to pglite --json
|
||||
```
|
||||
|
||||
If `timeout` returns 124 (exit code for timeout): surface D9 message
|
||||
("Migration didn't complete in 3 minutes — another gstack session may be
|
||||
holding a lock on the source brain. Close other workspaces and re-run
|
||||
`/setup-gbrain --switch`. Your original brain is untouched."). STOP.
|
||||
@@ -0,0 +1,268 @@
|
||||
Path-specific. Run ONLY the sub-section below for the path picked in Step 2
|
||||
(or the Switch flow when Step 2 chose engine migration).
|
||||
|
||||
### Path 1 (Supabase, existing URL)
|
||||
|
||||
Source the secret-read helper, collect URL with `read -s` + redacted preview:
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env GBRAIN_POOLER_URL "Paste Session Pooler URL: " \
|
||||
--echo-redacted 's#://[^@]*@#://***@#'
|
||||
```
|
||||
|
||||
Then validate structurally:
|
||||
|
||||
```bash
|
||||
printf '%s' "$GBRAIN_POOLER_URL" | ~/.claude/skills/gstack/bin/gstack-gbrain-supabase-verify -
|
||||
```
|
||||
|
||||
If the verify exit code is 3 (direct-connection URL), the verifier's own
|
||||
message explains the fix; surface it and re-prompt for a Session Pooler URL.
|
||||
|
||||
On success, hand off to gbrain via env var (D10, never argv):
|
||||
|
||||
```bash
|
||||
GBRAIN_DATABASE_URL="$GBRAIN_POOLER_URL" gbrain init --non-interactive --json
|
||||
```
|
||||
|
||||
Then `unset GBRAIN_POOLER_URL GBRAIN_DATABASE_URL` immediately. The URL is
|
||||
now persisted in `~/.gbrain/config.json` at mode 0600 by gbrain itself.
|
||||
|
||||
### Path 2a (Supabase, auto-provision — D7)
|
||||
|
||||
Show the D11 PAT scope disclosure verbatim BEFORE collecting the token:
|
||||
|
||||
> *This Supabase Personal Access Token grants full read/write/delete access
|
||||
> to every project in your Supabase account, not just the `gbrain` one we're
|
||||
> about to create. Supabase doesn't currently support scoped tokens. We use
|
||||
> this PAT only to: create one project, poll it until healthy, read the
|
||||
> Session Pooler URL — then discard it from process memory. The token
|
||||
> remains valid on Supabase's side until you manually revoke it at
|
||||
> https://supabase.com/dashboard/account/tokens — we recommend revoking
|
||||
> immediately after setup completes.*
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env SUPABASE_ACCESS_TOKEN "Paste PAT: "
|
||||
```
|
||||
|
||||
Ask the D17 tier prompt via AskUserQuestion: "Which Supabase tier?" Present
|
||||
Free (2-project limit, pauses after 7d inactivity) vs Pro ($25/mo, no
|
||||
pauses, recommended for real use). Explain that tier is **org-level** (per
|
||||
the Management API contract) — user picks their org based on its current
|
||||
tier. Pro may require them to upgrade the org first at supabase.com.
|
||||
|
||||
List orgs, pick one (AskUserQuestion if multiple):
|
||||
|
||||
```bash
|
||||
orgs=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision list-orgs --json)
|
||||
```
|
||||
|
||||
If the `.orgs` array is empty, surface: "Your Supabase account has no
|
||||
organizations. Create one at https://supabase.com/dashboard, then re-run
|
||||
`/setup-gbrain`." STOP.
|
||||
|
||||
Ask the user for a region (default `us-east-1`; valid values are the 18
|
||||
enum values in the Supabase Management API — list a few common ones, let
|
||||
them pick "Other" for a full list).
|
||||
|
||||
Generate the DB password (never shown to the user):
|
||||
|
||||
```bash
|
||||
export DB_PASS=$(openssl rand -base64 24)
|
||||
```
|
||||
|
||||
Set up a SIGINT trap (D12 basic recovery):
|
||||
|
||||
```bash
|
||||
trap 'echo ""; echo "gstack-gbrain: interrupted. In-flight ref: $INFLIGHT_REF"; \
|
||||
echo "Resume: /setup-gbrain --resume-provision $INFLIGHT_REF"; \
|
||||
echo "Delete: https://supabase.com/dashboard/project/$INFLIGHT_REF"; \
|
||||
unset SUPABASE_ACCESS_TOKEN DB_PASS; exit 130' INT TERM
|
||||
```
|
||||
|
||||
Create + wait + fetch:
|
||||
|
||||
```bash
|
||||
result=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision \
|
||||
create gbrain "$REGION" "$ORG_SLUG" --json)
|
||||
INFLIGHT_REF=$(echo "$result" | jq -r .ref)
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision wait "$INFLIGHT_REF" --json
|
||||
pooler=$(~/.claude/skills/gstack/bin/gstack-gbrain-supabase-provision \
|
||||
pooler-url "$INFLIGHT_REF" --json)
|
||||
GBRAIN_DATABASE_URL=$(echo "$pooler" | jq -r .pooler_url)
|
||||
export GBRAIN_DATABASE_URL
|
||||
gbrain init --non-interactive --json
|
||||
unset SUPABASE_ACCESS_TOKEN DB_PASS GBRAIN_DATABASE_URL INFLIGHT_REF
|
||||
trap - INT TERM
|
||||
```
|
||||
|
||||
After success, emit the PAT revocation reminder:
|
||||
|
||||
> "Setup complete. Revoke the PAT you pasted at
|
||||
> https://supabase.com/dashboard/account/tokens — we've already discarded
|
||||
> it from memory and don't need it again. The gbrain project will continue
|
||||
> working because it uses its own embedded database password."
|
||||
|
||||
### Path 2b (Supabase, manual)
|
||||
|
||||
Walk the user through the supabase.com steps:
|
||||
1. Login at https://supabase.com/dashboard
|
||||
2. Click "New Project," name it `gbrain`, pick a region, copy the generated
|
||||
database password (you'll need it for paste-back? no — it's embedded in
|
||||
the pooler URL we collect next)
|
||||
3. Wait ~2 min for the project to initialize
|
||||
4. Settings → Database → Connection Pooler → Session → copy the URL (port
|
||||
6543)
|
||||
|
||||
Then follow the same secret-read + verify + init flow as Path 1.
|
||||
|
||||
### Path 3 (PGLite local)
|
||||
|
||||
```bash
|
||||
# gstack default: voyage-code-3 (1024d) when VOYAGE_API_KEY is set — code
|
||||
# retrieval beats general-purpose embeddings on real code queries (validated
|
||||
# A/B). Without the key, gbrain auto-selects (OpenAI 1536d when available).
|
||||
# Never select gbrain's legacy zeroentropyai recipe for a new brain: the hosted
|
||||
# API sunsets September 4, 2026 (#2365); the wireup helper warns existing installs.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
gbrain init --pglite --json "$@"
|
||||
```
|
||||
|
||||
Done. No network, no secrets (beyond Voyage embedding API calls during sync, if
|
||||
`VOYAGE_API_KEY` is set — ~$0.18 per 1M tokens, pennies per repo).
|
||||
|
||||
### Path 4 (Remote gbrain MCP — HTTP transport with bearer token)
|
||||
|
||||
For users whose brain runs on another machine (Tailscale, ngrok, internal
|
||||
LAN, or a teammate's server). No local gbrain CLI install, no local DB.
|
||||
This skill registers the remote MCP and stops; ingestion + indexing happens
|
||||
on the brain host.
|
||||
|
||||
**4a. Collect MCP URL.** Prompt the user:
|
||||
|
||||
```
|
||||
Paste your gbrain MCP URL (e.g. https://wintermute.tail554574.ts.net:3131/mcp):
|
||||
```
|
||||
|
||||
Read with plain `read -r` (no secret hygiene needed — the URL alone isn't
|
||||
a credential). Validate it starts with `https://` (require TLS for any
|
||||
non-loopback host); refuse `http://` for non-localhost.
|
||||
|
||||
**4b. Collect bearer token via the secret-read helper (D10, never argv).**
|
||||
|
||||
```bash
|
||||
. ~/.claude/skills/gstack/bin/gstack-gbrain-lib.sh
|
||||
read_secret_to_env GBRAIN_MCP_TOKEN "Paste bearer token: " \
|
||||
--echo-redacted 's/.\{6\}$/***REDACTED***/'
|
||||
```
|
||||
|
||||
**4c. Verify via gstack-gbrain-mcp-verify.** Run the helper; capture the
|
||||
classified JSON output:
|
||||
|
||||
```bash
|
||||
verify_json=$(GBRAIN_MCP_TOKEN="$GBRAIN_MCP_TOKEN" \
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-mcp-verify "$MCP_URL")
|
||||
status=$(echo "$verify_json" | jq -r .status)
|
||||
```
|
||||
|
||||
If `status != "success"`, the helper has already classified the failure
|
||||
into NETWORK / AUTH / MALFORMED and emitted a one-line remediation hint.
|
||||
Surface the hint above the raw error from `error_text` and **STOP** with
|
||||
a clear "fix and re-run /setup-gbrain" message. Do NOT continue to Step 5a
|
||||
on a failed verify — partial registration would leave the user with a
|
||||
half-broken state.
|
||||
|
||||
Capture two values from the verify output for downstream steps:
|
||||
- `SERVER_VERSION` (e.g., `0.27.1`) — written to the CLAUDE.md block in Step 8.
|
||||
- `URL_FORM_SUPPORTED` (`true|false`) — passed to `gstack-artifacts-init` in
|
||||
Step 7 to control which form of the brain-admin hookup command is printed.
|
||||
|
||||
**4d. (Path 4) Offer local PGLite for code search.** Per plan D10/D11, ask:
|
||||
|
||||
> D# — Want symbol-aware code search on this machine?
|
||||
> Project/branch/task: <one-sentence grounding using detected slug + branch>
|
||||
> ELI10: The remote brain at `<MCP_URL>` is great for cross-machine knowledge,
|
||||
> but symbol queries like `gbrain code-def` / `code-refs` / `code-callers` need
|
||||
> a local index of THIS machine's code. We can spin up a tiny isolated PGLite
|
||||
> database (~30 seconds, no accounts, ~120 MB disk) just for code, separate
|
||||
> from your remote brain. Transcripts and artifacts continue routing through
|
||||
> the artifacts repo to the remote brain — local PGLite stays code-only.
|
||||
> Stakes: without it, semantic code search in this repo's worktrees falls
|
||||
> back to Grep.
|
||||
> Recommendation: A — 30 seconds, no ongoing cost, unlocks the symbol tools.
|
||||
> Completeness: A=10/10 (full split-engine), B=7/10 (remote-only).
|
||||
> A) Yes, set up local PGLite for code (recommended)
|
||||
> ✅ Unlocks `gbrain code-def`, `code-refs`, `code-callers` per worktree
|
||||
> ✅ Independent engine — won't disturb remote brain or share transcripts
|
||||
> B) No, remote MCP only
|
||||
> ✅ Zero local state — only `~/.claude.json` MCP registration
|
||||
> ❌ Symbol code queries fall back to Grep in this repo's worktrees
|
||||
> Net: A = full split-engine; B = remote-only.
|
||||
|
||||
**If A (Yes)**: install + init local PGLite with rollback-safe semantics (D7):
|
||||
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-gbrain-install || exit $?
|
||||
# At this point the local gbrain CLI is on PATH. Init PGLite, but back up any
|
||||
# existing ~/.gbrain/config.json first (rollback if init fails).
|
||||
if [ -f "$HOME/.gbrain/config.json" ]; then
|
||||
BACKUP="$HOME/.gbrain/config.json.gstack-bak-$(date +%s)"
|
||||
mv "$HOME/.gbrain/config.json" "$BACKUP"
|
||||
fi
|
||||
# gstack default for local code-search PGLite: voyage-code-3 (1024d) when
|
||||
# VOYAGE_API_KEY is set. It wins the A/B over voyage-4-large and OpenAI
|
||||
# text-embedding-3-large on this codebase's symbol queries. Falls back to
|
||||
# gbrain's auto-selected provider when the key isn't present.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
if ! gbrain init --pglite --json "$@"; then
|
||||
if [ -n "${BACKUP:-}" ] && [ -f "$BACKUP" ]; then mv "$BACKUP" "$HOME/.gbrain/config.json"; fi
|
||||
echo "gbrain init failed. Existing config (if any) was restored. PGLite at ~/.gbrain/pglite/ may be in a partial state — \`rm -rf ~/.gbrain/pglite\` to reset." >&2
|
||||
echo "Continuing setup without local code search; you can re-run /setup-gbrain to retry." >&2
|
||||
fi
|
||||
```
|
||||
|
||||
Then continue to Step 5a. The remote-http MCP registration in 5a runs as
|
||||
today; the local PGLite is independent of MCP registration (Claude Code talks
|
||||
to the remote brain via MCP for queries; `gbrain` CLI talks to local PGLite
|
||||
for code-def/refs/callers).
|
||||
|
||||
**If B (No)**: skip the install + init. The local engine stays absent.
|
||||
`gbrain_local_status` will be `missing-config` (or `no-cli` if gbrain isn't
|
||||
installed). `/sync-gbrain` will SKIP the code stage cleanly per plan D12.
|
||||
|
||||
**4e. Skip Steps 3, 4 (other paths) and 5 (local doctor) when B was picked.**
|
||||
When A was picked, Step 3 already ran (via gstack-gbrain-install) and Step 4
|
||||
already ran (via `gbrain init --pglite`); jump straight to Step 5a. When B
|
||||
was picked, Steps 3/4/5 are no-ops; also skip Step 7.5 (transcript ingest)
|
||||
since memory-stage routes through the artifacts pipeline in remote-http mode
|
||||
per plan D11.
|
||||
|
||||
The bearer token (`GBRAIN_MCP_TOKEN`) stays in process env until Step 5a's
|
||||
`claude mcp add --header` consumes it; then `unset GBRAIN_MCP_TOKEN`
|
||||
immediately. Token security trade-off documented in
|
||||
`setup-gbrain/memory.md`: brief argv exposure during `claude mcp add`,
|
||||
resting state in `~/.claude.json` mode 0600.
|
||||
|
||||
### Switch (from detect's existing-engine state)
|
||||
|
||||
```bash
|
||||
# Going PGLite → Supabase, collect URL first (Path 1 flow), then:
|
||||
timeout 180s gbrain migrate --to supabase --url "$URL" --json
|
||||
# Going Supabase → PGLite:
|
||||
timeout 180s gbrain migrate --to pglite --json
|
||||
```
|
||||
|
||||
If `timeout` returns 124 (exit code for timeout): surface D9 message
|
||||
("Migration didn't complete in 3 minutes — another gstack session may be
|
||||
holding a lock on the source brain. Close other workspaces and re-run
|
||||
`/setup-gbrain --switch`. Your original brain is untouched."). STOP.
|
||||
@@ -0,0 +1,79 @@
|
||||
<!-- AUTO-GENERATED from claude-md-persist.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
Find-and-replace (or append) the section. Block format depends on mode:
|
||||
|
||||
### Path 4 (Remote MCP)
|
||||
|
||||
```markdown
|
||||
## GBrain Configuration (configured by /setup-gbrain)
|
||||
- Mode: remote-http
|
||||
- MCP URL: {MCP_URL}
|
||||
- Server version: gbrain v{SERVER_VERSION} (from Step 4c verify)
|
||||
- Setup date: {today}
|
||||
- MCP registered: yes (user scope)
|
||||
- Token: stored in ~/.claude.json (do not commit; never written to CLAUDE.md)
|
||||
- Artifacts repo: {gstack_artifacts_remote URL or "none"}
|
||||
- Artifacts sync: {off|artifacts-only|full}
|
||||
- Current repo policy: {read-write|read-only|deny|unset}
|
||||
```
|
||||
|
||||
The bearer token is **never** written to CLAUDE.md (CLAUDE.md is checked
|
||||
in to git in many projects). It lives only in `~/.claude.json` where
|
||||
`claude mcp add` placed it.
|
||||
|
||||
### Paths 1, 2a, 2b, 3 (Local stdio)
|
||||
|
||||
```markdown
|
||||
## GBrain Configuration (configured by /setup-gbrain)
|
||||
- Mode: local-stdio
|
||||
- Engine: {pglite|postgres}
|
||||
- Config file: ~/.gbrain/config.json (mode 0600)
|
||||
- Setup date: {today}
|
||||
- MCP registered: {yes/no}
|
||||
- Artifacts sync: {off|artifacts-only|full}
|
||||
- Current repo policy: {read-write|read-only|deny|unset}
|
||||
```
|
||||
|
||||
**After Step 9 (smoke test) passes, also write the `## GBrain Search Guidance`
|
||||
block** so the coding agent learns when to prefer `gbrain` over Grep. This
|
||||
block is gated on the smoke test passing — write the Configuration block
|
||||
first (so the user knows what state they're in even if the smoke test fails),
|
||||
then return here after Step 9 and write the guidance block only if smoke
|
||||
test succeeded.
|
||||
|
||||
When Step 9 passes, find-and-replace (or append) this block. Use HTML-comment
|
||||
delimiters so removal regex is unambiguous and never eats user content. The
|
||||
block content is machine-AGNOSTIC — no engine type, no page counts, no
|
||||
last-sync time. Machine state stays in the Configuration block above.
|
||||
|
||||
```markdown
|
||||
## GBrain Search Guidance (configured by /sync-gbrain)
|
||||
<!-- gstack-gbrain-search-guidance:start -->
|
||||
|
||||
GBrain is set up and synced on this machine. The agent should prefer gbrain
|
||||
over Grep when the question is semantic or when you don't know the exact
|
||||
identifier yet. Two indexed corpora available via the `gbrain` CLI:
|
||||
- This repo's code (registered as `gstack-code-<repo>` source).
|
||||
- `~/.gstack/` curated memory (registered as `gstack-brain-<user>` source via
|
||||
the existing federation pipeline).
|
||||
|
||||
Prefer gbrain when:
|
||||
- "Where is X handled?" / semantic intent, no exact string yet:
|
||||
`gbrain search "<terms>"` or `gbrain query "<question>"`
|
||||
- "Where is symbol Y defined?" / symbol-based code questions:
|
||||
`gbrain code-def <symbol>` or `gbrain code-refs <symbol>`
|
||||
- "What calls Y?" / "What does Y depend on?":
|
||||
`gbrain code-callers <symbol>` / `gbrain code-callees <symbol>`
|
||||
- "What did we decide last time?" / past plans, retros, learnings:
|
||||
`gbrain search "<terms>" --source gstack-brain-<user>`
|
||||
|
||||
Grep is still right for known exact strings, regex, multiline patterns, and
|
||||
file globs. The brain auto-syncs incrementally on every gstack skill start.
|
||||
Run `/sync-gbrain` to force-refresh, `/sync-gbrain --full` for full reindex.
|
||||
|
||||
<!-- gstack-gbrain-search-guidance:end -->
|
||||
```
|
||||
|
||||
If Step 9 smoke test fails, skip the guidance block write entirely. The user's
|
||||
next `/sync-gbrain` run will re-evaluate capability and write the block when
|
||||
the round-trip works.
|
||||
@@ -0,0 +1,77 @@
|
||||
Find-and-replace (or append) the section. Block format depends on mode:
|
||||
|
||||
### Path 4 (Remote MCP)
|
||||
|
||||
```markdown
|
||||
## GBrain Configuration (configured by /setup-gbrain)
|
||||
- Mode: remote-http
|
||||
- MCP URL: {MCP_URL}
|
||||
- Server version: gbrain v{SERVER_VERSION} (from Step 4c verify)
|
||||
- Setup date: {today}
|
||||
- MCP registered: yes (user scope)
|
||||
- Token: stored in ~/.claude.json (do not commit; never written to CLAUDE.md)
|
||||
- Artifacts repo: {gstack_artifacts_remote URL or "none"}
|
||||
- Artifacts sync: {off|artifacts-only|full}
|
||||
- Current repo policy: {read-write|read-only|deny|unset}
|
||||
```
|
||||
|
||||
The bearer token is **never** written to CLAUDE.md (CLAUDE.md is checked
|
||||
in to git in many projects). It lives only in `~/.claude.json` where
|
||||
`claude mcp add` placed it.
|
||||
|
||||
### Paths 1, 2a, 2b, 3 (Local stdio)
|
||||
|
||||
```markdown
|
||||
## GBrain Configuration (configured by /setup-gbrain)
|
||||
- Mode: local-stdio
|
||||
- Engine: {pglite|postgres}
|
||||
- Config file: ~/.gbrain/config.json (mode 0600)
|
||||
- Setup date: {today}
|
||||
- MCP registered: {yes/no}
|
||||
- Artifacts sync: {off|artifacts-only|full}
|
||||
- Current repo policy: {read-write|read-only|deny|unset}
|
||||
```
|
||||
|
||||
**After Step 9 (smoke test) passes, also write the `## GBrain Search Guidance`
|
||||
block** so the coding agent learns when to prefer `gbrain` over Grep. This
|
||||
block is gated on the smoke test passing — write the Configuration block
|
||||
first (so the user knows what state they're in even if the smoke test fails),
|
||||
then return here after Step 9 and write the guidance block only if smoke
|
||||
test succeeded.
|
||||
|
||||
When Step 9 passes, find-and-replace (or append) this block. Use HTML-comment
|
||||
delimiters so removal regex is unambiguous and never eats user content. The
|
||||
block content is machine-AGNOSTIC — no engine type, no page counts, no
|
||||
last-sync time. Machine state stays in the Configuration block above.
|
||||
|
||||
```markdown
|
||||
## GBrain Search Guidance (configured by /sync-gbrain)
|
||||
<!-- gstack-gbrain-search-guidance:start -->
|
||||
|
||||
GBrain is set up and synced on this machine. The agent should prefer gbrain
|
||||
over Grep when the question is semantic or when you don't know the exact
|
||||
identifier yet. Two indexed corpora available via the `gbrain` CLI:
|
||||
- This repo's code (registered as `gstack-code-<repo>` source).
|
||||
- `~/.gstack/` curated memory (registered as `gstack-brain-<user>` source via
|
||||
the existing federation pipeline).
|
||||
|
||||
Prefer gbrain when:
|
||||
- "Where is X handled?" / semantic intent, no exact string yet:
|
||||
`gbrain search "<terms>"` or `gbrain query "<question>"`
|
||||
- "Where is symbol Y defined?" / symbol-based code questions:
|
||||
`gbrain code-def <symbol>` or `gbrain code-refs <symbol>`
|
||||
- "What calls Y?" / "What does Y depend on?":
|
||||
`gbrain code-callers <symbol>` / `gbrain code-callees <symbol>`
|
||||
- "What did we decide last time?" / past plans, retros, learnings:
|
||||
`gbrain search "<terms>" --source gstack-brain-<user>`
|
||||
|
||||
Grep is still right for known exact strings, regex, multiline patterns, and
|
||||
file globs. The brain auto-syncs incrementally on every gstack skill start.
|
||||
Run `/sync-gbrain` to force-refresh, `/sync-gbrain --full` for full reindex.
|
||||
|
||||
<!-- gstack-gbrain-search-guidance:end -->
|
||||
```
|
||||
|
||||
If Step 9 smoke test fails, skip the guidance block write entirely. The user's
|
||||
next `/sync-gbrain` run will re-evaluate capability and write the block when
|
||||
the round-trip works.
|
||||
@@ -0,0 +1,71 @@
|
||||
<!-- AUTO-GENERATED from engine-remediation.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
### Step 1.5 remediation: broken-engine AskUserQuestion + repair branches
|
||||
|
||||
The user has a non-working local engine (Garry's repro: `~/.gbrain/config.json`
|
||||
points at a dead Postgres URL). Fire a targeted AskUserQuestion BEFORE Step 2:
|
||||
|
||||
> D# — Your local gbrain engine isn't responding. How do you want to fix it?
|
||||
> Project/branch/task: <one-sentence grounding using detected slug + branch>
|
||||
> ELI10: gbrain has a config at `~/.gbrain/config.json` but the engine it points
|
||||
> at isn't reachable. That could be a transient outage (Postgres container
|
||||
> stopped, Tailscale down) OR a stale config you want to abandon. Different
|
||||
> remediation for each case.
|
||||
> Stakes if we pick wrong: "Switch to PGLite" overwrites your existing config
|
||||
> (one-way door if the user actually wanted the broken engine). "Retry" preserves
|
||||
> existing state for transient cases.
|
||||
> Recommendation: A (Retry) — always try the cheap option first; if engine is
|
||||
> just temporarily down it'll come back without any destructive change.
|
||||
> Note: options differ in kind, not coverage — no completeness score.
|
||||
> A) Retry — re-probe the engine (recommended; ~80ms)
|
||||
> ✅ Cheapest test: re-runs `gbrain sources list` to see if engine is back
|
||||
> ✅ Zero side effects; existing config preserved
|
||||
> ❌ If engine is permanently dead, retries forever; user must choose another option
|
||||
> B) Switch to local PGLite (one-way — moves existing config to .bak)
|
||||
> ✅ Fastest path to a working local engine if user has abandoned the old one
|
||||
> ✅ ~30s; no accounts; private to this machine
|
||||
> ❌ Destructive — existing config moved to ~/.gbrain/config.json.gstack-bak-{ts}
|
||||
> C) Switch brain mode (continue to Step 2 path picker)
|
||||
> ✅ Lets user pick Path 1/2/3/4 to re-init from scratch
|
||||
> ✅ Preserves existing config until they explicitly init the new one
|
||||
> ❌ Longer flow if user just wants to repair to PGLite
|
||||
> D) Quit (do nothing)
|
||||
> ✅ No cons — this is a hard-stop choice
|
||||
> ❌ N/A
|
||||
> Net: A is the right starting move; B/C are explicit destructive paths; D bails.
|
||||
|
||||
**If A (Retry)**: re-run `~/.claude/skills/gstack/bin/gstack-gbrain-detect`
|
||||
with `GSTACK_DETECT_NO_CACHE=1` (busts the 60s cache). If the new
|
||||
`gbrain_local_status` is `ok`, continue to Step 2. If still `broken-db` or
|
||||
`broken-config`, fire the same AskUserQuestion again (the user picks again).
|
||||
|
||||
**If B (Switch to PGLite)** — execute the rollback-safe init sequence (plan D7):
|
||||
|
||||
```bash
|
||||
BACKUP="$HOME/.gbrain/config.json.gstack-bak-$(date +%s)"
|
||||
mv "$HOME/.gbrain/config.json" "$BACKUP"
|
||||
# gstack default: voyage-code-3 (1024d) when VOYAGE_API_KEY is set — best for
|
||||
# code retrieval. Without the key, fall back to gbrain's own auto-selected
|
||||
# embedding provider chain (OpenAI 1536d when OPENAI_API_KEY is present, etc.).
|
||||
# Never select gbrain's legacy zeroentropyai recipe for a new brain: the hosted
|
||||
# API sunsets September 4, 2026 (#2365); the wireup helper warns existing installs.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
if ! gbrain init --pglite --json "$@"; then
|
||||
# Restore on failure
|
||||
mv "$BACKUP" "$HOME/.gbrain/config.json"
|
||||
echo "gbrain init failed. Your previous config was restored at $HOME/.gbrain/config.json." >&2
|
||||
echo "PGLite directory at ~/.gbrain/pglite/ may be in a partial state — \`rm -rf ~/.gbrain/pglite\` if needed before retrying." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Switched to local PGLite. Previous config saved at $BACKUP — review before deleting."
|
||||
```
|
||||
|
||||
Then jump to Step 5a (MCP registration; the new PGLite engine is registered as
|
||||
local-stdio).
|
||||
|
||||
**If C (Switch brain mode)**: continue to Step 2's normal path picker.
|
||||
|
||||
**If D (Quit)**: STOP the skill cleanly.
|
||||
@@ -0,0 +1,69 @@
|
||||
### Step 1.5 remediation: broken-engine AskUserQuestion + repair branches
|
||||
|
||||
The user has a non-working local engine (Garry's repro: `~/.gbrain/config.json`
|
||||
points at a dead Postgres URL). Fire a targeted AskUserQuestion BEFORE Step 2:
|
||||
|
||||
> D# — Your local gbrain engine isn't responding. How do you want to fix it?
|
||||
> Project/branch/task: <one-sentence grounding using detected slug + branch>
|
||||
> ELI10: gbrain has a config at `~/.gbrain/config.json` but the engine it points
|
||||
> at isn't reachable. That could be a transient outage (Postgres container
|
||||
> stopped, Tailscale down) OR a stale config you want to abandon. Different
|
||||
> remediation for each case.
|
||||
> Stakes if we pick wrong: "Switch to PGLite" overwrites your existing config
|
||||
> (one-way door if the user actually wanted the broken engine). "Retry" preserves
|
||||
> existing state for transient cases.
|
||||
> Recommendation: A (Retry) — always try the cheap option first; if engine is
|
||||
> just temporarily down it'll come back without any destructive change.
|
||||
> Note: options differ in kind, not coverage — no completeness score.
|
||||
> A) Retry — re-probe the engine (recommended; ~80ms)
|
||||
> ✅ Cheapest test: re-runs `gbrain sources list` to see if engine is back
|
||||
> ✅ Zero side effects; existing config preserved
|
||||
> ❌ If engine is permanently dead, retries forever; user must choose another option
|
||||
> B) Switch to local PGLite (one-way — moves existing config to .bak)
|
||||
> ✅ Fastest path to a working local engine if user has abandoned the old one
|
||||
> ✅ ~30s; no accounts; private to this machine
|
||||
> ❌ Destructive — existing config moved to ~/.gbrain/config.json.gstack-bak-{ts}
|
||||
> C) Switch brain mode (continue to Step 2 path picker)
|
||||
> ✅ Lets user pick Path 1/2/3/4 to re-init from scratch
|
||||
> ✅ Preserves existing config until they explicitly init the new one
|
||||
> ❌ Longer flow if user just wants to repair to PGLite
|
||||
> D) Quit (do nothing)
|
||||
> ✅ No cons — this is a hard-stop choice
|
||||
> ❌ N/A
|
||||
> Net: A is the right starting move; B/C are explicit destructive paths; D bails.
|
||||
|
||||
**If A (Retry)**: re-run `~/.claude/skills/gstack/bin/gstack-gbrain-detect`
|
||||
with `GSTACK_DETECT_NO_CACHE=1` (busts the 60s cache). If the new
|
||||
`gbrain_local_status` is `ok`, continue to Step 2. If still `broken-db` or
|
||||
`broken-config`, fire the same AskUserQuestion again (the user picks again).
|
||||
|
||||
**If B (Switch to PGLite)** — execute the rollback-safe init sequence (plan D7):
|
||||
|
||||
```bash
|
||||
BACKUP="$HOME/.gbrain/config.json.gstack-bak-$(date +%s)"
|
||||
mv "$HOME/.gbrain/config.json" "$BACKUP"
|
||||
# gstack default: voyage-code-3 (1024d) when VOYAGE_API_KEY is set — best for
|
||||
# code retrieval. Without the key, fall back to gbrain's own auto-selected
|
||||
# embedding provider chain (OpenAI 1536d when OPENAI_API_KEY is present, etc.).
|
||||
# Never select gbrain's legacy zeroentropyai recipe for a new brain: the hosted
|
||||
# API sunsets September 4, 2026 (#2365); the wireup helper warns existing installs.
|
||||
set -- # flags ride the positional params — unquoted $VAR breaks under zsh word-splitting (#1798)
|
||||
if [ -n "${VOYAGE_API_KEY:-}" ]; then
|
||||
set -- --embedding-model voyage:voyage-code-3 --embedding-dimensions 1024
|
||||
fi
|
||||
if ! gbrain init --pglite --json "$@"; then
|
||||
# Restore on failure
|
||||
mv "$BACKUP" "$HOME/.gbrain/config.json"
|
||||
echo "gbrain init failed. Your previous config was restored at $HOME/.gbrain/config.json." >&2
|
||||
echo "PGLite directory at ~/.gbrain/pglite/ may be in a partial state — \`rm -rf ~/.gbrain/pglite\` if needed before retrying." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Switched to local PGLite. Previous config saved at $BACKUP — review before deleting."
|
||||
```
|
||||
|
||||
Then jump to Step 5a (MCP registration; the new PGLite engine is registered as
|
||||
local-stdio).
|
||||
|
||||
**If C (Switch brain mode)**: continue to Step 2's normal path picker.
|
||||
|
||||
**If D (Quit)**: STOP the skill cleanly.
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "https://gstack.dev/schemas/section-manifest.json",
|
||||
"skill": "setup-gbrain",
|
||||
"version": 1,
|
||||
"note": "PASSIVE registry (v2 plan T9 / CM2). Fields are IDs, file paths, human titles, and human-readable trigger text ONLY. The skeleton's detect (Step 1) + path picker (Step 2) are the ONLY places that decide WHEN a section is read (the install routes are branch-exclusive — at most one init route runs per invocation); required-reads live in the E2E fixtures. No machine predicate here — see docs/designs/v2_PLAN.md:663.",
|
||||
"sections": [
|
||||
{
|
||||
"id": "engine-remediation",
|
||||
"file": "engine-remediation.md",
|
||||
"title": "Step 1.5: broken-local-engine remediation (retry / switch-to-PGLite / re-init AUQ, rollback-safe)",
|
||||
"trigger": "running the Step 1.5 broken-engine remediation — Step 1's detect returned `gbrain_local_status` of `broken-db` or `broken-config` and no shortcut flag was passed"
|
||||
},
|
||||
{
|
||||
"id": "brain-init",
|
||||
"file": "brain-init.md",
|
||||
"title": "Step 4: initialize the brain — per-path init procedures (Paths 1, 2a, 2b, 3, 4, Switch)",
|
||||
"trigger": "initializing the brain in Step 4 — run ONLY the procedure for the path picked in Step 2 (Paths 1/2a/2b/3/4 or Switch; also holds the PAT scope disclosure that `--cleanup-orphans` re-uses)"
|
||||
},
|
||||
{
|
||||
"id": "transcript-gate",
|
||||
"file": "transcript-gate.md",
|
||||
"title": "Step 7.5: transcript & memory ingest gate (probe, thresholds, ingest AskUserQuestion)",
|
||||
"trigger": "running the Step 7.5 transcript & memory ingest gate on Paths 1, 2a, 2b, or 3 (Path 4 skips this section entirely — see the skeleton's skip note)"
|
||||
},
|
||||
{
|
||||
"id": "claude-md-persist",
|
||||
"file": "claude-md-persist.md",
|
||||
"title": "Step 8: persist the GBrain Configuration + Search Guidance blocks in CLAUDE.md",
|
||||
"trigger": "persisting the Step 8 `## GBrain Configuration` block to CLAUDE.md (and the Search Guidance block after Step 9 passes)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<!-- AUTO-GENERATED from transcript-gate.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
After memory sync is wired (Step 7) but before persisting the CLAUDE.md
|
||||
config (Step 8), offer to bring this Mac's coding-agent transcripts +
|
||||
curated `~/.gstack/` artifacts into gbrain so the retrieval surface
|
||||
(per-skill manifests, salience block) has data to surface.
|
||||
|
||||
Run the probe to size the operation:
|
||||
```bash
|
||||
bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe
|
||||
```
|
||||
|
||||
Read the output. If `Total files in window: 0`, skip — there's nothing
|
||||
to ingest. Set `gstack-config set transcript_ingest_mode incremental`
|
||||
silently and continue to Step 8.
|
||||
|
||||
If `New (never ingested)` is < 200 AND total bytes are < 100MB: silent
|
||||
bulk via `bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet`. Set
|
||||
`transcript_ingest_mode=incremental` and continue.
|
||||
|
||||
Otherwise (the "many transcripts on disk" path): AskUserQuestion with
|
||||
the exact counts AND the value promise. Default scope is **current repo
|
||||
only, last 90 days**:
|
||||
|
||||
> "Found <N_repo> transcripts in THIS repo (<repo-slug>) over the last
|
||||
> 90 days, plus <N_other> across other repos on this machine (<bytes>
|
||||
> total if all ingested). Ingest THIS repo's transcripts into gbrain?
|
||||
>
|
||||
> What you get after this: every gstack skill auto-loads recent salience
|
||||
> from your past sessions in this repo, so the agent finds your prior
|
||||
> work without you describing it. You can query 'what was I doing on
|
||||
> day X' and get a real answer. Per-session pages are searchable,
|
||||
> taggable, and deletable. Secret scanning runs before any push.
|
||||
>
|
||||
> What stays the same: nothing leaves your machine unless gbrain sync
|
||||
> is enabled (Step 7). Per-repo trust policies still apply.
|
||||
>
|
||||
> Multi-Mac note: if you HAVE enabled brain sync (Step 7), these
|
||||
> transcript pages will sync across your Macs. Caveat: deleting a
|
||||
> transcript page later removes it from gbrain but git history retains
|
||||
> it in prior commits. Use `gstack-transcript-prune` to delete in bulk;
|
||||
> use `git filter-repo` on the brain remote for hard-delete from
|
||||
> history."
|
||||
|
||||
Options:
|
||||
- A) Yes — this repo, last 90 days (recommended; ~est min)
|
||||
- B) Yes — this repo, ALL history
|
||||
- C) Yes — this repo + other repos on this machine
|
||||
- D) Skip historical, track new from now (`transcript_ingest_mode=incremental`)
|
||||
- E) Never ingest transcripts (`transcript_ingest_mode=off`)
|
||||
|
||||
After answer:
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <choice>
|
||||
bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --full --no-brain-sync
|
||||
```
|
||||
(`--no-brain-sync` because Step 7 already wired that path; this just
|
||||
runs the code import + memory ingest stages. Brain-sync will run on the
|
||||
next preamble hook.)
|
||||
|
||||
If A/D/E, ingest is incremental from this point on; preamble-boundary
|
||||
hook runs `bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet` on every skill
|
||||
start (cheap mtime fast-path).
|
||||
|
||||
Reference doc for users: `setup-gbrain/memory.md` (linked from CLAUDE.md
|
||||
Step 8).
|
||||
@@ -0,0 +1,64 @@
|
||||
After memory sync is wired (Step 7) but before persisting the CLAUDE.md
|
||||
config (Step 8), offer to bring this Mac's coding-agent transcripts +
|
||||
curated `~/.gstack/` artifacts into gbrain so the retrieval surface
|
||||
(per-skill manifests, salience block) has data to surface.
|
||||
|
||||
Run the probe to size the operation:
|
||||
```bash
|
||||
bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe
|
||||
```
|
||||
|
||||
Read the output. If `Total files in window: 0`, skip — there's nothing
|
||||
to ingest. Set `gstack-config set transcript_ingest_mode incremental`
|
||||
silently and continue to Step 8.
|
||||
|
||||
If `New (never ingested)` is < 200 AND total bytes are < 100MB: silent
|
||||
bulk via `bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet`. Set
|
||||
`transcript_ingest_mode=incremental` and continue.
|
||||
|
||||
Otherwise (the "many transcripts on disk" path): AskUserQuestion with
|
||||
the exact counts AND the value promise. Default scope is **current repo
|
||||
only, last 90 days**:
|
||||
|
||||
> "Found <N_repo> transcripts in THIS repo (<repo-slug>) over the last
|
||||
> 90 days, plus <N_other> across other repos on this machine (<bytes>
|
||||
> total if all ingested). Ingest THIS repo's transcripts into gbrain?
|
||||
>
|
||||
> What you get after this: every gstack skill auto-loads recent salience
|
||||
> from your past sessions in this repo, so the agent finds your prior
|
||||
> work without you describing it. You can query 'what was I doing on
|
||||
> day X' and get a real answer. Per-session pages are searchable,
|
||||
> taggable, and deletable. Secret scanning runs before any push.
|
||||
>
|
||||
> What stays the same: nothing leaves your machine unless gbrain sync
|
||||
> is enabled (Step 7). Per-repo trust policies still apply.
|
||||
>
|
||||
> Multi-Mac note: if you HAVE enabled brain sync (Step 7), these
|
||||
> transcript pages will sync across your Macs. Caveat: deleting a
|
||||
> transcript page later removes it from gbrain but git history retains
|
||||
> it in prior commits. Use `gstack-transcript-prune` to delete in bulk;
|
||||
> use `git filter-repo` on the brain remote for hard-delete from
|
||||
> history."
|
||||
|
||||
Options:
|
||||
- A) Yes — this repo, last 90 days (recommended; ~est min)
|
||||
- B) Yes — this repo, ALL history
|
||||
- C) Yes — this repo + other repos on this machine
|
||||
- D) Skip historical, track new from now (`transcript_ingest_mode=incremental`)
|
||||
- E) Never ingest transcripts (`transcript_ingest_mode=off`)
|
||||
|
||||
After answer:
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <choice>
|
||||
bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --full --no-brain-sync
|
||||
```
|
||||
(`--no-brain-sync` because Step 7 already wired that path; this just
|
||||
runs the code import + memory ingest stages. Brain-sync will run on the
|
||||
next preamble hook.)
|
||||
|
||||
If A/D/E, ingest is incremental from this point on; preamble-boundary
|
||||
hook runs `bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet` on every skill
|
||||
start (cheap mtime fast-path).
|
||||
|
||||
Reference doc for users: `setup-gbrain/memory.md` (linked from CLAUDE.md
|
||||
Step 8).
|
||||
@@ -83,7 +83,7 @@ exit 0
|
||||
|
||||
/**
|
||||
* Verbatim reimplementation of the skill template's voyage-code-3
|
||||
* conditional. The template (setup-gbrain/SKILL.md.tmpl Path 3, Step 1.5
|
||||
* conditional. The template (setup-gbrain/sections/brain-init.md.tmpl Path 3, Step 1.5
|
||||
* inside the rollback wrapper, Step 4.5 Path 4 Yes branch) instructs the
|
||||
* model to execute this bash; we execute the same bash here and assert the
|
||||
* argv passed to gbrain matches the contract.
|
||||
@@ -202,9 +202,18 @@ gbrain init --pglite --json $GBRAIN_EMBED_FLAGS
|
||||
});
|
||||
|
||||
it("template uses the positional-params shape, not an unquoted flags var", () => {
|
||||
// Carved (token-reduction Phase 4): count across the tmpl UNION — one
|
||||
// PGLite init site stays in the skeleton, the Path-3/4 sites live in the
|
||||
// brain-init section.
|
||||
const tmpl = readFileSync(
|
||||
join(import.meta.dir, "..", "setup-gbrain", "SKILL.md.tmpl"),
|
||||
"utf-8",
|
||||
) + readFileSync(
|
||||
join(import.meta.dir, "..", "setup-gbrain", "sections", "brain-init.md.tmpl"),
|
||||
"utf-8",
|
||||
) + readFileSync(
|
||||
join(import.meta.dir, "..", "setup-gbrain", "sections", "engine-remediation.md.tmpl"),
|
||||
"utf-8",
|
||||
);
|
||||
expect(tmpl).not.toContain("$GBRAIN_EMBED_FLAGS");
|
||||
const sites = tmpl.match(/gbrain init --pglite --json "\$@"/g) || [];
|
||||
@@ -229,8 +238,10 @@ describe("template alignment: the .tmpl actually contains the voyage gate", () =
|
||||
// Belt-and-suspenders: if someone edits the template and drops the
|
||||
// VOYAGE_API_KEY conditional without updating the test above, this catches
|
||||
// it. The shell snippet under test must literally appear in the .tmpl.
|
||||
const TEMPLATE_PATH = join(import.meta.dir, "..", "setup-gbrain", "SKILL.md.tmpl");
|
||||
const tmpl = readFileSync(TEMPLATE_PATH, "utf-8");
|
||||
// Carved union — see comment above.
|
||||
const tmpl = readFileSync(join(import.meta.dir, "..", "setup-gbrain", "SKILL.md.tmpl"), "utf-8")
|
||||
+ readFileSync(join(import.meta.dir, "..", "setup-gbrain", "sections", "brain-init.md.tmpl"), "utf-8")
|
||||
+ readFileSync(join(import.meta.dir, "..", "setup-gbrain", "sections", "engine-remediation.md.tmpl"), "utf-8");
|
||||
|
||||
it("setup-gbrain template gates the embedding-model flag on VOYAGE_API_KEY", () => {
|
||||
// Should appear at least once (currently 3 init sites use the same gate).
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* setup-gbrain E2E fixture builder — carve-aware (token-reduction Phase 4).
|
||||
*
|
||||
* setup-gbrain is carved: the generated SKILL.md is a decision-tree skeleton
|
||||
* whose STOP-Read pointers reference install paths
|
||||
* (`~/.claude/skills/gstack/setup-gbrain/sections/*.md`) that do not exist in
|
||||
* a hermetic E2E sandbox. Pointing an agent at the raw skeleton would burn
|
||||
* turns on failed Reads and never reach the per-path init procedures under
|
||||
* test. This builder reconstructs a runnable single-file fixture, wave-1
|
||||
* style (see the codex fixture in test/skill-e2e-workflow.test.ts):
|
||||
*
|
||||
* 1. slice the skeleton from the skill title (dropping the shared preamble —
|
||||
* CLAUDE.md rule: "E2E test fixtures: extract, don't copy"),
|
||||
* 2. cut the Section index table (its sections/ paths don't resolve here),
|
||||
* 3. replace each STOP pointer with the section body the test needs, or an
|
||||
* explicit "not needed" stub for the rest, and
|
||||
* 4. run a non-empty guard: every needed section's distinctive anchor must
|
||||
* be present in the result, so a renamed/emptied section fails loudly
|
||||
* instead of shipping a silently hollow fixture.
|
||||
*
|
||||
* Monolith-tolerant: if the generated SKILL.md has no STOP pointers (pre-carve
|
||||
* checkout, or a regen that un-carves), the bodies are still inline and the
|
||||
* anchor guard passes — the builder works on both shapes.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..', '..');
|
||||
const SKILL_MD = path.join(ROOT, 'setup-gbrain', 'SKILL.md');
|
||||
const SECTIONS_DIR = path.join(ROOT, 'setup-gbrain', 'sections');
|
||||
|
||||
const TITLE = '# /setup-gbrain — Coding-Agent Onboarding for gbrain';
|
||||
|
||||
/** Matches one generated STOP-Read pointer (two lines) and captures the section file name. */
|
||||
const STOP_POINTER =
|
||||
/^> \*\*STOP\.\*\* Before [^\n]*sections\/([a-z0-9-]+\.md)[^\n]*\n> in full\.[^\n]*/gm;
|
||||
|
||||
/** Distinctive per-section anchors — the non-empty guard for inlined content. */
|
||||
export const SECTION_ANCHORS: Record<string, string> = {
|
||||
'brain-init.md': '### Path 4 (Remote gbrain MCP',
|
||||
'claude-md-persist.md': 'Mode: remote-http',
|
||||
'engine-remediation.md': "Your local gbrain engine isn't responding",
|
||||
'transcript-gate.md': 'gstack-memory-ingest.ts --probe',
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the fixture text: skeleton (preamble dropped, Section index cut) with
|
||||
* `neededSections` inlined at their STOP pointers and every other pointer
|
||||
* replaced by an explicit not-needed stub. Throws on any missing anchor.
|
||||
*/
|
||||
export function buildSetupGbrainFixture(neededSections: string[]): string {
|
||||
for (const file of neededSections) {
|
||||
if (!(file in SECTION_ANCHORS)) {
|
||||
throw new Error(
|
||||
`setup-gbrain fixture: unknown section "${file}" — known: ${Object.keys(SECTION_ANCHORS).join(', ')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let full = fs.readFileSync(SKILL_MD, 'utf-8');
|
||||
|
||||
const titleIdx = full.indexOf(TITLE);
|
||||
if (titleIdx < 0) throw new Error(`setup-gbrain fixture: title heading not found: "${TITLE}"`);
|
||||
full = full.slice(titleIdx);
|
||||
|
||||
// Cut the Section index table (heading through its closing --- separator).
|
||||
const idxStart = full.indexOf('## Section index');
|
||||
if (idxStart >= 0) {
|
||||
const idxEnd = full.indexOf('\n---\n', idxStart);
|
||||
if (idxEnd < 0) throw new Error('setup-gbrain fixture: Section index has no closing ---');
|
||||
full = full.slice(0, idxStart) + full.slice(idxEnd + '\n---\n'.length);
|
||||
}
|
||||
|
||||
full = full.replace(STOP_POINTER, (_m, file: string) => {
|
||||
if (!neededSections.includes(file)) {
|
||||
return '_(Section not included in this fixture — not needed for this run. Continue with the next step.)_';
|
||||
}
|
||||
const secPath = path.join(SECTIONS_DIR, file);
|
||||
if (!fs.existsSync(secPath)) {
|
||||
throw new Error(
|
||||
`setup-gbrain fixture: sections/${file} not generated — run bun run gen:skill-docs`,
|
||||
);
|
||||
}
|
||||
const body = fs
|
||||
.readFileSync(secPath, 'utf-8')
|
||||
.replace(/^<!--[^\n]*-->\n/gm, '') // strip AUTO-GENERATED header comments
|
||||
.trim();
|
||||
if (body.length < 500) {
|
||||
throw new Error(`setup-gbrain fixture: sections/${file} is unexpectedly small/empty`);
|
||||
}
|
||||
return body;
|
||||
});
|
||||
|
||||
// Non-empty guard on the RESULT — holds for both the carved shape (section
|
||||
// inlined above) and the monolith shape (body was never carved out).
|
||||
for (const file of neededSections) {
|
||||
if (!full.includes(SECTION_ANCHORS[file])) {
|
||||
throw new Error(
|
||||
`setup-gbrain fixture: needed section "${file}" content missing from fixture ` +
|
||||
`(anchor not found: "${SECTION_ANCHORS[file]}")`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return full;
|
||||
}
|
||||
@@ -25,9 +25,28 @@ import * as path from 'path';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const TMPL = path.join(ROOT, 'setup-gbrain', 'SKILL.md.tmpl');
|
||||
const SECTIONS_DIR = path.join(ROOT, 'setup-gbrain', 'sections');
|
||||
const MEMORY_DOC = path.join(ROOT, 'setup-gbrain', 'memory.md');
|
||||
|
||||
// Carve-aware (token-reduction Phase 4): setup-gbrain is carved. The Step 7.5
|
||||
// ingest-gate body (which owns the R1-R4 invocations) lives in
|
||||
// sections/transcript-gate.md.tmpl; the skeleton keeps dispatch + the Step 10
|
||||
// verdict prose. Negative (no-bare-invocation) checks run over the UNION so a
|
||||
// stale form can't hide in any template file.
|
||||
const tmpl = fs.readFileSync(TMPL, 'utf-8');
|
||||
const transcriptGate = fs.readFileSync(
|
||||
path.join(SECTIONS_DIR, 'transcript-gate.md.tmpl'),
|
||||
'utf-8',
|
||||
);
|
||||
const tmplUnion = [tmpl]
|
||||
.concat(
|
||||
fs
|
||||
.readdirSync(SECTIONS_DIR)
|
||||
.filter((f) => f.endsWith('.md.tmpl'))
|
||||
.sort()
|
||||
.map((f) => fs.readFileSync(path.join(SECTIONS_DIR, f), 'utf-8')),
|
||||
)
|
||||
.join('\n');
|
||||
const memoryDoc = fs.readFileSync(MEMORY_DOC, 'utf-8');
|
||||
|
||||
// A "bare invocation" is the tool name immediately followed by a flag/arg
|
||||
@@ -42,46 +61,46 @@ const memoryDoc = fs.readFileSync(MEMORY_DOC, 'utf-8');
|
||||
const bareMemoryIngest = /\bgstack-memory-ingest\b(?!\.ts)(?:\s|\\\r?\n)+--/;
|
||||
const bareGbrainSync = /\bgstack-gbrain-sync\b(?!\.ts)(?:\s|\\\r?\n)+--/;
|
||||
|
||||
describe('setup-gbrain/SKILL.md.tmpl — bin invocation paths', () => {
|
||||
test('no bare gstack-memory-ingest invocation remains', () => {
|
||||
expect(tmpl).not.toMatch(bareMemoryIngest);
|
||||
describe('setup-gbrain templates (skeleton + sections) — bin invocation paths', () => {
|
||||
test('no bare gstack-memory-ingest invocation remains anywhere in the union', () => {
|
||||
expect(tmplUnion).not.toMatch(bareMemoryIngest);
|
||||
});
|
||||
|
||||
test('no bare gstack-gbrain-sync invocation remains', () => {
|
||||
expect(tmpl).not.toMatch(bareGbrainSync);
|
||||
test('no bare gstack-gbrain-sync invocation remains anywhere in the union', () => {
|
||||
expect(tmplUnion).not.toMatch(bareGbrainSync);
|
||||
});
|
||||
|
||||
test('the probe step uses bun run + .ts (R1)', () => {
|
||||
expect(tmpl).toContain(
|
||||
test('the probe step uses bun run + .ts (R1, transcript-gate section)', () => {
|
||||
expect(transcriptGate).toContain(
|
||||
'bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --probe'
|
||||
);
|
||||
});
|
||||
|
||||
test('the silent-bulk mention uses bun run + .ts (R2)', () => {
|
||||
expect(tmpl).toContain(
|
||||
test('the silent-bulk mention uses bun run + .ts (R2, transcript-gate section)', () => {
|
||||
expect(transcriptGate).toContain(
|
||||
'bun run ~/.claude/skills/gstack/bin/gstack-memory-ingest.ts --bulk --quiet'
|
||||
);
|
||||
});
|
||||
|
||||
test('the post-answer full-sync step uses bun run + .ts (R3)', () => {
|
||||
expect(tmpl).toContain(
|
||||
test('the post-answer full-sync step uses bun run + .ts (R3, transcript-gate section)', () => {
|
||||
expect(transcriptGate).toContain(
|
||||
'bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --full --no-brain-sync'
|
||||
);
|
||||
});
|
||||
|
||||
test('the preamble-hook incremental-sync mention uses bun run + .ts (R4)', () => {
|
||||
expect(tmpl).toContain(
|
||||
test('the preamble-hook incremental-sync mention uses bun run + .ts (R4, transcript-gate section)', () => {
|
||||
expect(transcriptGate).toContain(
|
||||
'bun run ~/.claude/skills/gstack/bin/gstack-gbrain-sync.ts --incremental --quiet'
|
||||
);
|
||||
});
|
||||
|
||||
test('the neighboring gstack-config line in the post-answer block is untouched (bash script, no extension)', () => {
|
||||
expect(tmpl).toContain(
|
||||
expect(transcriptGate).toContain(
|
||||
'~/.claude/skills/gstack/bin/gstack-config set transcript_ingest_mode <choice>'
|
||||
);
|
||||
});
|
||||
|
||||
test('the prose-only mention naming the tool as a sentence subject is left unchanged (KTD4 — not a literal invocation)', () => {
|
||||
test('the prose-only mention naming the tool as a sentence subject is left unchanged (KTD4 — not a literal invocation; Step 10 verdict, skeleton)', () => {
|
||||
expect(tmpl).toContain('gstack-memory-ingest now persists staged transcripts to');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
// setup-gbrain Path 4 structural lint.
|
||||
//
|
||||
// Verifies the SKILL.md.tmpl has the prose contract that Path 4 (Remote MCP)
|
||||
// depends on: STOP gates after verify failures, never-write-token rules,
|
||||
// mode-aware CLAUDE.md block, idempotent re-run path.
|
||||
// Verifies the skill's templates carry the prose contract that Path 4
|
||||
// (Remote MCP) depends on: STOP gates after verify failures, never-write-token
|
||||
// rules, mode-aware CLAUDE.md block, idempotent re-run path.
|
||||
//
|
||||
// Carve-aware (token-reduction Phase 4): setup-gbrain is carved — the
|
||||
// SKILL.md.tmpl is a decision-tree skeleton (detect, path dispatch, verify,
|
||||
// MCP registration, verdict) and the branch-exclusive install bodies live in
|
||||
// setup-gbrain/sections/*.md.tmpl. Each pin below targets the file that OWNS
|
||||
// the content: dispatch/verdict pins hit the skeleton, per-path init pins hit
|
||||
// sections/brain-init.md.tmpl, the CLAUDE.md block pins hit
|
||||
// sections/claude-md-persist.md.tmpl, and the token-security regressions run
|
||||
// over the union so a marker can't silently vanish during a re-carve.
|
||||
//
|
||||
// Why a structural test instead of a full Agent SDK E2E:
|
||||
// - Side effects (claude.json mutation, MCP registration) are covered
|
||||
@@ -21,110 +30,157 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const TMPL = path.join(ROOT, 'setup-gbrain', 'SKILL.md.tmpl');
|
||||
const SKILL_DIR = path.join(ROOT, 'setup-gbrain');
|
||||
const SECTIONS_DIR = path.join(SKILL_DIR, 'sections');
|
||||
|
||||
const tmpl = fs.readFileSync(TMPL, 'utf-8');
|
||||
// Skeleton template — always loaded; owns detect, path dispatch, Steps 5/5a/6/7/9/10.
|
||||
const skeleton = fs.readFileSync(path.join(SKILL_DIR, 'SKILL.md.tmpl'), 'utf-8');
|
||||
// Per-path init procedures (Paths 1/2a/2b/3/4 + Switch) — Step 4 body.
|
||||
const brainInit = fs.readFileSync(path.join(SECTIONS_DIR, 'brain-init.md.tmpl'), 'utf-8');
|
||||
// Step 8 CLAUDE.md persist body (both mode blocks + the gated guidance write).
|
||||
const claudeMdPersist = fs.readFileSync(
|
||||
path.join(SECTIONS_DIR, 'claude-md-persist.md.tmpl'),
|
||||
'utf-8',
|
||||
);
|
||||
// Skeleton + every section template — total behavior, order-stable.
|
||||
const union = [skeleton]
|
||||
.concat(
|
||||
fs
|
||||
.readdirSync(SECTIONS_DIR)
|
||||
.filter((f) => f.endsWith('.md.tmpl'))
|
||||
.sort()
|
||||
.map((f) => fs.readFileSync(path.join(SECTIONS_DIR, f), 'utf-8')),
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
describe('setup-gbrain Path 4 (Remote MCP) — structural contract', () => {
|
||||
test('Step 2 lists Path 4 as one of the path options', () => {
|
||||
// "4 — Remote gbrain MCP" with em-dash (—, U+2014 — one codepoint).
|
||||
expect(tmpl).toMatch(/\*\*4 . Remote gbrain MCP/);
|
||||
describe('setup-gbrain carve — dispatch stays in the skeleton', () => {
|
||||
test('the path-dispatch step (Step 2 picker) stays always-loaded', () => {
|
||||
expect(skeleton).toContain('## Step 2: Pick a path (AskUserQuestion)');
|
||||
});
|
||||
|
||||
test('Step 4 has a Path 4 sub-section', () => {
|
||||
expect(tmpl).toMatch(/### Path 4 \(Remote gbrain MCP/);
|
||||
test('the skeleton routes to all four sections and renders the index', () => {
|
||||
expect(skeleton).toContain('{{SECTION_INDEX:setup-gbrain}}');
|
||||
for (const id of ['engine-remediation', 'brain-init', 'transcript-gate', 'claude-md-persist']) {
|
||||
expect(skeleton).toContain(`{{SECTION:${id}}}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('the carved bodies moved OUT of the skeleton (no leak-back)', () => {
|
||||
// Step 4 per-path init:
|
||||
expect(skeleton).not.toContain('### Path 1 (Supabase, existing URL)');
|
||||
expect(skeleton).not.toContain('read_secret_to_env GBRAIN_MCP_TOKEN');
|
||||
// Step 1.5 remediation AUQ:
|
||||
expect(skeleton).not.toContain("Your local gbrain engine isn't responding");
|
||||
// Step 7.5 ingest gate body:
|
||||
expect(skeleton).not.toContain('gstack-memory-ingest.ts --probe');
|
||||
// Step 8 block formats:
|
||||
expect(skeleton).not.toContain('Mode: remote-http');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup-gbrain Path 4 (Remote MCP) — structural contract', () => {
|
||||
test('Step 2 lists Path 4 as one of the path options (skeleton)', () => {
|
||||
// "4 — Remote gbrain MCP" with em-dash (—, U+2014 — one codepoint).
|
||||
expect(skeleton).toMatch(/\*\*4 . Remote gbrain MCP/);
|
||||
});
|
||||
|
||||
test('Step 4 has a Path 4 sub-section (brain-init section)', () => {
|
||||
expect(brainInit).toMatch(/### Path 4 \(Remote gbrain MCP/);
|
||||
});
|
||||
|
||||
test('Step 4 collects the bearer via read_secret_to_env, never argv', () => {
|
||||
// The secret-read helper is the canonical token-capture pattern.
|
||||
// Without it, tokens land in shell history.
|
||||
expect(tmpl).toContain('read_secret_to_env GBRAIN_MCP_TOKEN');
|
||||
expect(brainInit).toContain('read_secret_to_env GBRAIN_MCP_TOKEN');
|
||||
});
|
||||
|
||||
test('Step 4c invokes gstack-gbrain-mcp-verify and STOPs on failure', () => {
|
||||
expect(tmpl).toContain('gstack-gbrain-mcp-verify');
|
||||
expect(brainInit).toContain('gstack-gbrain-mcp-verify');
|
||||
// The STOP rule is what prevents partial registration after auth fail.
|
||||
const path4Section = tmpl.split('### Path 4')[1] || '';
|
||||
const path4Section = brainInit.split('### Path 4')[1] || '';
|
||||
expect(path4Section).toMatch(/STOP/);
|
||||
});
|
||||
|
||||
test('Step 4d explicitly skips Steps 3, 4 (other paths), 5, 7.5 in remote mode', () => {
|
||||
expect(tmpl).toMatch(/4d.*[Ss]kip Steps? 3, 4.*5.*7\.5/s);
|
||||
expect(brainInit).toMatch(/4d.*[Ss]kip Steps? 3, 4.*5.*7\.5/s);
|
||||
});
|
||||
|
||||
test('Step 5a has a Path 4 branch with claude mcp add --transport http', () => {
|
||||
expect(tmpl).toMatch(/Path 4 \(Remote MCP/);
|
||||
expect(tmpl).toMatch(/claude mcp add --scope user --transport http gbrain/);
|
||||
expect(tmpl).toContain('Authorization: Bearer $GBRAIN_MCP_TOKEN');
|
||||
test('Step 5a has a Path 4 branch with claude mcp add --transport http (skeleton)', () => {
|
||||
expect(skeleton).toMatch(/Path 4 \(Remote MCP/);
|
||||
expect(skeleton).toMatch(/claude mcp add --scope user --transport http gbrain/);
|
||||
expect(skeleton).toContain('Authorization: Bearer $GBRAIN_MCP_TOKEN');
|
||||
// Token must be unset after registration so it doesn't linger in env.
|
||||
expect(tmpl).toMatch(/unset GBRAIN_MCP_TOKEN/);
|
||||
expect(skeleton).toMatch(/unset GBRAIN_MCP_TOKEN/);
|
||||
});
|
||||
|
||||
test('Step 5a removes any prior gbrain registration before adding the new one', () => {
|
||||
// Otherwise local-stdio + remote-http coexist, which breaks routing.
|
||||
expect(tmpl).toMatch(/claude mcp remove gbrain/);
|
||||
expect(skeleton).toMatch(/claude mcp remove gbrain/);
|
||||
});
|
||||
|
||||
test('Step 7 calls gstack-artifacts-init with --url-form-supported flag', () => {
|
||||
expect(tmpl).toMatch(/gstack-artifacts-init.*--url-form-supported/);
|
||||
test('Step 7 calls gstack-artifacts-init with --url-form-supported flag (skeleton)', () => {
|
||||
expect(skeleton).toMatch(/gstack-artifacts-init.*--url-form-supported/);
|
||||
});
|
||||
|
||||
test('Step 8 CLAUDE.md block branches on mode', () => {
|
||||
test('Step 8 CLAUDE.md block branches on mode (claude-md-persist section)', () => {
|
||||
// The remote-http block has Mode: remote-http; local-stdio block has Engine:.
|
||||
expect(tmpl).toMatch(/### Path 4 \(Remote MCP\)/);
|
||||
expect(tmpl).toMatch(/Mode: remote-http/);
|
||||
expect(tmpl).toMatch(/Mode: local-stdio/);
|
||||
expect(claudeMdPersist).toMatch(/### Path 4 \(Remote MCP\)/);
|
||||
expect(claudeMdPersist).toMatch(/Mode: remote-http/);
|
||||
expect(claudeMdPersist).toMatch(/Mode: local-stdio/);
|
||||
});
|
||||
|
||||
test('Step 8 explicitly says the bearer is never written to CLAUDE.md', () => {
|
||||
// Token-leak regression guard. CLAUDE.md is committed in many projects.
|
||||
expect(tmpl).toMatch(/bearer token is \*\*never\*\* written to CLAUDE\.md/);
|
||||
expect(claudeMdPersist).toMatch(/bearer token is \*\*never\*\* written to CLAUDE\.md/);
|
||||
});
|
||||
|
||||
test('Step 9 smoke test on Path 4 prints a placeholder, never the real token', () => {
|
||||
// Don't paste the token into the curl example the user might share.
|
||||
expect(tmpl).toMatch(/<YOUR_TOKEN>/);
|
||||
expect(skeleton).toMatch(/<YOUR_TOKEN>/);
|
||||
});
|
||||
|
||||
test('Step 10 verdict block has a remote-http variant separate from local-stdio', () => {
|
||||
expect(tmpl).toMatch(/### Path 4 \(Remote MCP\)/);
|
||||
expect(tmpl).toMatch(/mode: remote-http/);
|
||||
expect(tmpl).toMatch(/N\/A.*remote mode/);
|
||||
expect(skeleton).toMatch(/### Path 4 \(Remote MCP\)/);
|
||||
expect(skeleton).toMatch(/mode: remote-http/);
|
||||
expect(skeleton).toMatch(/N\/A.*remote mode/);
|
||||
});
|
||||
|
||||
test('idempotency: re-running with gbrain_mcp_mode=remote-http skips Step 2', () => {
|
||||
// Re-run path stays graceful; no double-registration.
|
||||
expect(tmpl).toMatch(/gbrain_mcp_mode=remote-http/);
|
||||
expect(skeleton).toMatch(/gbrain_mcp_mode=remote-http/);
|
||||
});
|
||||
|
||||
test('Step 5 (local doctor) explicitly skips on Path 4', () => {
|
||||
expect(tmpl).toMatch(/SKIP entirely on Path 4 \(Remote MCP\)/);
|
||||
test('Step 5 (local doctor) explicitly skips on Path 4 (skeleton)', () => {
|
||||
expect(skeleton).toMatch(/SKIP entirely on Path 4 \(Remote MCP\)/);
|
||||
});
|
||||
|
||||
test('Step 7.5 (transcript ingest) explicitly skips on Path 4', () => {
|
||||
test('Step 7.5 (transcript ingest) explicitly skips on Path 4 (skeleton)', () => {
|
||||
// Transcript ingest needs local gbrain CLI which Path 4 doesn't install.
|
||||
const matches = tmpl.match(/SKIP entirely on Path 4 \(Remote MCP\)/g);
|
||||
// The skip notes are DISPATCH — they must stay in the always-loaded
|
||||
// skeleton (Steps 3, 5, and 7.5 each carry one).
|
||||
const matches = skeleton.match(/SKIP entirely on Path 4 \(Remote MCP\)/g);
|
||||
expect(matches?.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup-gbrain Path 4 — token security regressions', () => {
|
||||
test('the template never inlines a real-shaped bearer string', () => {
|
||||
test('no template (skeleton or section) inlines a real-shaped bearer string', () => {
|
||||
// We never want a literal "gbrain_<hex>" token to appear in the
|
||||
// template — placeholders only. This catches the failure mode where
|
||||
// someone copies a real token into the template by accident.
|
||||
// templates — placeholders only. This catches the failure mode where
|
||||
// someone copies a real token into a template by accident.
|
||||
const realTokenShape = /gbrain_[a-f0-9]{40,}/;
|
||||
expect(tmpl).not.toMatch(realTokenShape);
|
||||
expect(union).not.toMatch(realTokenShape);
|
||||
});
|
||||
|
||||
test('Path 4 always uses env-var $GBRAIN_MCP_TOKEN, never inline strings', () => {
|
||||
// Find every reference to the bearer header in Path 4 and verify it's
|
||||
// either an env-var expansion or an explicit placeholder. Allow:
|
||||
// Find every reference to the bearer header in Path 4 (across the
|
||||
// skeleton AND sections) and verify it's either an env-var expansion
|
||||
// or an explicit placeholder. Allow:
|
||||
// - $GBRAIN_MCP_TOKEN (env-var expansion)
|
||||
// - <bearer>, <YOUR_TOKEN>, <TOKEN> (placeholder)
|
||||
// - "..." (rest-of-doc-text continuation; a doc note showing how
|
||||
// `claude mcp add --header` shapes its argv).
|
||||
const path4Section = tmpl.match(/### Path 4 \(Remote MCP[\s\S]*?(?=###|## )/g)?.join('') || '';
|
||||
const path4Section = union.match(/### Path 4 \(Remote MCP[\s\S]*?(?=###|## )/g)?.join('') || '';
|
||||
const bearerLines = path4Section.match(/Bearer\s+\S+/g) || [];
|
||||
for (const line of bearerLines) {
|
||||
expect(line).toMatch(/Bearer (\$GBRAIN_MCP_TOKEN|<bearer>|<YOUR_TOKEN>|<TOKEN>|\.\.\."?)/);
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
// regression guard for the "verify failed → STOP" gate.
|
||||
//
|
||||
// Cost: ~$0.30-$0.50 per run. Gate-tier (EVALS=1 EVALS_TIER=gate).
|
||||
//
|
||||
// Carve-aware: the Step 4 Path 4 body (collect URL/token, verify, STOP rule)
|
||||
// lives in setup-gbrain/sections/brain-init.md, so the fixture inlines that
|
||||
// section into the skeleton via buildSetupGbrainFixture. Step 8 is not needed:
|
||||
// on a failed verify the skill STOPs before any CLAUDE.md write.
|
||||
|
||||
import { test, expect } from 'bun:test';
|
||||
import { describeE2ETier } from './helpers/e2e-gate';
|
||||
@@ -15,6 +20,7 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
import { runAgentSdkTest, passThroughNonAskUserQuestion, resolveClaudeBinary } from './helpers/agent-sdk-runner';
|
||||
import { buildSetupGbrainFixture } from './helpers/setup-gbrain-fixture';
|
||||
|
||||
// Periodic-tier (companion to skill-e2e-setup-gbrain-remote.test.ts).
|
||||
// Deterministic gate coverage lives in setup-gbrain-path4-structure.test.ts.
|
||||
@@ -86,7 +92,10 @@ describeE2E('/setup-gbrain Path 4 — bad token STOPs cleanly', () => {
|
||||
let modelTextOutput = '';
|
||||
|
||||
try {
|
||||
const skillPath = path.resolve(import.meta.dir, '..', 'setup-gbrain', 'SKILL.md');
|
||||
// Carve-aware fixture: skeleton + brain-init inlined (non-empty guard
|
||||
// inside the builder). The test drives Steps 4a-4c to the STOP.
|
||||
const skillPath = path.join(gstackHome, 'setup-gbrain-SKILL.md');
|
||||
fs.writeFileSync(skillPath, buildSetupGbrainFixture(['brain-init.md']));
|
||||
const result = await runAgentSdkTest({
|
||||
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
||||
userPrompt:
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
passThroughNonAskUserQuestion,
|
||||
resolveClaudeBinary,
|
||||
} from './helpers/agent-sdk-runner';
|
||||
import { buildSetupGbrainFixture } from './helpers/setup-gbrain-fixture';
|
||||
|
||||
const describeE2E = describeE2ETier('periodic');
|
||||
|
||||
@@ -166,11 +167,14 @@ describeE2E('/setup-gbrain Path 4 + Step 4.5 Yes → local PGLite for code', ()
|
||||
process.env.GBRAIN_MCP_TOKEN = 'gbrain_fake_token_for_test';
|
||||
|
||||
try {
|
||||
const skillPath = path.resolve(
|
||||
import.meta.dir,
|
||||
'..',
|
||||
'setup-gbrain',
|
||||
'SKILL.md',
|
||||
// Carve-aware fixture (see test/helpers/setup-gbrain-fixture.ts):
|
||||
// skeleton + brain-init (Step 4 Path 4 body incl. the Step 4d local
|
||||
// PGLite offer this test says Yes to) + claude-md-persist (Step 8 sits
|
||||
// on the walked path to Step 10). Non-empty guard inside the builder.
|
||||
const skillPath = path.join(sandboxHome, 'setup-gbrain-SKILL.md');
|
||||
fs.writeFileSync(
|
||||
skillPath,
|
||||
buildSetupGbrainFixture(['brain-init.md', 'claude-md-persist.md']),
|
||||
);
|
||||
const result = await runAgentSdkTest({
|
||||
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
// Cost: ~$0.30-$0.50 per run. Gate-tier (EVALS=1 EVALS_TIER=gate).
|
||||
//
|
||||
// See setup-gbrain/SKILL.md.tmpl Step 4 (Path 4) for the contract under test.
|
||||
// The Step 4 body lives in setup-gbrain/sections/brain-init.md (carved), so
|
||||
// the fixture is built via buildSetupGbrainFixture: skeleton + the brain-init
|
||||
// and claude-md-persist sections inlined (Step 8 writes the Mode: remote-http
|
||||
// block this test asserts on).
|
||||
|
||||
import { test, expect } from 'bun:test';
|
||||
import { describeE2ETier } from './helpers/e2e-gate';
|
||||
@@ -18,6 +22,7 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
import { runAgentSdkTest, passThroughNonAskUserQuestion, resolveClaudeBinary } from './helpers/agent-sdk-runner';
|
||||
import { buildSetupGbrainFixture } from './helpers/setup-gbrain-fixture';
|
||||
|
||||
// Periodic-tier: the model's interpretation of "follow Path 4 only" is
|
||||
// non-deterministic (it sometimes skips Step 8 CLAUDE.md write, sometimes
|
||||
@@ -144,7 +149,11 @@ describeE2E('/setup-gbrain Path 4 (Remote MCP) — happy path', () => {
|
||||
let modelTextOutput = '';
|
||||
|
||||
try {
|
||||
const skillPath = path.resolve(import.meta.dir, '..', 'setup-gbrain', 'SKILL.md');
|
||||
// Carve-aware fixture: skeleton + brain-init (Step 4 Path 4 body) +
|
||||
// claude-md-persist (Step 8 block formats), STOP pointers resolved
|
||||
// inline so no Read escapes the sandbox. Non-empty guard inside.
|
||||
const skillPath = path.join(gstackHome, 'setup-gbrain-SKILL.md');
|
||||
fs.writeFileSync(skillPath, buildSetupGbrainFixture(['brain-init.md', 'claude-md-persist.md']));
|
||||
const result = await runAgentSdkTest({
|
||||
systemPrompt: { type: 'preset', preset: 'claude_code' },
|
||||
env: childEnv,
|
||||
|
||||
Reference in New Issue
Block a user