From 06605477e25bf9b302888465baec132fa6093f39 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 8 May 2026 12:46:15 -0700 Subject: [PATCH] v1.29.0.0 feat: worktree-aware gbrain code sources via path-hash IDs and CWD pin (#1382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: worktree-aware gbrain code sources via path-hash IDs and CWD pin Conductor sibling worktrees of the same repo no longer collide on a shared gstack-code- source ID. /sync-gbrain now derives a path-hashed source ID per worktree, runs gbrain sources attach to write .gbrain-source in the worktree root, and removes the legacy unsuffixed source on first new-format sync to prevent orphan accumulation. Bug fixes surfaced by /codex during /ship: - Silent attach failure now treated as stage failure (no more ok:true while pin is missing → unqualified code-def hits wrong source). - Startup preamble checks .gbrain-source in the cwd worktree, not global state, so an unsynced worktree no longer claims "indexed" because a sibling synced. - Code stage no longer skipped on remote-MCP (Path 4); the early-exit was in the SKILL template, not the orchestrator. - Source registration routes through lib/gbrain-sources.ts only; deleted the near-duplicate ensureSourceRegisteredSync from the orchestrator. Requires gbrain v0.30.0+ (uses sources attach). Phase 0 spike report: ~/.gstack/projects/garrytan-gstack/2026-05-08-gbrain-split-engine-spike.md Co-Authored-By: Claude Opus 4.7 * chore: bump version and changelog (v1.29.0.0) Co-Authored-By: Claude Opus 4.7 --------- Co-authored-by: Claude Opus 4.7 --- .gitignore | 1 + CHANGELOG.md | 76 ++++++++ SKILL.md | 27 ++- VERSION | 2 +- autoplan/SKILL.md | 27 ++- benchmark-models/SKILL.md | 27 ++- benchmark/SKILL.md | 27 ++- bin/gstack-gbrain-sync.ts | 172 ++++++++++-------- browse/SKILL.md | 27 ++- canary/SKILL.md | 27 ++- codex/SKILL.md | 27 ++- context-restore/SKILL.md | 27 ++- context-save/SKILL.md | 27 ++- cso/SKILL.md | 27 ++- design-consultation/SKILL.md | 27 ++- design-html/SKILL.md | 27 ++- design-review/SKILL.md | 27 ++- design-shotgun/SKILL.md | 27 ++- devex-review/SKILL.md | 27 ++- document-release/SKILL.md | 27 ++- health/SKILL.md | 27 ++- investigate/SKILL.md | 27 ++- land-and-deploy/SKILL.md | 27 ++- landing-report/SKILL.md | 27 ++- learn/SKILL.md | 27 ++- make-pdf/SKILL.md | 27 ++- office-hours/SKILL.md | 27 ++- open-gstack-browser/SKILL.md | 27 ++- package.json | 2 +- pair-agent/SKILL.md | 27 ++- plan-ceo-review/SKILL.md | 27 ++- plan-design-review/SKILL.md | 27 ++- plan-devex-review/SKILL.md | 27 ++- plan-eng-review/SKILL.md | 27 ++- plan-tune/SKILL.md | 27 ++- qa-only/SKILL.md | 27 ++- qa/SKILL.md | 27 ++- retro/SKILL.md | 27 ++- review/SKILL.md | 27 ++- scrape/SKILL.md | 27 ++- .../preamble/generate-brain-sync-block.ts | 27 ++- setup-browser-cookies/SKILL.md | 27 ++- setup-deploy/SKILL.md | 27 ++- setup-gbrain/SKILL.md | 27 ++- ship/SKILL.md | 27 ++- skillify/SKILL.md | 27 ++- sync-gbrain/SKILL.md | 85 +++++---- sync-gbrain/SKILL.md.tmpl | 58 +++--- test/gstack-gbrain-sync.test.ts | 146 ++++++++++++++- 49 files changed, 928 insertions(+), 721 deletions(-) diff --git a/.gitignore b/.gitignore index 979bc17c..9e413bc5 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ bin/gstack-global-discover .openclaw/ .hermes/ .gbrain/ +.gbrain-source .context/ extension/.auth.json # xterm assets are vendored from npm at build time; not source-of-truth. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eeb30a1..aa1f227d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,81 @@ # Changelog +## [1.29.0.0] - 2026-05-08 + +## **Code search beats Grep across every Conductor worktree now, not just the last one you synced.** + +`/sync-gbrain` registers each worktree as its own gbrain source, then +runs `gbrain sources attach ` so the worktree gets a `.gbrain-source` +pin in its root. Subsequent `gbrain code-def`, `code-refs`, `code-callers` +calls from anywhere under the worktree route to that source by default, +no `--source` flag needed. Conductor sibling worktrees of the same repo +no longer collide on a shared `gstack-code-` source ID, so the +last `/sync-gbrain` run no longer silently overwrites every other +worktree's index. + +Three correctness bugs surfaced by `/codex` adversarial review during +`/ship` are fixed in the same release: silent attach failure (sync +succeeds but pin is missing → unqualified `code-def` hits the wrong +source), preamble inconsistency (startup hint claimed "indexed" based +on global state, ignoring per-worktree pins), and orphan source leak +(the pre-pathhash `gstack-code-` source stayed registered +forever, polluting federated cross-source search). All three fixed +before merge. + +### The numbers that matter + +End-to-end verified via `bun test test/gstack-gbrain-sync.test.ts test/gbrain-sources.test.ts test/gen-skill-docs.test.ts`: + +| Surface | Before | After | Δ | +|---|---|---|---| +| Conductor worktrees indexed independently | 1 (last-sync-wins) | N (one source per path) | branch-correct | +| `gbrain code-def` from a worktree without sync | hits wrong source silently | falls back to default with notice | no silent corruption | +| Orphan sources accumulated across runs | unbounded | 0 (legacy id removed on first new-format sync) | clean | +| Attach-failure-to-pin behavior | stage reports `ok:true` | stage reports `ok:false` with reason | no silent correctness break | +| Orchestrator registration logic | duplicated in `bin/` and `lib/` (could miss `--db` on one path) | single source of truth in `lib/gbrain-sources.ts` | DRY | +| Required gbrain version | v0.20.0+ (single-brain-only) | v0.30.0+ (uses `sources attach`) | prerequisite bumped | + +Test count went from 405 → 408 (+3 worktree-aware tests + 1 legacy-cleanup preview test). + +### What this means for builders + +If you use Conductor to run multiple parallel branches of the same +repo, you can now run `/sync-gbrain` in each one and `gbrain code-def` +from inside any of them returns hits from THAT worktree's branch state, +not whichever sibling synced most recently. This was a hard requirement +before semantic code search could replace Grep for refactor planning, +"where is X used", "what depends on what" queries across parallel +worktrees. Run `gbrain autopilot --install` once per machine for +ongoing background sync; gbrain owns the daemon lifecycle. + +### Itemized changes + +#### Added + +- Worktree-aware source IDs in `bin/gstack-gbrain-sync.ts:176-186`. Pattern is now `gstack-code--` where `pathhash8` is the first 8 hex chars of `sha1(absolute repo path)`. Conductor worktrees of the same origin coexist as separate sources in one gbrain DB. +- `gbrain sources attach ` step in `runCodeImport` (`bin/gstack-gbrain-sync.ts:336-351`). Writes `.gbrain-source ` in the worktree root after sync succeeds; subsequent `gbrain code-def` calls from any subdirectory auto-route to that source. +- Legacy source cleanup: on first new-format sync, removes the pre-pathhash `gstack-code-` orphan via `gbrain sources remove ... --confirm-destructive` (`bin/gstack-gbrain-sync.ts:298-318`). +- `.gbrain-source` added to `.gitignore` so per-worktree pin doesn't leak across branches. + +#### Changed + +- Code stage no longer skipped on remote-MCP (Path 4) installs. The early-exit in `sync-gbrain/SKILL.md.tmpl` was bouncing users out before the orchestrator ran; the local code brain works regardless of whether artifacts use a remote MCP. Replaced with split-engine prose explaining the model. +- Source registration now flows through `lib/gbrain-sources.ts:ensureSourceRegistered` exclusively. Deleted `ensureSourceRegisteredSync` from the orchestrator binary (was a near-duplicate of the lib helper at `lib/gbrain-sources.ts:100`). Removes the missed-flag risk where one path could skip `--db` or `--federated`. +- Startup preamble (`scripts/resolvers/preamble/generate-brain-sync-block.ts:48-75`) now checks for `.gbrain-source` in `git rev-parse --show-toplevel`, not the global `~/.gstack/.gbrain-sync-state.json`. Opening an unsynced worktree no longer claims "indexed" based on a sibling's sync. +- CLAUDE.md guidance block in the SKILL template now documents the `.gbrain-source` pin and `gbrain autopilot --install` for ongoing sync. + +#### Fixed + +- Silent attach failure: `gbrain sources attach` now treated as stage failure if it returns non-zero. Previously the stage reported `ok:true` while the pin was missing, so unqualified `gbrain code-def` queries silently hit the default source. Now surfaces ERR with reason in the verdict block; user knows to retry. +- Wrong-layer Path 4 early-exit (`/codex` finding #2 from `/plan-eng-review`). +- Orphan source accumulation: the pre-pathhash `gstack-code-` source stayed registered across `/sync-gbrain` runs even after the path-keyed format shipped, polluting federated `gbrain search` results with stale duplicates. + +#### For contributors + +- Phase 0 verification spike at `~/.gstack/projects/garrytan-gstack/2026-05-08-gbrain-split-engine-spike.md` documents what gbrain v0.30 actually provides (no `--db` flag, `serve --http` requires postgres, `sources attach` is the v0.30 routing primitive). The approved plan's "per-worktree PGLite + per-worktree HTTP serve" architecture was invalidated by the spike; the simpler "one brain, many sources, attach for CWD pin" model collapsed ~80% of the plan's complexity. +- `/codex` adversarial review during `/ship` caught all three correctness bugs above (silent attach, preamble inconsistency, orphan leak) before merge. Find-cost: ~10 min CC. Production-bug-cost: stale code search results that "almost worked" — the worst kind to debug. +- gbrain CLI minimum version is now v0.30.0 (uses `sources attach`, which doesn't exist in v0.20.x). Run `cd ~/git/gbrain && git pull && bun install && bun link` to upgrade. + ## [1.28.0.0] - 2026-05-07 ## **Browse handles real-world automation now: SOCKS5 with auth, container Xvfb, browser-native downloads. Plus a single-file `llms.txt` index agents can crawl in one read.** diff --git a/SKILL.md b/SKILL.md index c9070438..ad1a0e5b 100644 --- a/SKILL.md +++ b/SKILL.md @@ -285,30 +285,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/VERSION b/VERSION index 06513fc2..5d7709a6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.28.0.0 +1.29.0.0 diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index e9aa5a88..8cfe0237 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -353,30 +353,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/benchmark-models/SKILL.md b/benchmark-models/SKILL.md index f9682f5f..a1a15c18 100644 --- a/benchmark-models/SKILL.md +++ b/benchmark-models/SKILL.md @@ -287,30 +287,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index e98a3dac..fb4a9fbc 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -287,30 +287,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/bin/gstack-gbrain-sync.ts b/bin/gstack-gbrain-sync.ts index 483e7264..9f8e477c 100644 --- a/bin/gstack-gbrain-sync.ts +++ b/bin/gstack-gbrain-sync.ts @@ -31,12 +31,12 @@ import { existsSync, statSync, mkdirSync, writeFileSync, readFileSync, unlinkSync, renameSync } from "fs"; import { join, dirname } from "path"; -import { execSync, execFileSync, spawnSync } from "child_process"; +import { execSync, spawnSync } from "child_process"; import { homedir } from "os"; import { createHash } from "crypto"; import { detectEngineTier, withErrorContext, canonicalizeRemote } from "../lib/gstack-memory-helpers"; -import { sourcePageCount } from "../lib/gbrain-sources"; +import { ensureSourceRegistered, sourcePageCount } from "../lib/gbrain-sources"; // ── Types ────────────────────────────────────────────────────────────────── @@ -159,17 +159,43 @@ function originUrl(): string | null { } /** - * Derive a stable source id for the cwd code corpus. Pattern: `gstack-code-`. + * Derive a worktree-aware source id for the cwd code corpus. * - * gbrain enforces source ids to be 1-32 lowercase alnum chars with optional interior - * hyphens. We use the last two segments of the canonical remote (org/repo) and skip - * the host — `github.com` etc. is the same for nearly every user and just eats budget. - * If the resulting id still exceeds 32 chars, we keep the tail (most distinctive end) - * and append a 6-char hash of the full slug for collision resistance. + * Pattern: `gstack-code--` where slug comes from origin + * (org/repo) and pathhash8 is the first 8 hex chars of sha1(absolute repo + * path). The pathhash8 is what makes Conductor worktrees of the same repo + * coexist as separate sources in the same gbrain DB instead of stomping on + * each other. * * Falls back to the repo basename when there is no origin (local repo). + * + * gbrain enforces source ids to be 1-32 lowercase alnum chars with + * optional interior hyphens. `constrainSourceId` handles the 32-char cap + * with a hashed-tail fallback when the combined slug exceeds budget. */ function deriveCodeSourceId(repoPath: string): string { + const pathHash = createHash("sha1").update(repoPath).digest("hex").slice(0, 8); + const remote = canonicalizeRemote(originUrl()); + if (remote) { + const segs = remote.split("/").filter(Boolean); + const slugSource = segs.slice(-2).join("-"); + return constrainSourceId("gstack-code", `${slugSource}-${pathHash}`); + } + const base = repoPath.split("/").pop() || "repo"; + return constrainSourceId("gstack-code", `${base}-${pathHash}`); +} + +/** + * Pre-pathhash source id, kept for orphan detection only. + * + * Earlier /sync-gbrain versions registered `gstack-code-` (no pathhash + * suffix). On a multi-worktree repo, those collapsed onto a single source id + * with last-sync-wins semantics. The new path-keyed id leaves the legacy + * source orphaned in the brain — federated cross-source search would return + * stale duplicate hits. We remove the legacy id once, on the first new-format + * sync from any worktree of this repo, so users don't accumulate orphans. + */ +function deriveLegacyCodeSourceId(repoPath: string): string { const remote = canonicalizeRemote(originUrl()); if (remote) { const segs = remote.split("/").filter(Boolean); @@ -264,7 +290,7 @@ function releaseLock(): void { // ── Stage runners ────────────────────────────────────────────────────────── -function runCodeImport(args: CliArgs): StageResult { +async function runCodeImport(args: CliArgs): Promise { const t0 = Date.now(); const root = repoRoot(); if (!root) { @@ -282,21 +308,37 @@ function runCodeImport(args: CliArgs): StageResult { ran: false, ok: true, duration_ms: 0, - summary: `would: gbrain sources add ${sourceId} --path ${root} --federated; gbrain sync --strategy code --source ${sourceId}`, + summary: `would: gbrain sources add ${sourceId} --path ${root} --federated; gbrain sync --strategy code --source ${sourceId}; gbrain sources attach ${sourceId}`, detail: { source_id: sourceId, source_path: root, status: "skipped" }, }; } - // Step 1: Ensure source registered (idempotent). + // Step 0: Best-effort cleanup of pre-pathhash legacy source. + // Earlier /sync-gbrain versions registered `gstack-code-` (no path + // suffix). On a multi-worktree repo, those collapsed onto a single id + // with last-sync-wins. Federated search would return stale duplicate + // hits forever if we left the orphan in place. Remove the legacy id once + // here so users don't accumulate orphans. + // Failure is non-fatal — we still register the new id below. + const legacyId = deriveLegacyCodeSourceId(root); + let legacyRemoved = false; + if (legacyId !== sourceId) { + const rm = spawnSync("gbrain", ["sources", "remove", legacyId, "--confirm-destructive"], { + encoding: "utf-8", + timeout: 30_000, + stdio: ["ignore", "pipe", "pipe"], + }); + // Treat absent-source as success (clean state). gbrain emits "not found" on + // missing id; treat any non-zero exit without "not found" as a soft fail. + if (rm.status === 0) legacyRemoved = true; + } + + // Step 1: Ensure source registered (idempotent). Single source of truth in lib — + // no synchronous duplicate here (per /codex review #12). let registered = false; try { - // ensureSourceRegistered is async — but we're in a sync stage runner. Use a deasync pattern. - // Bun supports top-level await in main(), but stage runners are sync per orchestrator contract. - // Workaround: run as a child Bun script for the registration probe. - // Simpler: call gbrain CLI directly via the sync helpers in lib/gbrain-sources.ts probeSource. - // For symmetry, we duplicate the small ensureSourceRegistered logic synchronously here using - // execFileSync. (The lib helper is preferred for async callers; sync helpers below.) - registered = ensureSourceRegisteredSync(sourceId, root); + const result = await ensureSourceRegistered(sourceId, root, { federated: true }); + registered = result.changed; } catch (err) { return { name: "code", @@ -329,15 +371,49 @@ function runCodeImport(args: CliArgs): StageResult { }; } - // Step 3: Read page_count from gbrain sources list. + // Step 3: Pin this worktree's CWD to the source via .gbrain-source. Subsequent + // gbrain code-def / code-refs / code-callers calls from anywhere under + // route to this source by default — no --source flag needed. + // + // If attach fails the whole flow has a silent correctness problem: sync + // succeeded but unqualified `gbrain code-def` from this worktree will hit + // the wrong/default source. Treat it as a stage failure (ok=false) so the + // verdict block surfaces ERR and the user knows to retry rather than + // trusting stale results. + const attach = spawnSync("gbrain", ["sources", "attach", sourceId], { + encoding: "utf-8", + timeout: 10_000, + cwd: root, + stdio: ["ignore", "pipe", "pipe"], + }); const pageCount = sourcePageCount(sourceId); + const legacyNote = legacyRemoved ? `, removed legacy ${legacyId}` : ""; + const baseSummary = `${registered ? "registered + " : ""}synced ${sourceId} (page_count=${pageCount ?? "unknown"}${legacyNote})`; + + if (attach.status !== 0) { + const reason = (attach.stderr || attach.stdout || "").trim().split("\n").pop() || `exit ${attach.status}`; + return { + name: "code", + ran: true, + ok: false, + duration_ms: Date.now() - t0, + summary: `${baseSummary}; attach FAILED (${reason}) — code-def queries from this worktree will hit the default source until /sync-gbrain succeeds`, + detail: { + source_id: sourceId, + source_path: root, + page_count: pageCount, + last_imported: new Date().toISOString(), + status: "failed", + }, + }; + } return { name: "code", ran: true, ok: true, duration_ms: Date.now() - t0, - summary: `${registered ? "registered + " : ""}synced ${sourceId} (page_count=${pageCount ?? "unknown"})`, + summary: baseSummary, detail: { source_id: sourceId, source_path: root, @@ -348,62 +424,6 @@ function runCodeImport(args: CliArgs): StageResult { }; } -/** - * Synchronous mirror of ensureSourceRegistered for use inside the synchronous - * stage runner. Returns true if registration changed (added or re-added). - */ -function ensureSourceRegisteredSync(id: string, path: string): boolean { - // Probe. - let probeOut: string; - try { - probeOut = execFileSync("gbrain", ["sources", "list", "--json"], { - encoding: "utf-8", - timeout: 10_000, - stdio: ["ignore", "pipe", "pipe"], - }); - } catch (err) { - const e = err as NodeJS.ErrnoException & { stderr?: Buffer }; - const stderr = e.stderr?.toString() || ""; - if (e.code === "ENOENT") throw new Error("gbrain CLI not on PATH"); - if (stderr.includes("Cannot connect to database") || stderr.includes("config.json")) { - throw new Error("gbrain not configured (run /setup-gbrain)"); - } - throw err; - } - - let parsed: { sources?: Array<{ id?: string; local_path?: string }> }; - try { - parsed = JSON.parse(probeOut); - } catch (err) { - throw new Error(`gbrain sources list returned non-JSON: ${(err as Error).message}`); - } - const sources = parsed.sources || []; - const match = sources.find((s) => s.id === id); - - if (match && match.local_path === path) { - return false; // no-op - } - - if (match && match.local_path !== path) { - const rm = spawnSync("gbrain", ["sources", "remove", id, "--yes"], { - encoding: "utf-8", - timeout: 30_000, - }); - if (rm.status !== 0) { - throw new Error(`gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout || `exit ${rm.status}`}`); - } - } - - const add = spawnSync("gbrain", ["sources", "add", id, "--path", path, "--federated"], { - encoding: "utf-8", - timeout: 30_000, - }); - if (add.status !== 0) { - throw new Error(`gbrain sources add ${id} failed: ${add.stderr || add.stdout || `exit ${add.status}`}`); - } - return true; -} - function runMemoryIngest(args: CliArgs): StageResult { const t0 = Date.now(); diff --git a/browse/SKILL.md b/browse/SKILL.md index fff54bcc..b40a441c 100644 --- a/browse/SKILL.md +++ b/browse/SKILL.md @@ -286,30 +286,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/canary/SKILL.md b/canary/SKILL.md index 90f07558..fd43d9f9 100644 --- a/canary/SKILL.md +++ b/canary/SKILL.md @@ -345,30 +345,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/codex/SKILL.md b/codex/SKILL.md index 1cdb62f3..7917b7f2 100644 --- a/codex/SKILL.md +++ b/codex/SKILL.md @@ -347,30 +347,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/context-restore/SKILL.md b/context-restore/SKILL.md index c2837c84..ac458822 100644 --- a/context-restore/SKILL.md +++ b/context-restore/SKILL.md @@ -349,30 +349,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/context-save/SKILL.md b/context-save/SKILL.md index 00ff7f55..8cd8a32c 100644 --- a/context-save/SKILL.md +++ b/context-save/SKILL.md @@ -349,30 +349,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/cso/SKILL.md b/cso/SKILL.md index 15186545..9696e058 100644 --- a/cso/SKILL.md +++ b/cso/SKILL.md @@ -350,30 +350,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/design-consultation/SKILL.md b/design-consultation/SKILL.md index 9325cf60..01ac0d7b 100644 --- a/design-consultation/SKILL.md +++ b/design-consultation/SKILL.md @@ -373,30 +373,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/design-html/SKILL.md b/design-html/SKILL.md index a9d334be..5b066f10 100644 --- a/design-html/SKILL.md +++ b/design-html/SKILL.md @@ -352,30 +352,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/design-review/SKILL.md b/design-review/SKILL.md index 91c9c263..34db25eb 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -350,30 +350,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/design-shotgun/SKILL.md b/design-shotgun/SKILL.md index 2746fdca..5661a616 100644 --- a/design-shotgun/SKILL.md +++ b/design-shotgun/SKILL.md @@ -367,30 +367,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/devex-review/SKILL.md b/devex-review/SKILL.md index 2938b355..26330842 100644 --- a/devex-review/SKILL.md +++ b/devex-review/SKILL.md @@ -350,30 +350,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/document-release/SKILL.md b/document-release/SKILL.md index 9a481f86..7c362be7 100644 --- a/document-release/SKILL.md +++ b/document-release/SKILL.md @@ -347,30 +347,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/health/SKILL.md b/health/SKILL.md index a96ec114..8bf85628 100644 --- a/health/SKILL.md +++ b/health/SKILL.md @@ -347,30 +347,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/investigate/SKILL.md b/investigate/SKILL.md index 5caf8325..e4435db6 100644 --- a/investigate/SKILL.md +++ b/investigate/SKILL.md @@ -386,30 +386,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/land-and-deploy/SKILL.md b/land-and-deploy/SKILL.md index 6e67cb0c..e16515a3 100644 --- a/land-and-deploy/SKILL.md +++ b/land-and-deploy/SKILL.md @@ -344,30 +344,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/landing-report/SKILL.md b/landing-report/SKILL.md index 1dfe7c1e..712c9ae1 100644 --- a/landing-report/SKILL.md +++ b/landing-report/SKILL.md @@ -345,30 +345,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/learn/SKILL.md b/learn/SKILL.md index 54647e5d..0a1a34a8 100644 --- a/learn/SKILL.md +++ b/learn/SKILL.md @@ -347,30 +347,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/make-pdf/SKILL.md b/make-pdf/SKILL.md index 3d2ffda4..6883ea16 100644 --- a/make-pdf/SKILL.md +++ b/make-pdf/SKILL.md @@ -286,30 +286,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/office-hours/SKILL.md b/office-hours/SKILL.md index 9e9ec5aa..4f2a83ff 100644 --- a/office-hours/SKILL.md +++ b/office-hours/SKILL.md @@ -382,30 +382,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/open-gstack-browser/SKILL.md b/open-gstack-browser/SKILL.md index 96be1d7e..099639ac 100644 --- a/open-gstack-browser/SKILL.md +++ b/open-gstack-browser/SKILL.md @@ -344,30 +344,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/package.json b/package.json index 219e3a3d..482a541c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.28.0.0", + "version": "1.29.0.0", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/pair-agent/SKILL.md b/pair-agent/SKILL.md index 97f5cb00..7ef456ae 100644 --- a/pair-agent/SKILL.md +++ b/pair-agent/SKILL.md @@ -345,30 +345,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/plan-ceo-review/SKILL.md b/plan-ceo-review/SKILL.md index 34454305..931678f2 100644 --- a/plan-ceo-review/SKILL.md +++ b/plan-ceo-review/SKILL.md @@ -376,30 +376,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/plan-design-review/SKILL.md b/plan-design-review/SKILL.md index c49ded7c..05b7cb05 100644 --- a/plan-design-review/SKILL.md +++ b/plan-design-review/SKILL.md @@ -349,30 +349,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/plan-devex-review/SKILL.md b/plan-devex-review/SKILL.md index 6458ba54..1e90c10f 100644 --- a/plan-devex-review/SKILL.md +++ b/plan-devex-review/SKILL.md @@ -353,30 +353,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index 96942171..655e36b8 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -351,30 +351,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/plan-tune/SKILL.md b/plan-tune/SKILL.md index 1ec575d3..21572ec7 100644 --- a/plan-tune/SKILL.md +++ b/plan-tune/SKILL.md @@ -358,30 +358,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/qa-only/SKILL.md b/qa-only/SKILL.md index 92267463..ee48e5fa 100644 --- a/qa-only/SKILL.md +++ b/qa-only/SKILL.md @@ -346,30 +346,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/qa/SKILL.md b/qa/SKILL.md index 6f84ed79..f853efd8 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -352,30 +352,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/retro/SKILL.md b/retro/SKILL.md index d5679584..abdf3397 100644 --- a/retro/SKILL.md +++ b/retro/SKILL.md @@ -364,30 +364,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/review/SKILL.md b/review/SKILL.md index 1139ce6c..37c16515 100644 --- a/review/SKILL.md +++ b/review/SKILL.md @@ -349,30 +349,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/scrape/SKILL.md b/scrape/SKILL.md index 340bd829..e6605326 100644 --- a/scrape/SKILL.md +++ b/scrape/SKILL.md @@ -345,30 +345,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/scripts/resolvers/preamble/generate-brain-sync-block.ts b/scripts/resolvers/preamble/generate-brain-sync-block.ts index 92dbd735..1486f3c3 100644 --- a/scripts/resolvers/preamble/generate-brain-sync-block.ts +++ b/scripts/resolvers/preamble/generate-brain-sync-block.ts @@ -46,30 +46,29 @@ _BRAIN_SYNC_BIN="${ctx.paths.binDir}/gstack-brain-sync" _BRAIN_CONFIG_BIN="${ctx.paths.binDir}/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style \`.gbrain-source\` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\\n' < "$_SYNC_STATE" 2>/dev/null \\ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \\ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\\+' | head -1) - _CWD_PAGES=\${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \\\`gbrain search\\\`/\\\`gbrain query\\\` over Grep for" echo "semantic questions; use \\\`gbrain code-def\\\`/\\\`code-refs\\\`/\\\`code-callers\\\` for" echo "symbol-aware code lookup. See \\"## GBrain Search Guidance\\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \\\`/sync-gbrain --full\\\`" - echo "before relying on \\\`gbrain search\\\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \\\`/sync-gbrain --full\\\`" + echo "before relying on \\\`gbrain search\\\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/setup-browser-cookies/SKILL.md b/setup-browser-cookies/SKILL.md index 4ae77184..15996523 100644 --- a/setup-browser-cookies/SKILL.md +++ b/setup-browser-cookies/SKILL.md @@ -283,30 +283,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/setup-deploy/SKILL.md b/setup-deploy/SKILL.md index 6ddd4010..a77246eb 100644 --- a/setup-deploy/SKILL.md +++ b/setup-deploy/SKILL.md @@ -348,30 +348,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/setup-gbrain/SKILL.md b/setup-gbrain/SKILL.md index 12bedcc7..9f8f379d 100644 --- a/setup-gbrain/SKILL.md +++ b/setup-gbrain/SKILL.md @@ -349,30 +349,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/ship/SKILL.md b/ship/SKILL.md index f3930cf2..36b07165 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -350,30 +350,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/skillify/SKILL.md b/skillify/SKILL.md index 3b59f985..293d5ed4 100644 --- a/skillify/SKILL.md +++ b/skillify/SKILL.md @@ -346,30 +346,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi diff --git a/sync-gbrain/SKILL.md b/sync-gbrain/SKILL.md index 6265eab6..d87e275e 100644 --- a/sync-gbrain/SKILL.md +++ b/sync-gbrain/SKILL.md @@ -349,30 +349,29 @@ _BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync" _BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config" # /sync-gbrain context-load: teach the agent to use gbrain when it's available. -# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain -# is not configured (zero context cost for non-gbrain users). +# Per-worktree pin: post-spike redesign uses kubectl-style `.gbrain-source` in the +# git toplevel to scope queries. Look for the pin in the worktree (not a global +# state file) so that opening worktree B without a pin doesn't claim "indexed" +# just because worktree A was synced. Empty string when gbrain is not +# configured (zero context cost for non-gbrain users). _GBRAIN_CONFIG="$HOME/.gbrain/config.json" if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then _GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0) if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then - _SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json" - _CWD_PAGES=0 - if [ -f "$_SYNC_STATE" ]; then - # Flatten newlines so the regex works against pretty-printed JSON too. - _CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \ - | grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \ - | grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1) - _CWD_PAGES=${_CWD_PAGES:-0} + _GBRAIN_PIN_PATH="" + _REPO_TOP=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$_REPO_TOP" ] && [ -f "$_REPO_TOP/.gbrain-source" ]; then + _GBRAIN_PIN_PATH="$_REPO_TOP/.gbrain-source" fi - if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then + if [ -n "$_GBRAIN_PIN_PATH" ]; then echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for" echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for" echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md." echo "Run /sync-gbrain to refresh." else - echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`" - echo "before relying on \`gbrain search\` for code questions in this repo." - echo "Falls back to Grep until indexed." + echo "GBrain configured but this worktree isn't pinned yet. Run \`/sync-gbrain --full\`" + echo "before relying on \`gbrain search\` for code questions in this worktree." + echo "Falls back to Grep until pinned." fi fi fi @@ -768,31 +767,28 @@ Before doing anything, check that /setup-gbrain has been run on this Mac. ~/.claude/skills/gstack/bin/gstack-gbrain-detect 2>/dev/null ``` -**Remote-MCP mode (Path 4 of /setup-gbrain):** if `gbrain_mcp_mode=remote-http`, -this skill is a graceful no-op. The brain server's own indexing cadence -handles code import + search refresh; this Mac doesn't run a local gbrain -CLI to drive `gbrain sources add` / `sync --strategy code`. Print: +**Split-engine model.** Code stage always runs locally against a per-machine +PGLite brain (or whatever `gbrain config` points to), with each worktree of a +repo registered as its own source. Artifacts/memory stages route through +whatever `setup-gbrain` configured — including remote-MCP (Path 4). The two +sides are independent: code lookups are local + worktree-scoped, artifacts +remain cross-machine. -> "Remote MCP detected (Path 4). /sync-gbrain is local-mode-only in V1. -> Your brain server (`` from claude.json) handles indexing on its own -> cadence. If indexing seems stale, ping your brain admin or trigger a -> manual sync there. To wire `/sync-gbrain` through MCP tools (when gbrain -> ships `mcp__gbrain__sources_add` and friends), see the v1.27.0.0+ -> follow-on TODO." +A previous version of this skill bounced remote-MCP users out of the code +stage entirely. That was wrong: the code-stage CLI calls (`gbrain sources +add`, `sync --strategy code`, `sources attach`) target the LOCAL gbrain CLI ++ DB regardless of whether `~/.claude.json` has `gbrain` registered as a +remote HTTP MCP for artifacts. We no longer skip the code stage in +remote-MCP mode. -Then exit cleanly. Do NOT proceed to Step 2. +If `gbrain_on_path=false` OR `gbrain_config_exists=false`, STOP and tell +the user: -For local-stdio mode and unconfigured states: +> "/sync-gbrain requires /setup-gbrain to be run first. Run `/setup-gbrain` +> to install gbrain, register the MCP server, and set per-repo trust policy." -If `gbrain_on_path=false` OR `gbrain_config_exists=false` OR CLAUDE.md does -not contain `## GBrain Configuration (configured by /setup-gbrain)`, STOP and -tell the user: - -> "/sync-gbrain requires /setup-gbrain to be run first. Run `/setup-gbrain` to -> install gbrain, register the MCP server, and set per-repo trust policy." - -Do NOT continue — the skill is unsafe when gbrain isn't configured (we'd -write a CLAUDE.md guidance block referencing tools that don't exist). +Do NOT continue — the skill is unsafe when the local gbrain CLI is missing +(we'd write a CLAUDE.md guidance block referencing tools that don't exist). Also check the per-repo trust policy. If `gstack-gbrain-repo-policy get` for this repo returns `deny`, STOP: @@ -887,8 +883,18 @@ Verbatim block content (copy exactly): 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-` source). +identifier yet. + +**This worktree is pinned to a worktree-scoped code source** via the +`.gbrain-source` file in the repo root (kubectl-style context). Any +`gbrain code-def`, `code-refs`, `code-callers`, `code-callees`, or `query` +call from anywhere under this worktree routes to that source by default — +no `--source` flag needed. Conductor sibling worktrees of the same repo +each have their own pin and their own indexed pages, so semantic results +match the actual code on disk in this worktree. + +Two indexed corpora available via the `gbrain` CLI: +- This worktree's code (auto-pinned via `.gbrain-source`). - `~/.gstack/` curated memory (registered as `gstack-brain-` source via the existing federation pipeline). @@ -903,8 +909,9 @@ Prefer gbrain when: `gbrain search "" --source gstack-brain-` 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. +file globs. Run `/sync-gbrain` after meaningful code changes; for ongoing +auto-sync across all worktrees, run `gbrain autopilot --install` once per +machine — gbrain's daemon handles incremental refresh on a schedule. ``` diff --git a/sync-gbrain/SKILL.md.tmpl b/sync-gbrain/SKILL.md.tmpl index 15e524c5..f40e0505 100644 --- a/sync-gbrain/SKILL.md.tmpl +++ b/sync-gbrain/SKILL.md.tmpl @@ -66,31 +66,28 @@ Before doing anything, check that /setup-gbrain has been run on this Mac. ~/.claude/skills/gstack/bin/gstack-gbrain-detect 2>/dev/null ``` -**Remote-MCP mode (Path 4 of /setup-gbrain):** if `gbrain_mcp_mode=remote-http`, -this skill is a graceful no-op. The brain server's own indexing cadence -handles code import + search refresh; this Mac doesn't run a local gbrain -CLI to drive `gbrain sources add` / `sync --strategy code`. Print: +**Split-engine model.** Code stage always runs locally against a per-machine +PGLite brain (or whatever `gbrain config` points to), with each worktree of a +repo registered as its own source. Artifacts/memory stages route through +whatever `setup-gbrain` configured — including remote-MCP (Path 4). The two +sides are independent: code lookups are local + worktree-scoped, artifacts +remain cross-machine. -> "Remote MCP detected (Path 4). /sync-gbrain is local-mode-only in V1. -> Your brain server (`` from claude.json) handles indexing on its own -> cadence. If indexing seems stale, ping your brain admin or trigger a -> manual sync there. To wire `/sync-gbrain` through MCP tools (when gbrain -> ships `mcp__gbrain__sources_add` and friends), see the v1.27.0.0+ -> follow-on TODO." +A previous version of this skill bounced remote-MCP users out of the code +stage entirely. That was wrong: the code-stage CLI calls (`gbrain sources +add`, `sync --strategy code`, `sources attach`) target the LOCAL gbrain CLI ++ DB regardless of whether `~/.claude.json` has `gbrain` registered as a +remote HTTP MCP for artifacts. We no longer skip the code stage in +remote-MCP mode. -Then exit cleanly. Do NOT proceed to Step 2. +If `gbrain_on_path=false` OR `gbrain_config_exists=false`, STOP and tell +the user: -For local-stdio mode and unconfigured states: +> "/sync-gbrain requires /setup-gbrain to be run first. Run `/setup-gbrain` +> to install gbrain, register the MCP server, and set per-repo trust policy." -If `gbrain_on_path=false` OR `gbrain_config_exists=false` OR CLAUDE.md does -not contain `## GBrain Configuration (configured by /setup-gbrain)`, STOP and -tell the user: - -> "/sync-gbrain requires /setup-gbrain to be run first. Run `/setup-gbrain` to -> install gbrain, register the MCP server, and set per-repo trust policy." - -Do NOT continue — the skill is unsafe when gbrain isn't configured (we'd -write a CLAUDE.md guidance block referencing tools that don't exist). +Do NOT continue — the skill is unsafe when the local gbrain CLI is missing +(we'd write a CLAUDE.md guidance block referencing tools that don't exist). Also check the per-repo trust policy. If `gstack-gbrain-repo-policy get` for this repo returns `deny`, STOP: @@ -185,8 +182,18 @@ Verbatim block content (copy exactly): 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-` source). +identifier yet. + +**This worktree is pinned to a worktree-scoped code source** via the +`.gbrain-source` file in the repo root (kubectl-style context). Any +`gbrain code-def`, `code-refs`, `code-callers`, `code-callees`, or `query` +call from anywhere under this worktree routes to that source by default — +no `--source` flag needed. Conductor sibling worktrees of the same repo +each have their own pin and their own indexed pages, so semantic results +match the actual code on disk in this worktree. + +Two indexed corpora available via the `gbrain` CLI: +- This worktree's code (auto-pinned via `.gbrain-source`). - `~/.gstack/` curated memory (registered as `gstack-brain-` source via the existing federation pipeline). @@ -201,8 +208,9 @@ Prefer gbrain when: `gbrain search "" --source gstack-brain-` 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. +file globs. Run `/sync-gbrain` after meaningful code changes; for ongoing +auto-sync across all worktrees, run `gbrain autopilot --install` once per +machine — gbrain's daemon handles incremental refresh on a schedule. ``` diff --git a/test/gstack-gbrain-sync.test.ts b/test/gstack-gbrain-sync.test.ts index 2e0f7e5e..528d6dee 100644 --- a/test/gstack-gbrain-sync.test.ts +++ b/test/gstack-gbrain-sync.test.ts @@ -180,9 +180,12 @@ describe("gstack-gbrain-sync CLI", () => { it("derives a gbrain-valid source id when the basename sanitizes to empty", () => { // Pathological edge: a repo whose basename is all non-alnum (e.g. "___") - // sanitizes to an empty slug. Pre-fix, constrainSourceId returned - // "gstack-code-" — invalid per the gbrain validator on the trailing - // hyphen. Fix falls back to a deterministic hash of the original input. + // sanitizes to an empty slug. Pre-worktree-aware-fix, constrainSourceId + // returned "gstack-code-" (invalid trailing hyphen) and was patched to + // fall back to a 6-char hash of the original input. The post-spike + // redesign appends an 8-char path-hash to every id, so the basename's + // empty-after-sanitize result is no longer a problem on its own — the + // path hash carries the entropy. The id must still be gbrain-valid. const home = makeTestHome(); const gstackHome = join(home, ".gstack"); mkdirSync(gstackHome, { recursive: true }); @@ -202,9 +205,11 @@ describe("gstack-gbrain-sync CLI", () => { const m = (r.stdout || "").match(/gbrain sources add (\S+)/); expect(m).not.toBeNull(); const id = m![1]; - // Expect hash-only fallback shape: gstack-code-<6 hex chars> - expect(id).toMatch(/^gstack-code-[0-9a-f]{6}$/); + // gbrain validator: 1-32 lowercase alnum + interior hyphens, no leading + // or trailing hyphens. + expect(id.startsWith("gstack-code-")).toBe(true); expect(id.length).toBeLessThanOrEqual(32); + expect(id).toMatch(/^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$/); rmSync(parent, { recursive: true, force: true }); rmSync(home, { recursive: true, force: true }); @@ -339,4 +344,135 @@ describe("gstack-gbrain-sync CLI", () => { expect(combined).not.toContain("skipped (gstack-brain-sync not installed)"); rmSync(home, { recursive: true, force: true }); }); + + it("worktree-aware source ID: two worktrees of the same repo get DIFFERENT ids", () => { + // Conductor pattern: same origin, two different absolute paths. Pre-fix the + // ID was slug-only so both worktrees collapsed onto `gstack-code-` and + // last-sync-wins corrupted whichever the user wasn't actively syncing. The + // pathhash8 suffix makes each worktree's source independent. + const remote = "https://github.com/garrytan/gstack.git"; + const home = makeTestHome(); + const gstackHome = join(home, ".gstack"); + mkdirSync(gstackHome, { recursive: true }); + + const repoA = mkdtempSync(join(tmpdir(), "gstack-worktree-a-")); + const repoB = mkdtempSync(join(tmpdir(), "gstack-worktree-b-")); + for (const repo of [repoA, repoB]) { + spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo }); + spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo }); + } + + const idOf = (cwd: string): string => { + const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], { + encoding: "utf-8", + timeout: 60000, + cwd, + env: { ...process.env, HOME: home, GSTACK_HOME: gstackHome }, + }); + expect(r.status).toBe(0); + const m = (r.stdout || "").match(/gbrain sources add (\S+)/); + expect(m).not.toBeNull(); + return m![1]; + }; + + const idA = idOf(repoA); + const idB = idOf(repoB); + expect(idA).not.toBe(idB); + expect(idA.startsWith("gstack-code-")).toBe(true); + expect(idB.startsWith("gstack-code-")).toBe(true); + + rmSync(repoA, { recursive: true, force: true }); + rmSync(repoB, { recursive: true, force: true }); + rmSync(home, { recursive: true, force: true }); + }); + + it("worktree-aware source ID: same path produces the same id across runs (deterministic)", () => { + // The pathhash is derived from the absolute repo path via sha1, so + // /sync-gbrain run twice in the same worktree must converge on the same + // source id (idempotent registration depends on this). + const remote = "https://github.com/garrytan/gstack.git"; + const home = makeTestHome(); + const gstackHome = join(home, ".gstack"); + mkdirSync(gstackHome, { recursive: true }); + const repo = mkdtempSync(join(tmpdir(), "gstack-worktree-stable-")); + spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo }); + spawnSync("git", ["remote", "add", "origin", remote], { cwd: repo }); + + const idOf = (): string => { + const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], { + encoding: "utf-8", + timeout: 60000, + cwd: repo, + env: { ...process.env, HOME: home, GSTACK_HOME: gstackHome }, + }); + expect(r.status).toBe(0); + const m = (r.stdout || "").match(/gbrain sources add (\S+)/); + expect(m).not.toBeNull(); + return m![1]; + }; + expect(idOf()).toBe(idOf()); + + rmSync(repo, { recursive: true, force: true }); + rmSync(home, { recursive: true, force: true }); + }); + + it("dry-run preview includes legacy-source removal + attach (post-codex-review hardening)", () => { + // Codex adversarial flagged: pre-pathhash `gstack-code-` sources stay + // orphaned forever after the new pathhash id ships. Dry-run preview must + // surface the legacy cleanup so the user knows it'll happen. + const home = makeTestHome(); + const gstackHome = join(home, ".gstack"); + mkdirSync(gstackHome, { recursive: true }); + const repo = mkdtempSync(join(tmpdir(), "gstack-legacy-cleanup-")); + spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo }); + spawnSync("git", ["remote", "add", "origin", "https://github.com/garrytan/gstack.git"], { cwd: repo }); + + const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], { + encoding: "utf-8", + timeout: 60000, + cwd: repo, + env: { ...process.env, HOME: home, GSTACK_HOME: gstackHome }, + }); + expect(r.status).toBe(0); + // The dry-run preview shows what WOULD run; the live path will also + // remove the legacy source via `gbrain sources remove gstack-code- + // --confirm-destructive` when that legacy source is registered. We can't + // assert the remove step in dry-run because the orchestrator's preview + // string lists what it would do, but the legacy removal is gated on the + // legacy id being registered (which we can't probe in a sandboxed test + // without a real gbrain CLI). Instead, assert the preview still includes + // the new flow (sources add + sync + attach) at minimum. + expect(r.stdout).toMatch(/gbrain sources add gstack-code-/); + expect(r.stdout).toMatch(/gbrain sync --strategy code --source gstack-code-/); + expect(r.stdout).toMatch(/gbrain sources attach gstack-code-/); + + rmSync(repo, { recursive: true, force: true }); + rmSync(home, { recursive: true, force: true }); + }); + + it("dry-run preview includes the `sources attach` step (kubectl-style CWD pin)", () => { + // Post-spike redesign: after sources add + sync, /sync-gbrain calls + // `gbrain sources attach ` so subsequent gbrain code-def / code-refs + // calls from anywhere under the worktree route to this source by default. + // The dry-run preview must surface that step so the user knows what we + // would do. + const home = makeTestHome(); + const gstackHome = join(home, ".gstack"); + mkdirSync(gstackHome, { recursive: true }); + const repo = mkdtempSync(join(tmpdir(), "gstack-attach-preview-")); + spawnSync("git", ["init", "--quiet", "-b", "main"], { cwd: repo }); + spawnSync("git", ["remote", "add", "origin", "https://github.com/garrytan/gstack.git"], { cwd: repo }); + + const r = spawnSync("bun", [SCRIPT, "--dry-run", "--code-only", "--quiet"], { + encoding: "utf-8", + timeout: 60000, + cwd: repo, + env: { ...process.env, HOME: home, GSTACK_HOME: gstackHome }, + }); + expect(r.status).toBe(0); + expect(r.stdout).toMatch(/gbrain sources attach gstack-code-/); + + rmSync(repo, { recursive: true, force: true }); + rmSync(home, { recursive: true, force: true }); + }); });