mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-10 20:07:49 +02:00
v1.57.5.0 feat: cross-session decision memory + gbrain dream-stage call graph (#1910)
* feat(gbrain-sync): add cycleCompleted() cycle-state probe Reads `gbrain doctor` cycle_freshness to classify whether a source has completed a full cycle (completed/never/unknown). A fail naming this source -> never; a fail naming only other sources -> completed; an absent or unparseable check -> unknown, so an unrelated doctor failure never masks a real state. Gates the automatic call-graph build on --full. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(gbrain-sync): --dream call-graph stage with lock-free gate + honest outcome guard Adds a source-scoped `gbrain dream --source <id>` stage that builds this worktree's call graph (code-callers/code-callees). Runs lock-free after the sync lock releases so it never blocks sibling worktrees; a .dream-in-progress marker dedupes concurrent dreams. --full auto-runs it only when the cycle was never built; explicit --dream always forces; --no-dream opts out. The stage parses the cycle's own output and reports the truth, not a flat "built": a WARN when the schema pack can't extract code symbols, when the embed phase failed for a missing key, or when 0 edges resolved; OK with the resolved-edge count otherwise. gbrain exits 0 even when it skips on a held cycle lock (e.g. autopilot), so that case reports SKIP, not success. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: ignore gbrain .sources/ local staging dir gbrain writes per-source staging and capability-check artifacts under .sources/ in the repo root. It's machine-local runtime state, not source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(gbrain): honest call-graph guidance in /sync-gbrain + pin works on gbrain>=0.41.38 sync-gbrain frames the --dream offer honestly: building a call graph requires a code-aware schema pack, and the dream stage reports a WARN when it can't. The verdict's Call graph row mirrors the dream stage's real outcome instead of assuming a completed cycle means edges exist. The ## GBrain Search Guidance block written into CLAUDE.md drops the old code-callers --source caveat: gbrain >=0.41.38.0 honors the .gbrain-source pin for code-callers/code-callees. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(jsonl-store): shared audited JSONL plumbing (injection-reject + atomic append + tolerant read) Single source of truth extracted for D2A: gstack-learnings-* and the upcoming gstack-decision-* bins share one injection-pattern list, one atomic single-line appender, and one tolerant reader. No more drift between stores. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(learnings-log): use shared hasInjection from lib/jsonl-store (D2A) Replace the inline injection-pattern copy with the shared list. One audited write-path rejection across learnings + the upcoming decision store. Behavior unchanged (35/35 learnings tests green); learnings-search keeps its inline copy because a structural test pins its bash/bun shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(decision): event-sourced decision-memory model (lib/gstack-decision) decide/supersede/redact events on lib/jsonl-store; active set is computed (no mutable status), dangling refs tolerated. Free-text is injection-checked and redact-scanned on write (HIGH secret -> reject). Scope filter (repo/branch/issue) for relevant resurfacing. File-only + reliable; gbrain not required. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(decision): bounded active snapshot + compaction (redact expunges, supersede archives) writeSnapshot/readSnapshot/rebuildSnapshot give an O(active) bounded read for the session-start hot path (D1A). compact() rewrites the log to active, archives superseded decisions for history, and EXPUNGES redacted ones (dropped, never archived) so an accidentally-captured secret leaves the store for good. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(decision): gstack-decision-log + gstack-decision-search bins (non-interactive) Two bins mirroring gstack-learnings-* (D3A). log writes decide/--supersede/--redact/ --compact events + refreshes the bounded snapshot + enqueues for cross-machine sync; search reads the O(active) snapshot, scope-filtered to current branch, newest-first, --all to include superseded, --json for machines. Empty store returns silently (no snapshot write on an empty read). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(memory): surface active decisions at session start + capture nudge (Context Recovery) Context Recovery now shows recent scope-relevant active decisions (bounded read of decisions.active.json via gstack-decision-search) and instructs the agent to treat them as settled calls and to log durable decisions/reversals. Closes the Phase-1 capture->curate->resurface loop, reliable + file-only. Regen across all hosts folded in (squash-with-regen); parity 10/10, freshness green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: refresh ship golden baselines for the memory-loop preamble change Context Recovery now emits the cross-session-decisions block, so ship's preamble (all hosts) changed. Golden baselines are hand-maintained copies (gen does not write them); refresh them from the fresh gen so golden-file regression passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(memory): document the cross-session decision-memory loop in CLAUDE.md Adds a '## Cross-session decision memory' section: how to resurface (gstack-decision-search) and capture (gstack-decision-log) durable decisions, the supersede/redact/compact verbs, and a crisp durable-vs-trivial definition so the store stays signal. Reliable file-only path; gbrain not required. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(memory): emit durable decisions from ship/ceo/eng/spec at structured points Wires the four skills that finalize real decisions to capture them in the cross-session decision store, from their STRUCTURED outputs (never free-text scraping): - ship: the version bump (level + why) at write time - plan-ceo-review: accepted scope + verdict (branch-scoped) - plan-eng-review: the architecture verdict + key call (branch-scoped) - spec: the filed issue's core approach (issue-scoped) All emits are non-interactive, schema-correct (content in decision/rationale, source=skill, confidence 1-10), and best-effort (|| true) so a decision-log failure never blocks the workflow. Includes regen across hosts + refreshed ship golden baselines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(memory): optional gbrain --semantic recall for decision search Adds gstack-decision-search --semantic (with --query): appends a 'Related from memory' block from gbrain semantic search, scoped to the curated-memory source. Pure enhancement, reliability-first: a new lib/gstack-decision-semantic.ts is the ONLY decision module that touches gbrain and is imported lazily only on --semantic, so the reliable file path never loads gbrain code. Every path degrades to the reliable file results when gbrain is off, unconfigured, empty, or errors (never throws, 10s timeout). Built against the verified gbrain 0.42.x surface (text output [score] slug -- snippet, NOT JSON; curated-memory source resolved by worktree path, not a gstack-brain-<user> id). Deterministic-contract tests only: parser units, degrade-to-null when gbrain absent, and a fake-gbrain shim proving scope+search end-to-end. find-contradictions deferred (no verifiable CLI surface yet + curated memory not indexed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(gbrain-sync): self-heal stale autopilot lock (dead-pid) detectAutopilot treated a lock FILE as proof of life, so a crashed gbrain daemon left a stale lock that wedged every sync forever (observed: a dead pid refused --full indefinitely). Now read the holder pid (bare or JSON body) and check liveness via signal-0: ESRCH=dead → ignore the stale signal and keep checking; EPERM=alive (other user) → active. A stale lock never masks a live autopilot process. Pure decision function — does not delete the file; the caller may clean it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(review): drop stray trailing code fence in TODOS-format Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(test): align section-loading E2E testNames with their TOUCHFILES keys Pre-existing on main (v1.56.x): the two section-loading E2E tests used human-label testNames ('/ship section-loading') that don't match their slug keys ('ship-section-loading') in E2E_TOUCHFILES/E2E_TIERS. Every other E2E test uses the slug as its testName, and the TOUCHFILES completeness gate requires testName to be a registered key — so the gate was red. Align both testNames to their slug keys (also fixes tier lookup for these two periodic tests). Verified failing on a clean origin/main checkout before the fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: pre-landing review fixes (datamark, DRY, compact, coverage) Addresses the pre-landing review findings (all INFORMATIONAL, no criticals): - security: datamark resurfaced decision text at the render boundary (lib/gstack-decision.ts datamark() — neutralizes code fences, --- banners, <|role|>/</system> markers, control chars, newlines). Applied in gstack-decision-search human output so stored text can't masquerade as instructions in Context Recovery (codex hardening #3 / AC #7). --json stays raw. - DRY: extract resolveSlug/gitBranch/flagValue to lib/bin-context.ts; both decision bins use it instead of duplicating the helpers. - compact(): batch the archive append (one write, not N) and shrink the mid-compact crash window; simplify the opaque branch/issue ternary. - coverage: learnings-log injection rejection (D2A wiring), search --recent/ --scope + NaN-safe --recent, datamark-applied, unparseable lock body, compact-empty, corrupt-snapshot degrade. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(security): close adversarial-review findings in decision memory Adversarial review (Claude subagent) found a CRITICAL the specialist pass missed: - F1 (CRITICAL): 'Human:'/'Assistant:' turn-prefixes bypassed BOTH the write-time denylist AND datamark(), landing verbatim in agent context inside the trusted ACTIVE DECISIONS fence. Add 'human:' (+ 'disregard previous', 'from now on') to the shared denylist, and have datamark() neutralize Human:/Assistant:/System:/User: turn-prefixes (ZWSP) at the render boundary. - F2: datamark() only stripped ASCII C0; extend to Unicode line terminators (U+0085/2028/2029) and U+007F so 'strip newlines' actually holds. - F3: validateDecide blocked only HIGH secrets; MEDIUM-tier PII (e.g. SSN) persisted silently and synced cross-machine. The store is non-interactive (no confirm path), so fail closed on MEDIUM too. - F4: compact() was a lock-free read-modify-rewrite that could clobber a concurrent append (lost decision). Add an O_EXCL compact lock + a pre-rename size recheck that aborts untouched (skipped=true) if an append landed; caller re-runs. - F7: filterByScope unknown/garbage scope fell through to 'return true' (leaked into every context); fail conservative (false). F5 (pid reuse) and F6 (pgrep over-match) are intentionally left as-is: both fail SAFE (over-refuse sync); making them precise would introduce a fail-DANGEROUS path (allowing sync during a real autopilot). True disambiguation needs gbrain to stamp the lock with a start-time, which gstack doesn't own. F8 (compact moves history to archive) is by design. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(security): close cross-model (Codex) adversarial findings Codex adversarial review found a HIGH the Claude pass missed plus 3 mediums: - C1 (HIGH): gstack-decision-search --all returned every decide and IGNORED redact events, so a redacted secret still resurfaced via --all until compact ran. --all now excludes redacted (redact = expunge from every read path), still showing superseded history. - C-med: semantic (external gbrain) slug/snippet were printed raw — datamark them too so a gbrain hit can't spoof role markers / fences into agent context. - C4: semanticRecall fell back to an UNSCOPED gbrain search when no curated-memory source resolved, pulling code/doc corpora mislabeled as 'related decisions'. Now returns null (degrade) when there's no worktree-backed memory source. - C5: validateDecide scanned only decision/rationale/alternatives; branch and issue are stored + surfaced (raw via --json), so include them in the injection+secret scan. C2 (snapshot staleness) / C3 (compact TOCTOU residual): accepted for a single-user store — atomic appends never lose the event, rebuilds self-heal, and the compact size-recheck leaves only a sub-ms window; full append-locking would break the lock-free append design. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v1.57.5.0) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+112
-7
@@ -593,12 +593,19 @@ if [ -d "$_PROJ" ]; then
|
||||
fi
|
||||
_LATEST_CP=$(find "$_PROJ/checkpoints" -name "*.md" -type f 2>/dev/null | xargs ls -t 2>/dev/null | head -1)
|
||||
[ -n "$_LATEST_CP" ] && echo "LATEST_CHECKPOINT: $_LATEST_CP"
|
||||
if [ -f "$_PROJ/decisions.active.json" ]; then
|
||||
echo "--- ACTIVE DECISIONS (recent, scope-relevant) ---"
|
||||
~/.claude/skills/gstack/bin/gstack-decision-search --recent 5 2>/dev/null
|
||||
echo "--- END DECISIONS ---"
|
||||
fi
|
||||
echo "--- END ARTIFACTS ---"
|
||||
fi
|
||||
```
|
||||
|
||||
If artifacts are listed, read the newest useful one. If `LAST_SESSION` or `LATEST_CHECKPOINT` appears, give a 2-sentence welcome back summary. If `RECENT_PATTERN` clearly implies a next skill, suggest it once.
|
||||
|
||||
**Cross-session decisions.** If `ACTIVE DECISIONS` are listed, treat them as prior settled calls with their rationale — do not silently re-litigate them; if you're about to reverse one, say so explicitly. Reach for `~/.claude/skills/gstack/bin/gstack-decision-search` whenever a question touches a past decision ("what did we decide / why / did we try"). When you or the user make a DURABLE decision (architecture, scope, tool/vendor choice, or a reversal) — NOT a turn-level or trivial choice — log it with `~/.claude/skills/gstack/bin/gstack-decision-log` (`--supersede <id>` for a reversal). Reliable and local; gbrain not required.
|
||||
|
||||
## Writing Style (skip entirely if `EXPLAIN_LEVEL: terse` appears in the preamble echo OR the user's current message explicitly requests terse / no-explanations output)
|
||||
|
||||
Applies to AskUserQuestion, user replies, and findings. AskUserQuestion Format is structure; this is prose quality.
|
||||
@@ -752,7 +759,9 @@ When the user types `/sync-gbrain`, run this skill. Argument modes (parsed by
|
||||
the skill itself, not a dispatcher binary):
|
||||
|
||||
- `/sync-gbrain` — incremental sync (default; mtime fast-path; ~50ms steady-state)
|
||||
- `/sync-gbrain --full` — full code reindex via `gbrain reindex-code` (~25-35 min on a big repo)
|
||||
- `/sync-gbrain --full` — full code reindex via `gbrain reindex-code` (~25-35 min on a big repo). Auto-builds the call graph (`gbrain dream`) **only when it was never built**.
|
||||
- `/sync-gbrain --dream` — build this source's call graph (`gbrain code-callers`/`code-callees`) via a source-scoped `gbrain dream --source <id>` cycle; ~minutes; runs lock-free after the sync stages. Always forces, even if already built. Only produces a graph on a code-aware schema pack; otherwise the run reports a WARN explaining why the graph is still empty.
|
||||
- `/sync-gbrain --no-dream` — skip the dream cycle that `--full` would otherwise auto-run.
|
||||
- `/sync-gbrain --code-only` — only run the code stage; skip memory + brain-sync
|
||||
- `/sync-gbrain --dry-run` — preview what would sync; no writes anywhere
|
||||
- `/sync-gbrain --no-memory` / `--no-brain-sync` — selectively skip stages
|
||||
@@ -925,6 +934,76 @@ If B: continue to Step 4 with the empty-corpus state recorded.
|
||||
|
||||
---
|
||||
|
||||
## Step 3.5: Call-graph health check (offer `--dream`)
|
||||
|
||||
`gbrain code-callers` / `code-callees` (who-calls-this / what-this-calls) return
|
||||
`count: 0` until a `gbrain dream` cycle runs the `resolve_symbol_edges` phase for
|
||||
this source — not done by the code import in Step 2.
|
||||
|
||||
**One hard prerequisite:** building a call graph requires this source's active
|
||||
**schema pack to extract code symbols** (the `extract_atoms` phase). On a pack
|
||||
that doesn't declare it (e.g. `gbrain-base` / `gbrain-base-v2`), a `dream` cycle
|
||||
completes but `resolve_symbol_edges` matches nothing — the graph stays empty no
|
||||
matter how many times you run it. So "build the call graph" is only meaningful on
|
||||
a code-aware pack. The `--dream` stage detects this and reports it honestly
|
||||
(a WARN row) rather than claiming a build that didn't happen. gbrain exposes pack
|
||||
capability only at cycle runtime (no pre-flight query as of 0.41.x), so we can't
|
||||
detect it before running. `code-def` / `code-refs` need the same symbol
|
||||
extraction; they are NOT free "direct lookups" on a non-code-aware pack.
|
||||
|
||||
Detect whether this source's call graph is built via doctor's `cycle_freshness`
|
||||
check, matching the cwd `SOURCE_ID` literally:
|
||||
|
||||
```bash
|
||||
SOURCE_ID=$(grep -o '"source_id":"[^"]*"' ~/.gstack/.gbrain-sync-state.json 2>/dev/null \
|
||||
| head -1 | sed 's/.*"source_id":"//;s/".*//')
|
||||
CYCLE=$(gbrain doctor --json --fast 2>/dev/null \
|
||||
| jq -r --arg id "$SOURCE_ID" '
|
||||
(.checks[] | select(.name=="cycle_freshness")) as $c
|
||||
| if $c.status=="ok" then "completed"
|
||||
elif ($c.message | index($id)) then "never"
|
||||
else "unknown" end' 2>/dev/null || echo unknown)
|
||||
# index($id) = literal substring (NOT test() regex), matching the lib reader in
|
||||
# cycleCompleted(). A fail/warn that doesn't name this source → "unknown" (don't
|
||||
# mask other-source failures).
|
||||
echo "call graph for $SOURCE_ID: $CYCLE"
|
||||
```
|
||||
|
||||
If `CYCLE == never` AND the user did NOT pass `--dream`/`--full` AND Step 3
|
||||
`PAGES > 0`, AskUserQuestion via the format in the preamble:
|
||||
|
||||
> D2 — This repo's call graph isn't built. Build it now?
|
||||
>
|
||||
> ELI10: `gbrain code-callers`/`code-callees` (who calls this function / what it
|
||||
> calls) return nothing until the `resolve_symbol_edges` phase runs for this
|
||||
> source. `gbrain dream --source <this source>` runs it (scoped to this
|
||||
> worktree's code, takes a few minutes). It only produces a graph if this
|
||||
> source's schema pack extracts code symbols; if it doesn't, the run completes
|
||||
> but the graph stays empty and the dream row will say so.
|
||||
>
|
||||
> Recommendation: A — call-graph queries return 0 until this runs, and the code
|
||||
> index is already populated. If A comes back as a WARN ("pack does not extract
|
||||
> code symbols"), the fix is a code-aware schema pack, not re-running dream.
|
||||
>
|
||||
> Note: options differ in kind, not coverage — no completeness score.
|
||||
>
|
||||
> A) Run /sync-gbrain --dream now (recommended)
|
||||
> B) Skip — I'll run it later
|
||||
|
||||
If A: re-invoke the orchestrator with `--dream --code-only` (skips memory +
|
||||
brain-sync; the dream stage still runs because it's gated on `--dream`). Then
|
||||
report the dream stage's ACTUAL row — `OK call graph built (N edges)` vs a
|
||||
`WARN` that names why the graph is still empty (non-code-aware pack, missing
|
||||
embedding key, or 0 edges matched). Do not claim success on a WARN.
|
||||
If B: continue to Step 4 with the call-graph-not-built state recorded for the
|
||||
verdict.
|
||||
|
||||
If `CYCLE == completed` or `unknown`, do not prompt — but note `completed` means
|
||||
only that a cycle has run, not that edges exist (a non-code-aware pack reports
|
||||
`completed` with an empty graph). Step 5's verdict row surfaces the real state.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Refresh `## GBrain Search Guidance` block in CLAUDE.md
|
||||
|
||||
Capability check (per /plan-eng-review §6):
|
||||
@@ -973,12 +1052,19 @@ over Grep when the question is semantic or when you don't know the exact
|
||||
identifier yet.
|
||||
|
||||
**This worktree is pinned to a worktree-scoped code source** via the
|
||||
`.gbrain-source` file in the repo root (kubectl-style context). Any
|
||||
`gbrain code-def`, `code-refs`, `code-callers`, `code-callees`, or `query`
|
||||
call from anywhere under this worktree routes to that source by default —
|
||||
no `--source` flag needed. Conductor sibling worktrees of the same repo
|
||||
each have their own pin and their own indexed pages, so semantic results
|
||||
match the actual code on disk in this worktree.
|
||||
`.gbrain-source` file in the repo root (kubectl-style context).
|
||||
`gbrain code-def`, `code-refs`, `code-callers`, `code-callees`, `search`, and
|
||||
`query` from anywhere under this worktree route to that source by default —
|
||||
no `--source` flag needed (gbrain >= 0.41.38.0; on older gbrain the call-graph
|
||||
commands need `--source "$(cat .gbrain-source)"`). Conductor sibling worktrees
|
||||
of the same repo each have their own pin and their own indexed pages, so
|
||||
semantic results match the code on disk here.
|
||||
|
||||
Call-graph queries (`code-callers`/`code-callees`) also need the graph to be
|
||||
built first — run `/sync-gbrain --dream` (or `--full`) if they return
|
||||
`count: 0`. This only works if this source's gbrain schema pack extracts code
|
||||
symbols; on a non-code-aware pack `--dream` completes but the graph stays empty
|
||||
and reports a WARN. `code-def`/`code-refs` need the same extraction.
|
||||
|
||||
Two indexed corpora available via the `gbrain` CLI:
|
||||
- This worktree's code (auto-pinned via `.gbrain-source`).
|
||||
@@ -1043,6 +1129,7 @@ gbrain status: GREEN
|
||||
Engine .......... OK <pglite|supabase>
|
||||
Capability ...... OK write+search round-trip
|
||||
CWD source ...... OK <gstack-code-{repo_slug}> (page_count=<N>)
|
||||
Call graph ...... OK <N> edges resolved (code-callers/callees live)
|
||||
~/.gstack source. OK <gstack-brain-{user}> (page_count=<N>) — managed by /setup-gbrain
|
||||
Memory sync ..... OK <artifacts_sync_mode>
|
||||
CLAUDE.md ....... OK ## GBrain Search Guidance present
|
||||
@@ -1051,9 +1138,27 @@ gbrain status: GREEN
|
||||
Run `/sync-gbrain` again any time gbrain feels off; safe and idempotent.
|
||||
```
|
||||
|
||||
The **Call graph** row reports the most authoritative signal available:
|
||||
|
||||
1. **If a dream stage ran this invocation** (`--dream`, or `--full` auto-build),
|
||||
mirror its row verbatim — it's the ground truth for this run:
|
||||
- `OK <N> edges resolved (code-callers/callees live)`
|
||||
- `WARN dream ran but this source's schema pack does not extract code symbols
|
||||
— switch to a code-aware pack (\`gbrain schema use <pack>\`)`
|
||||
- `WARN dream ran but the embed phase failed (missing embedding key)`
|
||||
- `WARN dream ran but resolved 0 edges (no code symbols matched yet)`
|
||||
2. **Otherwise** fall back to the `CYCLE` value from Step 3.5, with honest wording
|
||||
(a completed cycle proves a cycle ran, NOT that edges exist):
|
||||
- `completed` → `OK cycle complete — code-callers/callees live IF this source's pack extracts code symbols`
|
||||
- `never` → `WARN call graph not built — run /sync-gbrain --dream`
|
||||
- `unknown` → `WARN could not probe call graph (doctor unavailable) — run /sync-gbrain --dream if code-callers returns 0`
|
||||
|
||||
Any `WARN` Call graph row flips the verdict to YELLOW.
|
||||
|
||||
If any row is YELLOW or RED, the verdict line says so and the failing rows
|
||||
surface a one-line "next action" (e.g., `Capability ...... ERR capability
|
||||
check failed; CLAUDE.md guidance block REMOVED — run /setup-gbrain to repair`).
|
||||
A `never`/`unknown` Call graph row flips the verdict to YELLOW.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+105
-7
@@ -47,7 +47,9 @@ When the user types `/sync-gbrain`, run this skill. Argument modes (parsed by
|
||||
the skill itself, not a dispatcher binary):
|
||||
|
||||
- `/sync-gbrain` — incremental sync (default; mtime fast-path; ~50ms steady-state)
|
||||
- `/sync-gbrain --full` — full code reindex via `gbrain reindex-code` (~25-35 min on a big repo)
|
||||
- `/sync-gbrain --full` — full code reindex via `gbrain reindex-code` (~25-35 min on a big repo). Auto-builds the call graph (`gbrain dream`) **only when it was never built**.
|
||||
- `/sync-gbrain --dream` — build this source's call graph (`gbrain code-callers`/`code-callees`) via a source-scoped `gbrain dream --source <id>` cycle; ~minutes; runs lock-free after the sync stages. Always forces, even if already built. Only produces a graph on a code-aware schema pack; otherwise the run reports a WARN explaining why the graph is still empty.
|
||||
- `/sync-gbrain --no-dream` — skip the dream cycle that `--full` would otherwise auto-run.
|
||||
- `/sync-gbrain --code-only` — only run the code stage; skip memory + brain-sync
|
||||
- `/sync-gbrain --dry-run` — preview what would sync; no writes anywhere
|
||||
- `/sync-gbrain --no-memory` / `--no-brain-sync` — selectively skip stages
|
||||
@@ -220,6 +222,76 @@ If B: continue to Step 4 with the empty-corpus state recorded.
|
||||
|
||||
---
|
||||
|
||||
## Step 3.5: Call-graph health check (offer `--dream`)
|
||||
|
||||
`gbrain code-callers` / `code-callees` (who-calls-this / what-this-calls) return
|
||||
`count: 0` until a `gbrain dream` cycle runs the `resolve_symbol_edges` phase for
|
||||
this source — not done by the code import in Step 2.
|
||||
|
||||
**One hard prerequisite:** building a call graph requires this source's active
|
||||
**schema pack to extract code symbols** (the `extract_atoms` phase). On a pack
|
||||
that doesn't declare it (e.g. `gbrain-base` / `gbrain-base-v2`), a `dream` cycle
|
||||
completes but `resolve_symbol_edges` matches nothing — the graph stays empty no
|
||||
matter how many times you run it. So "build the call graph" is only meaningful on
|
||||
a code-aware pack. The `--dream` stage detects this and reports it honestly
|
||||
(a WARN row) rather than claiming a build that didn't happen. gbrain exposes pack
|
||||
capability only at cycle runtime (no pre-flight query as of 0.41.x), so we can't
|
||||
detect it before running. `code-def` / `code-refs` need the same symbol
|
||||
extraction; they are NOT free "direct lookups" on a non-code-aware pack.
|
||||
|
||||
Detect whether this source's call graph is built via doctor's `cycle_freshness`
|
||||
check, matching the cwd `SOURCE_ID` literally:
|
||||
|
||||
```bash
|
||||
SOURCE_ID=$(grep -o '"source_id":"[^"]*"' ~/.gstack/.gbrain-sync-state.json 2>/dev/null \
|
||||
| head -1 | sed 's/.*"source_id":"//;s/".*//')
|
||||
CYCLE=$(gbrain doctor --json --fast 2>/dev/null \
|
||||
| jq -r --arg id "$SOURCE_ID" '
|
||||
(.checks[] | select(.name=="cycle_freshness")) as $c
|
||||
| if $c.status=="ok" then "completed"
|
||||
elif ($c.message | index($id)) then "never"
|
||||
else "unknown" end' 2>/dev/null || echo unknown)
|
||||
# index($id) = literal substring (NOT test() regex), matching the lib reader in
|
||||
# cycleCompleted(). A fail/warn that doesn't name this source → "unknown" (don't
|
||||
# mask other-source failures).
|
||||
echo "call graph for $SOURCE_ID: $CYCLE"
|
||||
```
|
||||
|
||||
If `CYCLE == never` AND the user did NOT pass `--dream`/`--full` AND Step 3
|
||||
`PAGES > 0`, AskUserQuestion via the format in the preamble:
|
||||
|
||||
> D2 — This repo's call graph isn't built. Build it now?
|
||||
>
|
||||
> ELI10: `gbrain code-callers`/`code-callees` (who calls this function / what it
|
||||
> calls) return nothing until the `resolve_symbol_edges` phase runs for this
|
||||
> source. `gbrain dream --source <this source>` runs it (scoped to this
|
||||
> worktree's code, takes a few minutes). It only produces a graph if this
|
||||
> source's schema pack extracts code symbols; if it doesn't, the run completes
|
||||
> but the graph stays empty and the dream row will say so.
|
||||
>
|
||||
> Recommendation: A — call-graph queries return 0 until this runs, and the code
|
||||
> index is already populated. If A comes back as a WARN ("pack does not extract
|
||||
> code symbols"), the fix is a code-aware schema pack, not re-running dream.
|
||||
>
|
||||
> Note: options differ in kind, not coverage — no completeness score.
|
||||
>
|
||||
> A) Run /sync-gbrain --dream now (recommended)
|
||||
> B) Skip — I'll run it later
|
||||
|
||||
If A: re-invoke the orchestrator with `--dream --code-only` (skips memory +
|
||||
brain-sync; the dream stage still runs because it's gated on `--dream`). Then
|
||||
report the dream stage's ACTUAL row — `OK call graph built (N edges)` vs a
|
||||
`WARN` that names why the graph is still empty (non-code-aware pack, missing
|
||||
embedding key, or 0 edges matched). Do not claim success on a WARN.
|
||||
If B: continue to Step 4 with the call-graph-not-built state recorded for the
|
||||
verdict.
|
||||
|
||||
If `CYCLE == completed` or `unknown`, do not prompt — but note `completed` means
|
||||
only that a cycle has run, not that edges exist (a non-code-aware pack reports
|
||||
`completed` with an empty graph). Step 5's verdict row surfaces the real state.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Refresh `## GBrain Search Guidance` block in CLAUDE.md
|
||||
|
||||
Capability check (per /plan-eng-review §6):
|
||||
@@ -268,12 +340,19 @@ over Grep when the question is semantic or when you don't know the exact
|
||||
identifier yet.
|
||||
|
||||
**This worktree is pinned to a worktree-scoped code source** via the
|
||||
`.gbrain-source` file in the repo root (kubectl-style context). Any
|
||||
`gbrain code-def`, `code-refs`, `code-callers`, `code-callees`, or `query`
|
||||
call from anywhere under this worktree routes to that source by default —
|
||||
no `--source` flag needed. Conductor sibling worktrees of the same repo
|
||||
each have their own pin and their own indexed pages, so semantic results
|
||||
match the actual code on disk in this worktree.
|
||||
`.gbrain-source` file in the repo root (kubectl-style context).
|
||||
`gbrain code-def`, `code-refs`, `code-callers`, `code-callees`, `search`, and
|
||||
`query` from anywhere under this worktree route to that source by default —
|
||||
no `--source` flag needed (gbrain >= 0.41.38.0; on older gbrain the call-graph
|
||||
commands need `--source "$(cat .gbrain-source)"`). Conductor sibling worktrees
|
||||
of the same repo each have their own pin and their own indexed pages, so
|
||||
semantic results match the code on disk here.
|
||||
|
||||
Call-graph queries (`code-callers`/`code-callees`) also need the graph to be
|
||||
built first — run `/sync-gbrain --dream` (or `--full`) if they return
|
||||
`count: 0`. This only works if this source's gbrain schema pack extracts code
|
||||
symbols; on a non-code-aware pack `--dream` completes but the graph stays empty
|
||||
and reports a WARN. `code-def`/`code-refs` need the same extraction.
|
||||
|
||||
Two indexed corpora available via the `gbrain` CLI:
|
||||
- This worktree's code (auto-pinned via `.gbrain-source`).
|
||||
@@ -338,6 +417,7 @@ gbrain status: GREEN
|
||||
Engine .......... OK <pglite|supabase>
|
||||
Capability ...... OK write+search round-trip
|
||||
CWD source ...... OK <gstack-code-{repo_slug}> (page_count=<N>)
|
||||
Call graph ...... OK <N> edges resolved (code-callers/callees live)
|
||||
~/.gstack source. OK <gstack-brain-{user}> (page_count=<N>) — managed by /setup-gbrain
|
||||
Memory sync ..... OK <artifacts_sync_mode>
|
||||
CLAUDE.md ....... OK ## GBrain Search Guidance present
|
||||
@@ -346,9 +426,27 @@ gbrain status: GREEN
|
||||
Run `/sync-gbrain` again any time gbrain feels off; safe and idempotent.
|
||||
```
|
||||
|
||||
The **Call graph** row reports the most authoritative signal available:
|
||||
|
||||
1. **If a dream stage ran this invocation** (`--dream`, or `--full` auto-build),
|
||||
mirror its row verbatim — it's the ground truth for this run:
|
||||
- `OK <N> edges resolved (code-callers/callees live)`
|
||||
- `WARN dream ran but this source's schema pack does not extract code symbols
|
||||
— switch to a code-aware pack (\`gbrain schema use <pack>\`)`
|
||||
- `WARN dream ran but the embed phase failed (missing embedding key)`
|
||||
- `WARN dream ran but resolved 0 edges (no code symbols matched yet)`
|
||||
2. **Otherwise** fall back to the `CYCLE` value from Step 3.5, with honest wording
|
||||
(a completed cycle proves a cycle ran, NOT that edges exist):
|
||||
- `completed` → `OK cycle complete — code-callers/callees live IF this source's pack extracts code symbols`
|
||||
- `never` → `WARN call graph not built — run /sync-gbrain --dream`
|
||||
- `unknown` → `WARN could not probe call graph (doctor unavailable) — run /sync-gbrain --dream if code-callers returns 0`
|
||||
|
||||
Any `WARN` Call graph row flips the verdict to YELLOW.
|
||||
|
||||
If any row is YELLOW or RED, the verdict line says so and the failing rows
|
||||
surface a one-line "next action" (e.g., `Capability ...... ERR capability
|
||||
check failed; CLAUDE.md guidance block REMOVED — run /setup-gbrain to repair`).
|
||||
A `never`/`unknown` Call graph row flips the verdict to YELLOW.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user