From db9447c3339950bb0506c547e5b3225f5def3854 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 4 May 2026 09:29:48 -0700 Subject: [PATCH] v1.26.3.0 feat: /sync-gbrain skill + native code-surface orchestrator (#1314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: native gbrain code-surface orchestrator + ensureSourceRegistered helper Replaces gbrain import (markdown only) with gbrain sources add + sync --strategy code (or reindex-code on --full). Adds lib/gbrain-sources.ts exporting ensureSourceRegistered/probeSource/sourcePageCount, plus lock file + tmp-rename atomicity + dry-run write skip in the orchestrator. Co-Authored-By: Claude Opus 4.7 (1M context) * feat: setup-gbrain Step 8 writes ## GBrain Search Guidance after smoke test Extends Step 8 to write a machine-agnostic guidance block that teaches the agent when to prefer gbrain CLI (search/query/code-def/code-refs/ code-callers/code-callees) over Grep. Gated on smoke test pass. Co-Authored-By: Claude Opus 4.7 (1M context) * feat: /sync-gbrain skill — keep gbrain current and refresh agent guidance New top-level skill that wraps gstack-gbrain-sync with state probing, capability check (write+search round-trip, not gbrain doctor), CLAUDE.md guidance lifecycle (write iff healthy, remove iff broken), and a per-source verdict block. Re-runnable, idempotent. Co-Authored-By: Claude Opus 4.7 (1M context) * feat: preamble emits gbrain-availability block when capability ok Extends generate-brain-sync-block.ts to emit Variant A (steady-state, 4 lines) when cwd page_count > 0 or Variant B (empty-corpus emergency, 3 lines) when 0; empty string otherwise. Reads cached page_count from .gbrain-sync-state.json (handles pretty + compact JSON). Refreshes ship golden fixtures and bumps the plan-review preamble byte budget to 35K to absorb the new block. Co-Authored-By: Claude Opus 4.7 (1M context) * docs: register /sync-gbrain in AGENTS.md and docs/skills.md Co-Authored-By: Claude Opus 4.7 (1M context) * chore: regenerate SKILL.md across all hosts (gen:skill-docs) Mechanical regeneration after preamble + setup-gbrain template + new sync-gbrain skill. Run via: bun run gen:skill-docs --host all. Co-Authored-By: Claude Opus 4.7 (1M context) * chore: bump version and changelog (v1.26.3.0) Co-Authored-By: Claude Opus 4.7 (1M context) * docs: add /sync-gbrain to README skills table and gbrain section Co-Authored-By: Claude Opus 4.7 --------- Co-authored-by: Claude Opus 4.7 (1M context) --- AGENTS.md | 1 + CHANGELOG.md | 42 + README.md | 5 +- SKILL.md | 29 + VERSION | 2 +- autoplan/SKILL.md | 29 + benchmark-models/SKILL.md | 29 + benchmark/SKILL.md | 29 + bin/gstack-gbrain-sync.ts | 363 +++++-- browse/SKILL.md | 29 + canary/SKILL.md | 29 + codex/SKILL.md | 29 + context-restore/SKILL.md | 29 + context-save/SKILL.md | 29 + cso/SKILL.md | 29 + design-consultation/SKILL.md | 29 + design-html/SKILL.md | 29 + design-review/SKILL.md | 29 + design-shotgun/SKILL.md | 29 + devex-review/SKILL.md | 29 + docs/skills.md | 1 + document-release/SKILL.md | 29 + health/SKILL.md | 29 + investigate/SKILL.md | 29 + land-and-deploy/SKILL.md | 29 + landing-report/SKILL.md | 29 + learn/SKILL.md | 29 + lib/gbrain-sources.ts | 184 ++++ make-pdf/SKILL.md | 29 + office-hours/SKILL.md | 29 + open-gstack-browser/SKILL.md | 29 + package.json | 2 +- pair-agent/SKILL.md | 29 + plan-ceo-review/SKILL.md | 29 + plan-design-review/SKILL.md | 29 + plan-devex-review/SKILL.md | 29 + plan-eng-review/SKILL.md | 29 + plan-tune/SKILL.md | 29 + qa-only/SKILL.md | 29 + qa/SKILL.md | 29 + retro/SKILL.md | 29 + review/SKILL.md | 29 + scrape/SKILL.md | 29 + .../preamble/generate-brain-sync-block.ts | 32 + setup-browser-cookies/SKILL.md | 29 + setup-deploy/SKILL.md | 29 + setup-gbrain/SKILL.md | 73 ++ setup-gbrain/SKILL.md.tmpl | 44 + ship/SKILL.md | 29 + skillify/SKILL.md | 29 + sync-gbrain/SKILL.md | 945 ++++++++++++++++++ sync-gbrain/SKILL.md.tmpl | 267 +++++ test/fixtures/golden/claude-ship-SKILL.md | 29 + test/fixtures/golden/codex-ship-SKILL.md | 29 + test/fixtures/golden/factory-ship-SKILL.md | 29 + test/gbrain-sources.test.ts | 219 ++++ test/gen-skill-docs.test.ts | 4 +- test/gstack-gbrain-sync.test.ts | 84 +- test/skill-e2e-memory-pipeline.test.ts | 5 +- 59 files changed, 3412 insertions(+), 79 deletions(-) create mode 100644 lib/gbrain-sources.ts create mode 100644 sync-gbrain/SKILL.md create mode 100644 sync-gbrain/SKILL.md.tmpl create mode 100644 test/gbrain-sources.test.ts diff --git a/AGENTS.md b/AGENTS.md index bea9c7fd..c1e5595f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -63,6 +63,7 @@ Invoke them by name (e.g., `/office-hours`). | `/benchmark-models` | Cross-model benchmark for skills (Claude, GPT, Gemini side-by-side). | | `/cso` | OWASP Top 10 + STRIDE security audit. | | `/setup-gbrain` | Set up gbrain for cross-machine session memory sync. | +| `/sync-gbrain` | Keep gbrain current with this repo's code; refresh agent search guidance in CLAUDE.md. | ### Browser + agent integration diff --git a/CHANGELOG.md b/CHANGELOG.md index 3144aa4e..176bb55f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # Changelog +## [1.26.3.0] - 2026-05-03 + +## **`/sync-gbrain` keeps your brain current and teaches the agent when to use it.** + +Two functional gaps closed in one ship: the cwd repo wasn't actually being indexed by gbrain (the orchestrator called `gbrain import` which only handles markdown directories, not code), and the coding agent had no idea gbrain existed in any session that didn't explicitly opt in. Both fixed by switching to gbrain v0.20.0+'s native code surfaces and adding a CLAUDE.md guidance block that's gated on a working capability check. + +### What you can now do + +- **Run `/sync-gbrain` to refresh gbrain against this repo's code.** Default is `--incremental` (mtime fast-path, ~50ms). `--full` runs `gbrain reindex-code` for a full re-index. `--dry-run` previews what would sync without writing anywhere. `--code-only`, `--no-memory`, `--no-brain-sync`, `--quiet` all work. +- **Use `gbrain code-def`/`code-refs`/`code-callers`/`code-callees` against your repo.** /sync-gbrain registers the cwd as a federated source via `gbrain sources add` (idempotent — id is `gstack-code-`), then runs `gbrain sync --strategy code`. The native code surfaces just work afterward. +- **Get gbrain hints in every gstack skill preamble.** When gbrain is configured AND the cwd source has page_count > 0, every skill start emits a 4-line "prefer `gbrain search`/`code-def`/`code-refs` over Grep" hint. When configured but the corpus is empty, you get a 3-line emergency hint nudging you to run `/sync-gbrain --full`. When gbrain isn't configured, the hint resolves to empty string — zero context tax for non-gbrain users. +- **Find the long-form guidance in CLAUDE.md.** `/sync-gbrain` (and `/setup-gbrain` Step 8) write a `## GBrain Search Guidance` block delimited by HTML comments, with concrete CLI commands for semantic search, symbol-aware code lookup, and curated-memory queries. The block is removed automatically when the capability check fails, so a Mac with synced repo CLAUDE.md but no local gbrain doesn't end up telling the agent to use tools that don't exist. + +### What gets safer + +- **Concurrent /sync-gbrain runs from two terminals don't corrupt CLAUDE.md or `.gbrain-sync-state.json`.** Lock file at `~/.gstack/.sync-gbrain.lock` with PID + timestamp. Stale-lock takeover after 5 min. Both files written via tmp+atomic-rename. SIGINT/SIGTERM trap releases the lock. +- **`--dry-run` actually doesn't write anywhere.** Previously the orchestrator skipped only the `gbrain import` call; now it skips `sources add`, `sync --strategy code`, the state file, AND the CLAUDE.md guidance block. Print "would: ..." lines for every action. +- **The capability check is narrower than `gbrain doctor`.** Doctor exits "unhealthy" for unrelated reasons (`resolver_health` warnings, `minions_migration` partial-installs) on otherwise-functional brains. /sync-gbrain uses a write+search round-trip (`gbrain put $SLUG | gbrain search ping | grep $SLUG`) which actually tests what we care about: can the agent search. + +### Itemized changes + +#### Added +- New `lib/gbrain-sources.ts` — `ensureSourceRegistered(id, path, options)` + `probeSource(id, env)` + `sourcePageCount(id, env)` helpers. Production callers leave `env` unset (inherit `process.env`); tests pass a custom env to point at a fake `gbrain` on PATH. +- New `sync-gbrain/SKILL.md.tmpl` — top-level skill, ~250 lines. +- New `test/gbrain-sources.test.ts` — 9 unit tests with a fake gbrain shell script on PATH (jq-driven state file, no real DB needed). +- Lock-file primitives (`acquireLock` / `releaseLock`) in the orchestrator. +- New code-stage detail schema in `.gbrain-sync-state.json`: `last_stages.code.detail = {source_id, source_path, page_count, last_imported, status}`. + +#### Changed +- `bin/gstack-gbrain-sync.ts` `runCodeImport` rewritten to use `gbrain sources add` + `gbrain sync --strategy code` (incremental) or `gbrain reindex-code --yes` (`--full`) instead of `gbrain import`. State file written via tmp+rename for atomicity. +- `setup-gbrain/SKILL.md.tmpl` Step 8 now writes both `## GBrain Configuration` AND `## GBrain Search Guidance` blocks, gated on Step 9 smoke test pass. +- `scripts/resolvers/preamble/generate-brain-sync-block.ts` emits Variant A (4 lines, healthy) / Variant B (3 lines, empty corpus) / empty string (gbrain not configured). Reads cached cwd page_count from the state file (handles pretty + compact JSON via `tr -d '\n'` flatten). +- `test/gen-skill-docs.test.ts` plan-review preamble byte budget bumped 33000 → 35000 to absorb the new context-load block. +- `test/gstack-gbrain-sync.test.ts` updated for native code surfaces (12 tests, was 8) — adds source-id derivation, dry-run no-lock, stale-lock takeover, fresh-lock blocking. +- `test/skill-e2e-memory-pipeline.test.ts` updated to assert `would: gbrain sources add` instead of `would: gbrain import`. +- Ship golden fixtures (`test/fixtures/golden/{claude,codex,factory}-ship-SKILL.md`) refreshed. + +#### For contributors +- The 4-digit `MAJOR.MINOR.PATCH.MICRO` version in `package.json` and `VERSION` is the source of truth. +- Run `bun run gen:skill-docs --host all` after editing any `.tmpl` to regenerate per-host SKILL.md files; commit both. +- gbrain v0.25.1 already ships `gbrain sync --watch [--interval N]` and `gbrain sync --install-cron` natively. The previously-deferred V1.5 P0 daemon can wire through to those rather than building a gstack-side watcher. + ## [1.26.2.0] - 2026-05-03 ## **`/plan-eng-review` always asks. Never silently writes findings to your plan first.** diff --git a/README.md b/README.md index 426c8468..dcab7cf2 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,7 @@ Each skill feeds into the next. `/office-hours` writes a design doc that `/plan- | `/open-gstack-browser` | **GStack Browser** — launch GStack Browser with sidebar, anti-bot stealth, auto model routing (Sonnet for actions, Opus for analysis), one-click cookie import, and Claude Code integration. Clean up pages, take smart screenshots, edit CSS, and pass info back to your terminal. | | `/setup-deploy` | **Deploy Configurator** — one-time setup for `/land-and-deploy`. Detects your platform, production URL, and deploy commands. | | `/setup-gbrain` | **GBrain Onboarding** — from zero to running gbrain in under 5 minutes. PGLite local, Supabase existing URL, or auto-provision a new Supabase project via Management API. MCP registration for Claude Code + per-repo trust triad (read-write/read-only/deny). [Full guide](USING_GBRAIN_WITH_GSTACK.md). | +| `/sync-gbrain` | **Keep Brain Current** — re-index this repo's code into gbrain via `gbrain sources add` + `gbrain sync --strategy code`, refresh the `## GBrain Search Guidance` block in CLAUDE.md, and auto-remove guidance when the capability check fails. `--incremental` (default), `--full`, `--dry-run`. Idempotent; safe to re-run. | | `/gstack-upgrade` | **Self-Updater** — upgrade gstack to latest. Detects global vs vendored install, syncs both, shows what changed. | ### New binaries (v0.19) @@ -385,6 +386,8 @@ Three paths, pick one: After init, the skill offers to register gbrain as an MCP server for Claude Code (`claude mcp add gbrain -- gbrain serve`) so `gbrain search`, `gbrain put_page`, etc. show up as first-class typed tools — not bash shell-outs. +**Keeping the brain current.** Run `/sync-gbrain` from any repo to re-index its code into gbrain (incremental by default, `--full` for a full reindex, `--dry-run` to preview). The skill registers the cwd as a federated source via `gbrain sources add`, runs `gbrain sync --strategy code`, and writes a `## GBrain Search Guidance` block to your project's CLAUDE.md so the agent prefers `gbrain search`/`code-def`/`code-refs` over Grep. The block is removed automatically if the capability check fails — no stale guidance pointing at tools that aren't installed. + **Per-remote trust policy.** Each repo on your machine gets one of three tiers: - `read-write` — agent can search the brain AND write new pages back from this repo @@ -454,7 +457,7 @@ Use /browse from gstack for all web browsing. Never use mcp__claude-in-chrome__* Available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review, /design-consultation, /design-shotgun, /design-html, /review, /ship, /land-and-deploy, /canary, /benchmark, /browse, /open-gstack-browser, /qa, /qa-only, /design-review, -/setup-browser-cookies, /setup-deploy, /setup-gbrain, /retro, /investigate, /document-release, +/setup-browser-cookies, /setup-deploy, /setup-gbrain, /sync-gbrain, /retro, /investigate, /document-release, /codex, /cso, /autoplan, /pair-agent, /careful, /freeze, /guard, /unfreeze, /gstack-upgrade, /learn. ``` diff --git a/SKILL.md b/SKILL.md index 4f428fb8..bfa730ca 100644 --- a/SKILL.md +++ b/SKILL.md @@ -278,6 +278,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/VERSION b/VERSION index 75334e9d..068ff0d4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.26.2.0 +1.26.3.0 diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index 628c36ee..8ec89146 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -346,6 +346,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/benchmark-models/SKILL.md b/benchmark-models/SKILL.md index 6f52cdea..a774d2c0 100644 --- a/benchmark-models/SKILL.md +++ b/benchmark-models/SKILL.md @@ -280,6 +280,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index 3f0d4fff..b7e135f5 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -280,6 +280,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/bin/gstack-gbrain-sync.ts b/bin/gstack-gbrain-sync.ts index a62c3cb7..6aea0f93 100644 --- a/bin/gstack-gbrain-sync.ts +++ b/bin/gstack-gbrain-sync.ts @@ -4,29 +4,38 @@ * * Orchestrates three storage tiers per plan §"Storage tiering": * - * 1. Code (current repo) → gbrain import (Supabase or local PGLite) + * 1. Code (current repo) → `gbrain sources add` (idempotent via + * lib/gbrain-sources.ts) + `gbrain sync + * --strategy code` (incremental) or + * `gbrain reindex-code --yes` (--full). + * NEVER `gbrain import` (markdown only). * 2. Transcripts + curated memory → gstack-memory-ingest (typed put_page) * 3. Curated artifacts to git → gstack-brain-sync (existing pipeline) * * Modes: * --incremental (default) — mtime fast-path; runs all 3 stages with cache hits - * --full — first-run; full walk + import; honest budget per ED2 - * --dry-run — preview what would sync; no writes + * --full — first-run; full walk + reindex; honest budget per ED2 + * --dry-run — preview what would sync; no writes anywhere (incl. state file) * - * --watch (V1.5 P0 TODO): file-watcher daemon. Deferred per Codex F3 ("no daemon" - * invariant). For V1, continuous sync rides the preamble-boundary hook only. + * Concurrency safety per /plan-eng-review D1: + * - Lock file at ~/.gstack/.sync-gbrain.lock (PID + start ts). + * - Stale-lock takeover after 5 min (process death). + * - State file written via tmp+rename for atomicity. + * - Lock released in finally; SIGINT/SIGTERM trapped for cleanup. * - * Cross-repo TODO (V1.5): when gbrain CLI ships `put_file` + `restore-from-sync`, - * this helper picks them up via version probe (Codex F6 + D9) and routes - * code/transcripts to Supabase Storage instead of put_page. + * --watch (V1.5 P0 TODO): file-watcher daemon. NOTE: gbrain v0.25.1 already + * ships `gbrain sync --watch [--interval N]` and `gbrain sync --install-cron`; + * when revisited, /sync-gbrain --watch wires through to the gbrain CLI rather + * than building a gstack-side daemon. */ -import { existsSync, statSync, mkdirSync, writeFileSync, readFileSync } from "fs"; +import { existsSync, statSync, mkdirSync, writeFileSync, readFileSync, unlinkSync, renameSync } from "fs"; import { join, dirname } from "path"; -import { execSync, spawnSync } from "child_process"; +import { execSync, execFileSync, spawnSync } from "child_process"; import { homedir } from "os"; -import { detectEngineTier, withErrorContext } from "../lib/gstack-memory-helpers"; +import { detectEngineTier, withErrorContext, canonicalizeRemote } from "../lib/gstack-memory-helpers"; +import { sourcePageCount } from "../lib/gbrain-sources"; // ── Types ────────────────────────────────────────────────────────────────── @@ -41,12 +50,22 @@ interface CliArgs { codeOnly: boolean; } +interface CodeStageDetail { + source_id?: string; + source_path?: string; + page_count?: number | null; + last_imported?: string; + status?: "ok" | "skipped" | "failed"; +} + interface StageResult { name: string; ran: boolean; ok: boolean; duration_ms: number; summary: string; + /** Stage-specific structured detail. Code stage carries source_id + page_count. */ + detail?: CodeStageDetail; } // ── Constants ────────────────────────────────────────────────────────────── @@ -54,6 +73,8 @@ interface StageResult { const HOME = homedir(); const GSTACK_HOME = process.env.GSTACK_HOME || join(HOME, ".gstack"); const STATE_PATH = join(GSTACK_HOME, ".gbrain-sync-state.json"); +const LOCK_PATH = join(GSTACK_HOME, ".sync-gbrain.lock"); +const STALE_LOCK_MS = 5 * 60 * 1000; // ── CLI ──────────────────────────────────────────────────────────────────── @@ -62,18 +83,18 @@ function printUsage(): void { Modes: --incremental Default. mtime fast-path; ~50ms steady-state. - --full First-run; full walk + import. Honest ~25-35 min for big Macs (ED2). - --dry-run Preview what would sync; no writes. + --full First-run; full walk + reindex. Honest ~25-35 min for big Macs (ED2). + --dry-run Preview what would sync; no writes anywhere. Options: --quiet Suppress per-stage output. - --no-code Skip the gbrain import (current repo) stage. + --no-code Skip the cwd code-import stage. --no-memory Skip the gstack-memory-ingest stage (transcripts + artifacts). --no-brain-sync Skip the gstack-brain-sync git pipeline stage. - --code-only Only run the gbrain import stage (alias for --no-memory --no-brain-sync). + --code-only Only run the code-import stage (alias for --no-memory --no-brain-sync). --help This text. -Stages run in order: code import → memory ingest → curated git push. +Stages run in order: code → memory ingest → curated git push. Each stage failure is non-fatal; subsequent stages still run. `); } @@ -116,7 +137,7 @@ function parseArgs(): CliArgs { return { mode, quiet, noCode, noMemory, noBrainSync, codeOnly }; } -// ── Stage runners ────────────────────────────────────────────────────────── +// ── Helpers ──────────────────────────────────────────────────────────────── function repoRoot(): string | null { try { @@ -127,6 +148,32 @@ function repoRoot(): string | null { } } +function originUrl(): string | null { + try { + const out = execSync("git remote get-url origin", { encoding: "utf-8", timeout: 2000 }); + return out.trim(); + } catch { + return null; + } +} + +/** + * Derive a stable source id for the cwd code corpus. Pattern: `gstack-code-`, + * where comes from canonicalizeRemote() then `/` → `-` (e.g., + * `github.com/garrytan/gstack` → `gstack-code-github-com-garrytan-gstack`). + * + * Falls back to `gstack-code-` when there is no origin (local repo). + */ +function deriveCodeSourceId(repoPath: string): string { + const remote = canonicalizeRemote(originUrl()); + if (remote) { + return `gstack-code-${remote.replace(/[\/\s]+/g, "-").replace(/-+/g, "-")}`; + } + // Fallback for repos without a remote. + const base = repoPath.split("/").pop() || "repo"; + return `gstack-code-${base.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-")}`; +} + function gbrainAvailable(): boolean { try { execSync("command -v gbrain", { stdio: "ignore" }); @@ -136,6 +183,55 @@ function gbrainAvailable(): boolean { } } +// ── Lock file (D1) ───────────────────────────────────────────────────────── + +interface LockInfo { + pid: number; + started_at: string; +} + +function acquireLock(): boolean { + mkdirSync(GSTACK_HOME, { recursive: true }); + if (existsSync(LOCK_PATH)) { + // Check if stale. + try { + const stat = statSync(LOCK_PATH); + const ageMs = Date.now() - stat.mtimeMs; + if (ageMs > STALE_LOCK_MS) { + // Stale; take over. + unlinkSync(LOCK_PATH); + } else { + return false; + } + } catch { + // Cannot stat; bail conservatively. + return false; + } + } + const info: LockInfo = { pid: process.pid, started_at: new Date().toISOString() }; + try { + writeFileSync(LOCK_PATH, JSON.stringify(info), { encoding: "utf-8", flag: "wx" }); + return true; + } catch { + return false; + } +} + +function releaseLock(): void { + try { + if (!existsSync(LOCK_PATH)) return; + const raw = readFileSync(LOCK_PATH, "utf-8"); + const info = JSON.parse(raw) as LockInfo; + if (info.pid === process.pid) { + unlinkSync(LOCK_PATH); + } + } catch { + // Best-effort cleanup. + } +} + +// ── Stage runners ────────────────────────────────────────────────────────── + function runCodeImport(args: CliArgs): StageResult { const t0 = Date.now(); const root = repoRoot(); @@ -145,42 +241,135 @@ function runCodeImport(args: CliArgs): StageResult { if (!gbrainAvailable()) { return { name: "code", ran: false, ok: false, duration_ms: 0, summary: "skipped (gbrain CLI not in PATH)" }; } + + const sourceId = deriveCodeSourceId(root); + if (args.mode === "dry-run") { - return { name: "code", ran: false, ok: true, duration_ms: 0, summary: `would: gbrain import ${root} --no-embed` }; - } - - const importArgs = ["import", root, "--no-embed"]; - if (args.mode === "incremental") { - // gbrain import is itself idempotent on re-import; --incremental flag if it supports - importArgs.push("--incremental"); - } - - try { - spawnSync("gbrain", importArgs, { - stdio: args.quiet ? ["ignore", "ignore", "ignore"] : ["ignore", "inherit", "inherit"], - timeout: 5 * 60 * 1000, - }); - // Trigger background embedding catch-up - spawnSync("gbrain", ["embed", "--stale"], { - stdio: ["ignore", "ignore", "ignore"], - timeout: 1000, // background spawn; don't wait - }); return { name: "code", - ran: true, + ran: false, ok: true, - duration_ms: Date.now() - t0, - summary: `imported ${root}`, + duration_ms: 0, + summary: `would: gbrain sources add ${sourceId} --path ${root} --federated; gbrain sync --strategy code --source ${sourceId}`, + detail: { source_id: sourceId, source_path: root, status: "skipped" }, }; + } + + // Step 1: Ensure source registered (idempotent). + 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); } catch (err) { return { name: "code", ran: true, ok: false, duration_ms: Date.now() - t0, - summary: `gbrain import failed: ${(err as Error).message}`, + summary: `source registration failed: ${(err as Error).message}`, + detail: { source_id: sourceId, source_path: root, status: "failed" }, }; } + + // Step 2: Run sync or reindex. + const syncArgs = args.mode === "full" + ? ["reindex-code", "--source", sourceId, "--yes"] + : ["sync", "--strategy", "code", "--source", sourceId]; + + const syncResult = spawnSync("gbrain", syncArgs, { + stdio: args.quiet ? ["ignore", "ignore", "ignore"] : ["ignore", "inherit", "inherit"], + timeout: 35 * 60 * 1000, + }); + + if (syncResult.status !== 0) { + return { + name: "code", + ran: true, + ok: false, + duration_ms: Date.now() - t0, + summary: `gbrain ${syncArgs.join(" ")} exited ${syncResult.status}`, + detail: { source_id: sourceId, source_path: root, status: "failed" }, + }; + } + + // Step 3: Read page_count from gbrain sources list. + const pageCount = sourcePageCount(sourceId); + + return { + name: "code", + ran: true, + ok: true, + duration_ms: Date.now() - t0, + summary: `${registered ? "registered + " : ""}synced ${sourceId} (page_count=${pageCount ?? "unknown"})`, + detail: { + source_id: sourceId, + source_path: root, + page_count: pageCount, + last_imported: new Date().toISOString(), + status: "ok", + }, + }; +} + +/** + * 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 { @@ -198,7 +387,7 @@ function runMemoryIngest(args: CliArgs): StageResult { const result = spawnSync("bun", ingestArgs, { encoding: "utf-8", - timeout: 35 * 60 * 1000, // honest 35-min ceiling per ED2 + timeout: 35 * 60 * 1000, }); const summary = (result.stderr || "").split("\n").filter((l) => l.includes("[memory-ingest]")).slice(-1)[0] || "ingest pass complete"; @@ -224,7 +413,6 @@ function runBrainSyncPush(args: CliArgs): StageResult { return { name: "brain-sync", ran: false, ok: true, duration_ms: 0, summary: "skipped (gstack-brain-sync not installed)" }; } - // Discover new artifacts then drain queue spawnSync(brainSyncPath, ["--discover-new"], { stdio: args.quiet ? ["ignore", "ignore", "ignore"] : ["ignore", "inherit", "inherit"], timeout: 60 * 1000, @@ -243,7 +431,7 @@ function runBrainSyncPush(args: CliArgs): StageResult { }; } -// ── State file (records last sync timestamp + stage outcomes) ────────────── +// ── State file ───────────────────────────────────────────────────────────── interface SyncState { schema_version: 1; @@ -266,10 +454,16 @@ function loadSyncState(): SyncState { return { schema_version: 1, last_writer: "gstack-gbrain-sync" }; } +/** + * Atomic state file write per /plan-eng-review D1: write tmp file then rename. + * rename(2) is atomic on POSIX filesystems. + */ function saveSyncState(state: SyncState): void { try { mkdirSync(dirname(STATE_PATH), { recursive: true }); - writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), "utf-8"); + const tmp = `${STATE_PATH}.tmp.${process.pid}`; + writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8"); + renameSync(tmp, STATE_PATH); } catch { // non-fatal } @@ -293,40 +487,67 @@ async function main(): Promise { console.error(`[gbrain-sync] mode=${args.mode} engine=${engine.engine}`); } - const state = loadSyncState(); - const stages: StageResult[] = []; - - if (!args.noCode) { - stages.push(await withErrorContext("sync:code", () => runCodeImport(args), "gstack-gbrain-sync")); - } - if (!args.noMemory) { - stages.push(await withErrorContext("sync:memory", () => runMemoryIngest(args), "gstack-gbrain-sync")); - } - if (!args.noBrainSync) { - stages.push(await withErrorContext("sync:brain-sync", () => runBrainSyncPush(args), "gstack-gbrain-sync")); + // Acquire lock (skip on dry-run since dry-run never writes). + const needsLock = args.mode !== "dry-run"; + let haveLock = false; + if (needsLock) { + haveLock = acquireLock(); + if (!haveLock) { + console.error( + `[gbrain-sync] another /sync-gbrain is running (lock at ${LOCK_PATH}). ` + + `If that process died, the lock auto-clears after 5 min, or remove it manually.` + ); + process.exit(2); + } } - // Persist state (skip on dry-run) - if (args.mode !== "dry-run") { - state.last_sync = new Date().toISOString(); - if (args.mode === "full") state.last_full_sync = state.last_sync; - state.last_stages = stages; - saveSyncState(state); + const cleanup = () => { + if (haveLock) releaseLock(); + }; + process.on("SIGINT", () => { cleanup(); process.exit(130); }); + process.on("SIGTERM", () => { cleanup(); process.exit(143); }); + + let exitCode = 0; + try { + const state = loadSyncState(); + const stages: StageResult[] = []; + + if (!args.noCode) { + stages.push(await withErrorContext("sync:code", () => runCodeImport(args), "gstack-gbrain-sync")); + } + if (!args.noMemory) { + stages.push(await withErrorContext("sync:memory", () => runMemoryIngest(args), "gstack-gbrain-sync")); + } + if (!args.noBrainSync) { + stages.push(await withErrorContext("sync:brain-sync", () => runBrainSyncPush(args), "gstack-gbrain-sync")); + } + + if (args.mode !== "dry-run") { + state.last_sync = new Date().toISOString(); + if (args.mode === "full") state.last_full_sync = state.last_sync; + state.last_stages = stages; + saveSyncState(state); + } + + if (!args.quiet || args.mode === "dry-run") { + console.log(`\ngstack-gbrain-sync (${args.mode}):`); + for (const s of stages) console.log(formatStage(s)); + const okCount = stages.filter((s) => s.ok).length; + const errCount = stages.filter((s) => !s.ok && s.ran).length; + console.log(`\n ${okCount} ok, ${errCount} error, ${stages.length - okCount - errCount} skipped`); + } + + const anyError = stages.some((s) => s.ran && !s.ok); + exitCode = anyError ? 1 : 0; + } finally { + cleanup(); } - if (!args.quiet || args.mode === "dry-run") { - console.log(`\ngstack-gbrain-sync (${args.mode}):`); - for (const s of stages) console.log(formatStage(s)); - const okCount = stages.filter((s) => s.ok).length; - const errCount = stages.filter((s) => !s.ok && s.ran).length; - console.log(`\n ${okCount} ok, ${errCount} error, ${stages.length - okCount - errCount} skipped`); - } - - const anyError = stages.some((s) => s.ran && !s.ok); - process.exit(anyError ? 1 : 0); + process.exit(exitCode); } main().catch((err) => { console.error(`gstack-gbrain-sync fatal: ${err instanceof Error ? err.message : String(err)}`); + releaseLock(); process.exit(1); }); diff --git a/browse/SKILL.md b/browse/SKILL.md index 8aa6ac2f..9a48cd43 100644 --- a/browse/SKILL.md +++ b/browse/SKILL.md @@ -279,6 +279,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/canary/SKILL.md b/canary/SKILL.md index 55837e01..eb4dc50b 100644 --- a/canary/SKILL.md +++ b/canary/SKILL.md @@ -338,6 +338,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/codex/SKILL.md b/codex/SKILL.md index 60820dd2..44b5f0ed 100644 --- a/codex/SKILL.md +++ b/codex/SKILL.md @@ -340,6 +340,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/context-restore/SKILL.md b/context-restore/SKILL.md index a510adbf..0b846206 100644 --- a/context-restore/SKILL.md +++ b/context-restore/SKILL.md @@ -342,6 +342,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/context-save/SKILL.md b/context-save/SKILL.md index 45f7a33a..88e5909e 100644 --- a/context-save/SKILL.md +++ b/context-save/SKILL.md @@ -342,6 +342,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/cso/SKILL.md b/cso/SKILL.md index 44850ff7..75a9df70 100644 --- a/cso/SKILL.md +++ b/cso/SKILL.md @@ -343,6 +343,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/design-consultation/SKILL.md b/design-consultation/SKILL.md index cf2f852f..ec1ef026 100644 --- a/design-consultation/SKILL.md +++ b/design-consultation/SKILL.md @@ -366,6 +366,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/design-html/SKILL.md b/design-html/SKILL.md index ec9f8403..b997772d 100644 --- a/design-html/SKILL.md +++ b/design-html/SKILL.md @@ -345,6 +345,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/design-review/SKILL.md b/design-review/SKILL.md index 96f23235..da5190ce 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -343,6 +343,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/design-shotgun/SKILL.md b/design-shotgun/SKILL.md index 41d2da13..eb09b277 100644 --- a/design-shotgun/SKILL.md +++ b/design-shotgun/SKILL.md @@ -360,6 +360,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/devex-review/SKILL.md b/devex-review/SKILL.md index 2854789d..53030027 100644 --- a/devex-review/SKILL.md +++ b/devex-review/SKILL.md @@ -343,6 +343,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/docs/skills.md b/docs/skills.md index 05c2209c..b20bf665 100644 --- a/docs/skills.md +++ b/docs/skills.md @@ -42,6 +42,7 @@ Detailed guides for every gstack skill — philosophy, workflow, and examples. | [`/codex`](#codex) | **Second Opinion** | Independent review from OpenAI Codex CLI. Three modes: code review (pass/fail gate), adversarial challenge, and open consultation with session continuity. Cross-model analysis when both `/review` and `/codex` have run. | | [`/pair-agent`](#pair-agent) | **Remote Agent Bridge** | Pair a remote AI agent (OpenClaw, Codex, Cursor, Hermes) with your browser. Scoped tunnel, locked allowlist, session token. | | [`/setup-gbrain`](#setup-gbrain) | **Memory Sync** | Set up gbrain for cross-machine session memory sync. One command from zero to live. | +| [`/sync-gbrain`](#sync-gbrain) | **Keep Brain Current** | Refresh gbrain against this repo's code; teach the agent when to use `gbrain search`/`code-def` over Grep. Idempotent; safe to re-run. | | | | | | **Safety & Utility** | | | | [`/careful`](#safety--guardrails) | **Safety Guardrails** | Warns before destructive commands (rm -rf, DROP TABLE, force-push, git reset --hard). Override any warning. Common build cleanups whitelisted. | diff --git a/document-release/SKILL.md b/document-release/SKILL.md index 3394ce40..d863a99d 100644 --- a/document-release/SKILL.md +++ b/document-release/SKILL.md @@ -340,6 +340,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/health/SKILL.md b/health/SKILL.md index a4c63c00..39bc7e8f 100644 --- a/health/SKILL.md +++ b/health/SKILL.md @@ -340,6 +340,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/investigate/SKILL.md b/investigate/SKILL.md index 44f9a403..00de6593 100644 --- a/investigate/SKILL.md +++ b/investigate/SKILL.md @@ -379,6 +379,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/land-and-deploy/SKILL.md b/land-and-deploy/SKILL.md index 04ed85c0..afb47cbe 100644 --- a/land-and-deploy/SKILL.md +++ b/land-and-deploy/SKILL.md @@ -337,6 +337,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/landing-report/SKILL.md b/landing-report/SKILL.md index 3aec38e7..111cbbf8 100644 --- a/landing-report/SKILL.md +++ b/landing-report/SKILL.md @@ -338,6 +338,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/learn/SKILL.md b/learn/SKILL.md index c921c5f9..9e0d739e 100644 --- a/learn/SKILL.md +++ b/learn/SKILL.md @@ -340,6 +340,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/lib/gbrain-sources.ts b/lib/gbrain-sources.ts new file mode 100644 index 00000000..6cf21955 --- /dev/null +++ b/lib/gbrain-sources.ts @@ -0,0 +1,184 @@ +/** + * gbrain-sources — TypeScript helper for idempotent gbrain federated source registration. + * + * Mirrors the bash logic in bin/gstack-gbrain-source-wireup:204-310 but in a form + * importable by other TS callers (currently bin/gstack-gbrain-sync.ts; future + * callers welcome). gbrain has no `sources update` — drift recovery is + * `sources remove` followed by `sources add`. + * + * Per /plan-eng-review D3 (DRY extraction). + */ + +import { execFileSync, spawnSync } from "child_process"; +import { withErrorContext } from "./gstack-memory-helpers"; + +export interface SourceState { + /** "absent" — id not registered. "match" — id at expected path. "drift" — id at different path. */ + status: "absent" | "match" | "drift"; + /** Path gbrain has registered for this id. Only set when status !== "absent". */ + registered_path?: string; +} + +export interface EnsureResult { + /** True if registration state changed (added or re-registered). False on no-op. */ + changed: boolean; + /** Final source state after the call. */ + state: SourceState; +} + +export interface EnsureOptions { + /** Pass --federated to `gbrain sources add`. Default false. */ + federated?: boolean; + /** When status=drift, force a remove+add to update the registered path. Default true. */ + reregister_on_drift?: boolean; + /** + * Optional env override for the spawned `gbrain` calls. Production callers + * leave this unset (inherit process.env). Tests pass a custom env to point + * at a fake `gbrain` on PATH (Bun's execFileSync does not respect runtime + * mutations of process.env.PATH unless env is passed explicitly). + */ + env?: NodeJS.ProcessEnv; +} + +/** + * Probe the registration state of a source by id. + * + * Errors: + * - "gbrain CLI not on PATH" (exit 127) — caller should treat as absent + skip stage. + * - "gbrain DB connection failed" — caller should treat as absent + skip stage. + * - JSON parse error — propagate via withErrorContext caller. + */ +export function probeSource(id: string, env?: NodeJS.ProcessEnv): SourceState { + let stdout: string; + try { + stdout = execFileSync("gbrain", ["sources", "list", "--json"], { + encoding: "utf-8", + timeout: 10_000, + stdio: ["ignore", "pipe", "pipe"], + env, + }); + } catch (err) { + const e = err as NodeJS.ErrnoException & { stderr?: Buffer }; + const stderr = e.stderr?.toString() || ""; + if (e.code === "ENOENT" || stderr.includes("command not found")) { + 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(stdout); + } catch (err) { + throw new Error(`gbrain sources list returned non-JSON output: ${(err as Error).message}`); + } + + const sources = parsed.sources || []; + const match = sources.find((s) => s.id === id); + if (!match) return { status: "absent" }; + return { + status: "match", + registered_path: match.local_path, + }; +} + +/** + * Ensure source is registered at . Idempotent. + * + * Behavior: + * - status=absent → `gbrain sources add --path [--federated]`, returns changed=true. + * - status=match + same path → no-op, returns changed=false. + * - status=match + different path → `sources remove` + `sources add`, returns changed=true. + * (Skip when reregister_on_drift=false; returns changed=false.) + * + * Caller is responsible for catching errors. The function uses withErrorContext for + * forensic logging to ~/.gstack/.gbrain-errors.jsonl. + */ +export async function ensureSourceRegistered( + id: string, + path: string, + options: EnsureOptions = {} +): Promise { + const federated = options.federated ?? false; + const reregister_on_drift = options.reregister_on_drift ?? true; + const env = options.env; + + return withErrorContext(`ensureSourceRegistered:${id}`, () => { + const probed = probeSource(id, env); + + // Disambiguate match-but-different-path + let state: SourceState = probed; + if (probed.status === "match" && probed.registered_path !== path) { + state = { status: "drift", registered_path: probed.registered_path }; + } + + if (state.status === "match") { + return { changed: false, state }; + } + + if (state.status === "drift" && !reregister_on_drift) { + return { changed: false, state }; + } + + // For drift, remove first. + if (state.status === "drift") { + const rm = spawnSync("gbrain", ["sources", "remove", id, "--yes"], { + encoding: "utf-8", + timeout: 30_000, + env, + }); + if (rm.status !== 0) { + throw new Error(`gbrain sources remove ${id} failed: ${rm.stderr || rm.stdout || `exit ${rm.status}`}`); + } + } + + // Add. + const addArgs = ["sources", "add", id, "--path", path]; + if (federated) addArgs.push("--federated"); + const add = spawnSync("gbrain", addArgs, { + encoding: "utf-8", + timeout: 30_000, + env, + }); + if (add.status !== 0) { + throw new Error(`gbrain sources add ${id} failed: ${add.stderr || add.stdout || `exit ${add.status}`}`); + } + + return { + changed: true, + state: { status: "match", registered_path: path }, + }; + }, "gbrain-sources"); +} + +/** + * Get page_count for a registered source. Returns null if source is absent or if + * page_count is missing/invalid in the JSON. Used by the verdict block + preamble + * variant selection. + */ +export function sourcePageCount(id: string, env?: NodeJS.ProcessEnv): number | null { + let stdout: string; + try { + stdout = execFileSync("gbrain", ["sources", "list", "--json"], { + encoding: "utf-8", + timeout: 10_000, + stdio: ["ignore", "pipe", "pipe"], + env, + }); + } catch { + return null; + } + + try { + const parsed = JSON.parse(stdout) as { sources?: Array<{ id?: string; page_count?: number }> }; + const match = (parsed.sources || []).find((s) => s.id === id); + if (!match) return null; + if (typeof match.page_count !== "number") return null; + return match.page_count; + } catch { + return null; + } +} diff --git a/make-pdf/SKILL.md b/make-pdf/SKILL.md index 4e9a3c39..cc2e5f68 100644 --- a/make-pdf/SKILL.md +++ b/make-pdf/SKILL.md @@ -279,6 +279,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/office-hours/SKILL.md b/office-hours/SKILL.md index 836a041f..ed160ea6 100644 --- a/office-hours/SKILL.md +++ b/office-hours/SKILL.md @@ -375,6 +375,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/open-gstack-browser/SKILL.md b/open-gstack-browser/SKILL.md index 86ce4ece..d927c042 100644 --- a/open-gstack-browser/SKILL.md +++ b/open-gstack-browser/SKILL.md @@ -337,6 +337,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/package.json b/package.json index 3fa1d216..380239b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.26.2.0", + "version": "1.26.3.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 0fa3f98e..2e028e8e 100644 --- a/pair-agent/SKILL.md +++ b/pair-agent/SKILL.md @@ -338,6 +338,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/plan-ceo-review/SKILL.md b/plan-ceo-review/SKILL.md index 6543257d..42344be3 100644 --- a/plan-ceo-review/SKILL.md +++ b/plan-ceo-review/SKILL.md @@ -369,6 +369,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/plan-design-review/SKILL.md b/plan-design-review/SKILL.md index 5afbedbe..d6ae7013 100644 --- a/plan-design-review/SKILL.md +++ b/plan-design-review/SKILL.md @@ -342,6 +342,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/plan-devex-review/SKILL.md b/plan-devex-review/SKILL.md index ff059c4a..22c508b3 100644 --- a/plan-devex-review/SKILL.md +++ b/plan-devex-review/SKILL.md @@ -346,6 +346,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index 02ffc528..9d9e623f 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -344,6 +344,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/plan-tune/SKILL.md b/plan-tune/SKILL.md index bfdfbaee..f2f9d769 100644 --- a/plan-tune/SKILL.md +++ b/plan-tune/SKILL.md @@ -351,6 +351,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/qa-only/SKILL.md b/qa-only/SKILL.md index bfa69764..ee683d85 100644 --- a/qa-only/SKILL.md +++ b/qa-only/SKILL.md @@ -339,6 +339,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/qa/SKILL.md b/qa/SKILL.md index 85b1598a..b0215723 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -345,6 +345,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/retro/SKILL.md b/retro/SKILL.md index 2b738511..0cef5a25 100644 --- a/retro/SKILL.md +++ b/retro/SKILL.md @@ -357,6 +357,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/review/SKILL.md b/review/SKILL.md index 112e3c53..921905d3 100644 --- a/review/SKILL.md +++ b/review/SKILL.md @@ -342,6 +342,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/scrape/SKILL.md b/scrape/SKILL.md index 1a7c9072..60a8f294 100644 --- a/scrape/SKILL.md +++ b/scrape/SKILL.md @@ -338,6 +338,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/scripts/resolvers/preamble/generate-brain-sync-block.ts b/scripts/resolvers/preamble/generate-brain-sync-block.ts index fa00b2df..7aa43727 100644 --- a/scripts/resolvers/preamble/generate-brain-sync-block.ts +++ b/scripts/resolvers/preamble/generate-brain-sync-block.ts @@ -2,6 +2,9 @@ * gbrain-sync preamble block. * * Emits bash that runs at every skill invocation: + * 0. Live gbrain-availability hint (per /plan-eng-review): when gbrain is + * configured, emit one of two variants (steady-state vs empty-corpus + * emergency). Zero context cost when gbrain is not configured. * 1. If ~/.gstack-brain-remote.txt exists AND ~/.gstack/.git is missing, * surface a restore-available hint (does NOT auto-run restore). * 2. If sync is on, run `gstack-brain-sync --once` (drain + push). @@ -31,6 +34,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/setup-browser-cookies/SKILL.md b/setup-browser-cookies/SKILL.md index 5cf4d4a6..308dd18b 100644 --- a/setup-browser-cookies/SKILL.md +++ b/setup-browser-cookies/SKILL.md @@ -276,6 +276,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/setup-deploy/SKILL.md b/setup-deploy/SKILL.md index f28b9914..466e362b 100644 --- a/setup-deploy/SKILL.md +++ b/setup-deploy/SKILL.md @@ -341,6 +341,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/setup-gbrain/SKILL.md b/setup-gbrain/SKILL.md index e0dcc91b..522ac792 100644 --- a/setup-gbrain/SKILL.md +++ b/setup-gbrain/SKILL.md @@ -342,6 +342,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then @@ -1130,6 +1159,50 @@ Find-and-replace (or append) this section in CLAUDE.md: - Current repo policy: {read-write|read-only|deny|unset} ``` +**After Step 9 (smoke test) passes, also write the `## GBrain Search Guidance` +block** so the coding agent learns when to prefer `gbrain` over Grep. This +block is gated on the smoke test passing — write the Configuration block +first (so the user knows what state they're in even if the smoke test fails), +then return here after Step 9 and write the guidance block only if smoke +test succeeded. + +When Step 9 passes, find-and-replace (or append) this block. Use HTML-comment +delimiters so removal regex is unambiguous and never eats user content. The +block content is machine-AGNOSTIC — no engine type, no page counts, no +last-sync time. Machine state stays in the Configuration block above. + +```markdown +## GBrain Search Guidance (configured by /sync-gbrain) + + +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). +- `~/.gstack/` curated memory (registered as `gstack-brain-` source via + the existing federation pipeline). + +Prefer gbrain when: +- "Where is X handled?" / semantic intent, no exact string yet: + `gbrain search ""` or `gbrain query ""` +- "Where is symbol Y defined?" / symbol-based code questions: + `gbrain code-def ` or `gbrain code-refs ` +- "What calls Y?" / "What does Y depend on?": + `gbrain code-callers ` / `gbrain code-callees ` +- "What did we decide last time?" / past plans, retros, learnings: + `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. + + +``` + +If Step 9 smoke test fails, skip the guidance block write entirely. The user's +next `/sync-gbrain` run will re-evaluate capability and write the block when +the round-trip works. + --- ## Step 9: Smoke test diff --git a/setup-gbrain/SKILL.md.tmpl b/setup-gbrain/SKILL.md.tmpl index 3b1ff2d7..fb748044 100644 --- a/setup-gbrain/SKILL.md.tmpl +++ b/setup-gbrain/SKILL.md.tmpl @@ -481,6 +481,50 @@ Find-and-replace (or append) this section in CLAUDE.md: - Current repo policy: {read-write|read-only|deny|unset} ``` +**After Step 9 (smoke test) passes, also write the `## GBrain Search Guidance` +block** so the coding agent learns when to prefer `gbrain` over Grep. This +block is gated on the smoke test passing — write the Configuration block +first (so the user knows what state they're in even if the smoke test fails), +then return here after Step 9 and write the guidance block only if smoke +test succeeded. + +When Step 9 passes, find-and-replace (or append) this block. Use HTML-comment +delimiters so removal regex is unambiguous and never eats user content. The +block content is machine-AGNOSTIC — no engine type, no page counts, no +last-sync time. Machine state stays in the Configuration block above. + +```markdown +## GBrain Search Guidance (configured by /sync-gbrain) + + +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). +- `~/.gstack/` curated memory (registered as `gstack-brain-` source via + the existing federation pipeline). + +Prefer gbrain when: +- "Where is X handled?" / semantic intent, no exact string yet: + `gbrain search ""` or `gbrain query ""` +- "Where is symbol Y defined?" / symbol-based code questions: + `gbrain code-def ` or `gbrain code-refs ` +- "What calls Y?" / "What does Y depend on?": + `gbrain code-callers ` / `gbrain code-callees ` +- "What did we decide last time?" / past plans, retros, learnings: + `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. + + +``` + +If Step 9 smoke test fails, skip the guidance block write entirely. The user's +next `/sync-gbrain` run will re-evaluate capability and write the block when +the round-trip works. + --- ## Step 9: Smoke test diff --git a/ship/SKILL.md b/ship/SKILL.md index 8815ebad..c7a74dd7 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -343,6 +343,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/skillify/SKILL.md b/skillify/SKILL.md index feb765d9..7dd70a95 100644 --- a/skillify/SKILL.md +++ b/skillify/SKILL.md @@ -339,6 +339,35 @@ _BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt" _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). +_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} + fi + if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; 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." + fi + fi +fi + _BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get gbrain_sync_mode 2>/dev/null || echo off) if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then diff --git a/sync-gbrain/SKILL.md b/sync-gbrain/SKILL.md new file mode 100644 index 00000000..c456dd9d --- /dev/null +++ b/sync-gbrain/SKILL.md @@ -0,0 +1,945 @@ +--- +name: sync-gbrain +preamble-tier: 2 +version: 1.0.0 +description: | + Keep gbrain current with this repo's code and refresh agent search + guidance in CLAUDE.md. Wraps the gstack-gbrain-sync orchestrator with + state probing, native code-surface registration, capability checks, + and a verdict block. Re-runnable, idempotent. Use when: "sync gbrain", + "refresh gbrain", "re-index this repo", "gbrain search isn't finding + things". (gstack) +triggers: + - sync gbrain + - refresh gbrain + - reindex repo + - update gbrain +allowed-tools: + - Bash + - Read + - Write + - Edit + - Glob + - Grep + - AskUserQuestion +--- + + + +## Preamble (run first) + +```bash +_UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true) +[ -n "$_UPD" ] && echo "$_UPD" || true +mkdir -p ~/.gstack/sessions +touch ~/.gstack/sessions/"$PPID" +_SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true +_PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") +_PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") +_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") +echo "BRANCH: $_BRANCH" +_SKILL_PREFIX=$(~/.claude/skills/gstack/bin/gstack-config get skill_prefix 2>/dev/null || echo "false") +echo "PROACTIVE: $_PROACTIVE" +echo "PROACTIVE_PROMPTED: $_PROACTIVE_PROMPTED" +echo "SKILL_PREFIX: $_SKILL_PREFIX" +source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true +REPO_MODE=${REPO_MODE:-unknown} +echo "REPO_MODE: $REPO_MODE" +_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") +echo "LAKE_INTRO: $_LAKE_SEEN" +_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) +_TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no") +_TEL_START=$(date +%s) +_SESSION_ID="$$-$(date +%s)" +echo "TELEMETRY: ${_TEL:-off}" +echo "TEL_PROMPTED: $_TEL_PROMPTED" +_EXPLAIN_LEVEL=$(~/.claude/skills/gstack/bin/gstack-config get explain_level 2>/dev/null || echo "default") +if [ "$_EXPLAIN_LEVEL" != "default" ] && [ "$_EXPLAIN_LEVEL" != "terse" ]; then _EXPLAIN_LEVEL="default"; fi +echo "EXPLAIN_LEVEL: $_EXPLAIN_LEVEL" +_QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning 2>/dev/null || echo "false") +echo "QUESTION_TUNING: $_QUESTION_TUNING" +mkdir -p ~/.gstack/analytics +if [ "$_TEL" != "off" ]; then +echo '{"skill":"sync-gbrain","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi +for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do + if [ -f "$_PF" ]; then + if [ "$_TEL" != "off" ] && [ -x "~/.claude/skills/gstack/bin/gstack-telemetry-log" ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true + fi + rm -f "$_PF" 2>/dev/null || true + fi + break +done +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true +_LEARN_FILE="${GSTACK_HOME:-$HOME/.gstack}/projects/${SLUG:-unknown}/learnings.jsonl" +if [ -f "$_LEARN_FILE" ]; then + _LEARN_COUNT=$(wc -l < "$_LEARN_FILE" 2>/dev/null | tr -d ' ') + echo "LEARNINGS: $_LEARN_COUNT entries loaded" + if [ "$_LEARN_COUNT" -gt 5 ] 2>/dev/null; then + ~/.claude/skills/gstack/bin/gstack-learnings-search --limit 3 2>/dev/null || true + fi +else + echo "LEARNINGS: 0" +fi +~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"sync-gbrain","event":"started","branch":"'"$_BRANCH"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null & +_HAS_ROUTING="no" +if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then + _HAS_ROUTING="yes" +fi +_ROUTING_DECLINED=$(~/.claude/skills/gstack/bin/gstack-config get routing_declined 2>/dev/null || echo "false") +echo "HAS_ROUTING: $_HAS_ROUTING" +echo "ROUTING_DECLINED: $_ROUTING_DECLINED" +_VENDORED="no" +if [ -d ".claude/skills/gstack" ] && [ ! -L ".claude/skills/gstack" ]; then + if [ -f ".claude/skills/gstack/VERSION" ] || [ -d ".claude/skills/gstack/.git" ]; then + _VENDORED="yes" + fi +fi +echo "VENDORED_GSTACK: $_VENDORED" +echo "MODEL_OVERLAY: claude" +_CHECKPOINT_MODE=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_mode 2>/dev/null || echo "explicit") +_CHECKPOINT_PUSH=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_push 2>/dev/null || echo "false") +echo "CHECKPOINT_MODE: $_CHECKPOINT_MODE" +echo "CHECKPOINT_PUSH: $_CHECKPOINT_PUSH" +[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true +``` + +## Plan Mode Safe Operations + +In plan mode, allowed because they inform the plan: `$B`, `$D`, `codex exec`/`codex review`, writes to `~/.gstack/`, writes to the plan file, and `open` for generated artifacts. + +## Skill Invocation During Plan Mode + +If the user invokes a skill in plan mode, the skill takes precedence over generic plan mode behavior. **Treat the skill file as executable instructions, not reference.** Follow it step by step starting from Step 0; the first AskUserQuestion is the workflow entering plan mode, not a violation of it. AskUserQuestion (any variant — `mcp__*__AskUserQuestion` or native; see "AskUserQuestion Format → Tool resolution") satisfies plan mode's end-of-turn requirement. If no variant is callable, fall back to writing the decision brief into the plan file as a `## Decisions to confirm` section + ExitPlanMode — never silently auto-decide. At a STOP point, stop immediately. Do not continue the workflow or call ExitPlanMode there. Commands marked "PLAN MODE EXCEPTION — ALWAYS RUN" execute. Call ExitPlanMode only after the skill workflow completes, or if the user tells you to cancel the skill or leave plan mode. + +If `PROACTIVE` is `"false"`, do not auto-invoke or proactively suggest skills. If a skill seems useful, ask: "I think /skillname might help here — want me to run it?" + +If `SKILL_PREFIX` is `"true"`, suggest/invoke `/gstack-*` names. Disk paths stay `~/.claude/skills/gstack/[skill-name]/SKILL.md`. + +If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). + +If output shows `JUST_UPGRADED `: print "Running gstack v{to} (just updated!)". If `SPAWNED_SESSION` is true, skip feature discovery. + +Feature discovery, max one prompt per session: +- Missing `~/.claude/skills/gstack/.feature-prompted-continuous-checkpoint`: AskUserQuestion for Continuous checkpoint auto-commits. If accepted, run `~/.claude/skills/gstack/bin/gstack-config set checkpoint_mode continuous`. Always touch marker. +- Missing `~/.claude/skills/gstack/.feature-prompted-model-overlay`: inform "Model overlays are active. MODEL_OVERLAY shows the patch." Always touch marker. + +After upgrade prompts, continue workflow. + +If `WRITING_STYLE_PENDING` is `yes`: ask once about writing style: + +> v1 prompts are simpler: first-use jargon glosses, outcome-framed questions, shorter prose. Keep default or restore terse? + +Options: +- A) Keep the new default (recommended — good writing helps everyone) +- B) Restore V0 prose — set `explain_level: terse` + +If A: leave `explain_level` unset (defaults to `default`). +If B: run `~/.claude/skills/gstack/bin/gstack-config set explain_level terse`. + +Always run (regardless of choice): +```bash +rm -f ~/.gstack/.writing-style-prompt-pending +touch ~/.gstack/.writing-style-prompted +``` + +Skip if `WRITING_STYLE_PENDING` is `no`. + +If `LAKE_INTRO` is `no`: say "gstack follows the **Boil the Lake** principle — do the complete thing when AI makes marginal cost near-zero. Read more: https://garryslist.org/posts/boil-the-ocean" Offer to open: + +```bash +open https://garryslist.org/posts/boil-the-ocean +touch ~/.gstack/.completeness-intro-seen +``` + +Only run `open` if yes. Always run `touch`. + +If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion: + +> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names. + +Options: +- A) Help gstack get better! (recommended) +- B) No thanks + +If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry community` + +If B: ask follow-up: + +> Anonymous mode sends only aggregate usage, no unique ID. + +Options: +- A) Sure, anonymous is fine +- B) No thanks, fully off + +If B→A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous` +If B→B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off` + +Always run: +```bash +touch ~/.gstack/.telemetry-prompted +``` + +Skip if `TEL_PROMPTED` is `yes`. + +If `PROACTIVE_PROMPTED` is `no` AND `TEL_PROMPTED` is `yes`: ask once: + +> Let gstack proactively suggest skills, like /qa for "does this work?" or /investigate for bugs? + +Options: +- A) Keep it on (recommended) +- B) Turn it off — I'll type /commands myself + +If A: run `~/.claude/skills/gstack/bin/gstack-config set proactive true` +If B: run `~/.claude/skills/gstack/bin/gstack-config set proactive false` + +Always run: +```bash +touch ~/.gstack/.proactive-prompted +``` + +Skip if `PROACTIVE_PROMPTED` is `yes`. + +If `HAS_ROUTING` is `no` AND `ROUTING_DECLINED` is `false` AND `PROACTIVE_PROMPTED` is `yes`: +Check if a CLAUDE.md file exists in the project root. If it does not exist, create it. + +Use AskUserQuestion: + +> gstack works best when your project's CLAUDE.md includes skill routing rules. + +Options: +- A) Add routing rules to CLAUDE.md (recommended) +- B) No thanks, I'll invoke skills manually + +If A: Append this section to the end of CLAUDE.md: + +```markdown + +## Skill routing + +When the user's request matches an available skill, invoke it via the Skill tool. When in doubt, invoke the skill. + +Key routing rules: +- Product ideas/brainstorming → invoke /office-hours +- Strategy/scope → invoke /plan-ceo-review +- Architecture → invoke /plan-eng-review +- Design system/plan review → invoke /design-consultation or /plan-design-review +- Full review pipeline → invoke /autoplan +- Bugs/errors → invoke /investigate +- QA/testing site behavior → invoke /qa or /qa-only +- Code review/diff check → invoke /review +- Visual polish → invoke /design-review +- Ship/deploy/PR → invoke /ship or /land-and-deploy +- Save progress → invoke /context-save +- Resume context → invoke /context-restore +``` + +Then commit the change: `git add CLAUDE.md && git commit -m "chore: add gstack skill routing rules to CLAUDE.md"` + +If B: run `~/.claude/skills/gstack/bin/gstack-config set routing_declined true` and say they can re-enable with `gstack-config set routing_declined false`. + +This only happens once per project. Skip if `HAS_ROUTING` is `yes` or `ROUTING_DECLINED` is `true`. + +If `VENDORED_GSTACK` is `yes`, warn once via AskUserQuestion unless `~/.gstack/.vendoring-warned-$SLUG` exists: + +> This project has gstack vendored in `.claude/skills/gstack/`. Vendoring is deprecated. +> Migrate to team mode? + +Options: +- A) Yes, migrate to team mode now +- B) No, I'll handle it myself + +If A: +1. Run `git rm -r .claude/skills/gstack/` +2. Run `echo '.claude/skills/gstack/' >> .gitignore` +3. Run `~/.claude/skills/gstack/bin/gstack-team-init required` (or `optional`) +4. Run `git add .claude/ .gitignore CLAUDE.md && git commit -m "chore: migrate gstack from vendored to team mode"` +5. Tell the user: "Done. Each developer now runs: `cd ~/.claude/skills/gstack && ./setup --team`" + +If B: say "OK, you're on your own to keep the vendored copy up to date." + +Always run (regardless of choice): +```bash +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true +touch ~/.gstack/.vendoring-warned-${SLUG:-unknown} +``` + +If marker exists, skip. + +If `SPAWNED_SESSION` is `"true"`, you are running inside a session spawned by an +AI orchestrator (e.g., OpenClaw). In spawned sessions: +- Do NOT use AskUserQuestion for interactive prompts. Auto-choose the recommended option. +- Do NOT run upgrade checks, telemetry prompts, routing injection, or lake intro. +- Focus on completing the task and reporting results via prose output. +- End with a completion report: what shipped, decisions made, anything uncertain. + +## AskUserQuestion Format + +### Tool resolution (read first) + +"AskUserQuestion" can resolve to two tools at runtime: the **host MCP variant** (e.g. `mcp__conductor__AskUserQuestion` — appears in your tool list when the host registers it) or the **native** Claude Code tool. + +**Rule:** if any `mcp__*__AskUserQuestion` variant is in your tool list, prefer it. Hosts may disable native AUQ via `--disallowedTools AskUserQuestion` (Conductor does, by default) and route through their MCP variant; calling native there silently fails. Same questions/options shape; same decision-brief format applies. + +**Fallback when neither variant is callable:** in plan mode, write the decision brief into the plan file as a `## Decisions to confirm` section + ExitPlanMode (the native "Ready to execute?" surfaces it). Outside plan mode, output the brief as prose and stop. **Never silently auto-decide** — only `/plan-tune` AUTO_DECIDE opt-ins authorize auto-picking. + +### Format + +Every AskUserQuestion is a decision brief and must be sent as tool_use, not prose. + +``` +D +Project/branch/task: <1 short grounding sentence using _BRANCH> +ELI10: +Stakes if we pick wrong: +Recommendation: because +Completeness: A=X/10, B=Y/10 (or: Note: options differ in kind, not coverage — no completeness score) +Pros / cons: +A)