Merge origin/master (v0.33.3.0 foundation) into garrytan/managua-v3

Master landed PR #934 (v0.33.3.0 code intelligence foundation: W0a
source-routing fix + W0b CLI source-scoping flip + W0c within-file
two-pass symbol resolver + W3 MCP exposure of code_callers/callees/def/
refs + pre-w0 eval harness). My branch already contained all of that
work via the original miami merge at the start of this session; the
conflicts are version-label drift (my comments said v0.33.2, master
shipped v0.33.3) and a few additive cases.

Conflict resolutions:
- VERSION / package.json: kept 0.34.0.0 (higher semver wins).
- CHANGELOG.md: both entries preserved. Order is v0.34.0.0 → v0.33.3.0 →
  v0.33.2.1 → v0.33.2.0 → v0.33.1.1 → v0.33.1.0; chronologically
  reasonable with newest-on-top.
- src/core/chunkers/symbol-resolver.ts (add/add): kept my version. Diff
  was W1+W2 documentation block + bumped EDGE_EXTRACTOR_VERSION_TS
  ('2026-05-14T01:00:00Z' vs master's '2026-05-11T00:00:00Z') so the
  next dream cycle re-walks every chunk and picks up qualified-name
  matches from the W1 receiver-type resolution + W2 imports/references.
- src/core/cycle.ts, operations-descriptions.ts, src/commands/eval.ts,
  test/core/cycle.serial.test.ts, test/e2e/cycle.test.ts, test/e2e/
  dream-cycle-phase-order-pglite.test.ts: pure version-string drift
  (v0.33.2 → v0.33.3 in comments). Took master's labels — that's the
  shipped version number.
- src/core/operations.ts: 4 zones merged.
  1. Kept my "v0.34 (Codex finding #2) sourceId resolution" comment.
  2. Took master's wording on the hybridSearchCached comment (functionally
     identical).
  3. Kept my new code_blast + code_flow + code_traversal_cache_clear op
     definitions (W3 + W3b — master doesn't have these).
  4. Deduplicated the ops registration: kept master's v0.33.3 label +
     my W3 + W3b ops registered alongside the foundation ones.

Verification:
- `bun run typecheck` clean
- `bun run verify` clean (all 11 pre-checks pass)
- Migrations v50→v59 schema still valid (no new master migrations in
  this merge; v55-v57 search-lite + v58-v59 v0.34 already landed
  pre-merge in commit f25b674f)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-14 18:22:04 -07:00
co-authored by Claude Opus 4.7
10 changed files with 206 additions and 16 deletions
+8
View File
@@ -74,6 +74,14 @@ To exercise the new W1/W2 edge shapes on an existing brain:
- contents of `~/.gbrain/upgrade-errors.jsonl` if it exists
- which step broke
## [0.33.3.0] - 2026-05-12
**Code intelligence ships to agents. Plan-mode subagents stop falling through to grep.**
**`code_callers`, `code_callees`, `code_def`, `code_refs` are MCP-exposed with resolver-grade descriptions.**
Pre-v0.33.3 the four code-intelligence commands from v0.20+ Cathedral II lived in `CLI_ONLY` at `cli.ts:30`. An agent running through MCP saw `query`/`search` but no structural retrieval, so it grepped, missed callers in string literals, shipped plans with broken call chains, and got caught in review. v0.33.3 closes that gap and lays the foundation work that v0.34 Cathedral III (recursive blast/flow + Leiden clusters + wiki) will build on top of.
This release was scoped after Codex's outside-voice review caught two load-bearing premise gaps in the original v0.34 plan: the call graph stored bare callee tokens (not qualified names), and source routing was already broken in `query` and `two-pass.ts`. Both are fixed here before any user-facing recursive op ships.
## [0.33.2.1] - 2026-05-14
**Doc-only: name the fork-PR escape hatch so AI-authored PRs don't drop their CI secrets.**
+133
View File
@@ -0,0 +1,133 @@
# v0.33.3.0 migration — code intelligence foundation + MCP-exposed code ops
`gbrain upgrade` runs `gbrain apply-migrations` automatically. Most users
won't need to do anything else. If you hit issues or want to verify the
upgrade succeeded, run the steps below.
## What changed
v0.33.3.0 is the foundation pre-release for v0.34 Cathedral III. It ships the
agent-facing MCP surface for the v0.20+ tree-sitter call graph plus the
foundation fixes needed before recursive blast/flow and Leiden clusters
land in v0.34.
**MCP exposure (NEW for agents):**
Four code-intelligence ops graduated from `CLI_ONLY` to first-class MCP ops:
- `code_callers(symbol, [limit, source_id, all_sources])` — find every
caller of a symbol. Use BEFORE editing any function.
- `code_callees(symbol, [limit, source_id, all_sources])` — trace what a
function calls. Use when debugging unexpected behavior.
- `code_def(symbol, [limit, lang])` — find a symbol's definition site(s).
- `code_refs(symbol, [limit, lang])` — find every reference (comments,
imports, type annotations, call sites).
The MCP tool descriptions are resolver-grade — they tell agents WHEN to
reach for each op so plan-mode subagents route to structural retrieval
instead of falling through to text search.
**Foundation fixes:**
- **Source-routing fix** (Codex finding #2): `query` op now threads
`ctx.sourceId` to `hybridSearch`. Two-pass retrieval honors `sourceId`
at both the `nearSymbol` lookup and unresolved-edge resolution sites.
Multi-source brains stop cross-contaminating structural retrieval.
- **CLI source-scoping default flipped** (Codex finding #7): `gbrain
code-callers <symbol>` without `--source` now resolves to your brain's
default source (the only source on single-source brains; explicit
error listing valid ids on multi-source brains). Pre-v0.33 the
default silently was global — multi-repo brains cross-resolved every
same-named symbol.
**Within-file two-pass symbol resolution:**
A new cycle phase `resolve_symbol_edges` runs after `extract` on every
autopilot tick. It walks `content_chunks.edges_backfilled_at IS NULL`
chunks in 200-row batches, matching each `to_symbol_qualified` against
the same-file `symbol_name_qualified`, and writes the outcome to
`code_edges_symbol.edge_metadata`:
- `{resolved_chunk_id: N}` — one unambiguous match
- `{ambiguous: true, candidates: [...]}` — 2+ matches in the same file
- (no metadata change) — zero matches; caller's two-pass walk handles
cross-file resolution
## Schema migration v51 — what it adds
```sql
ALTER TABLE content_chunks ADD COLUMN IF NOT EXISTS edges_backfilled_at TIMESTAMPTZ;
CREATE INDEX IF NOT EXISTS idx_code_edges_symbol_resolver
ON code_edges_symbol (source_id, to_symbol_qualified);
CREATE INDEX IF NOT EXISTS idx_content_chunks_symbol_lookup
ON content_chunks (page_id, symbol_name_qualified)
WHERE symbol_name_qualified IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_content_chunks_edges_backfill
ON content_chunks (edges_backfilled_at)
WHERE edges_backfilled_at IS NULL;
```
All idempotent. Existing brains see the column + indexes added; the resolver
walks the corpus lazily over the next several autopilot cycles.
**First-run cost:** ~5-15 minutes of background resolver work on a 10K-chunk
brain spread across cycles (~2000 chunks/tick). No user-visible interruption
unless you watch `gbrain doctor`'s autopilot stats.
## What the agent should do
If you're an agent (Claude Code, OpenClaw, Cursor) running through MCP:
1. **Before editing any function**, reach for `code_callers` to surface
every caller. The MCP tool description tells you this — follow it.
2. **When tracing execution**, reach for `code_callees` from the entry
point.
3. **When looking up a definition**, reach for `code_def` not `search`.
It returns line numbers + snippet directly.
4. **When planning a rename**, reach for `code_refs` to find every
literal mention.
5. **Honor source scoping**: multi-source brains require explicit
`source_id` or `all_sources: true`. Single-source brains auto-resolve.
The previously available CLI commands (`gbrain code-callers <symbol>`)
still work but now default to source-scoped on multi-source brains.
Pass `--all-sources` to get the pre-v0.33 cross-source default.
## Verification
```bash
# 1. Confirm upgrade
gbrain --version # 0.33.3.0
# 2. Confirm migration v51 ran
gbrain apply-migrations --status | grep edges_backfilled_at_v0_34
# 3. Confirm the new MCP ops are exposed
gbrain --tools-json | jq '.[] | select(.name | startswith("code_")) | .name'
# Should show: code_callers, code_callees, code_def, code_refs
# 4. Try one end-to-end (replace `parseMarkdown` with any symbol you index)
gbrain code-callers parseMarkdown --json | jq '.count'
# 5. (Optional) inspect the resolver's per-source progress
gbrain doctor --json | jq '.checks | to_entries[] | select(.key | contains("code"))'
```
## Rollback
If you need to roll back: `git checkout` an older binary and run
`gbrain doctor`. The v51 column + indexes stay in your DB (no-op for
older binaries; idempotent re-add on upgrade). No data is destroyed.
## What v0.34 will add on top
Per the v0.34 design doc (`/Users/garrytan/.gstack/projects/garrytan-gbrain/garrytan-garrytan-miami-design-20260510-194145.md`):
- Recursive `code_blast` / `code_flow` MCP ops on top of the resolved graph
- Leiden community detection (`code_clusters_list`, `code_cluster_get`) +
inline mermaid diagrams
- `gbrain wiki` zero-LLM aggregator CLI
- Per-op graph-traversal eval metrics extending v0.25.0's eval-capture
- `imports` and `references` edge types for JS/TS/TSX + Python
- Receiver-type scope walkers (e.g. `obj.method()` → `Class.method`)
+1 -1
View File
@@ -46,7 +46,7 @@ export async function runEvalCommand(engine: BrainEngine, args: string[]): Promi
process.exit(await runEvalCrossModal(args.slice(1)));
}
if (sub === 'code-retrieval') {
// v0.33.2 pre-w0 — code-retrieval baseline / gate harness. Needs a brain
// v0.33.3 pre-w0 — code-retrieval baseline / gate harness. Needs a brain
// for the baseline (BaselineStrategy calls hybridSearch); --compare
// mode reads JSON only but the engine is already connected by this
// dispatcher.
+5 -5
View File
@@ -71,7 +71,7 @@ export const ALL_PHASES: CyclePhase[] = [
// The empty-fence guard refuses to run if pre-v51 legacy facts are
// pending the v0_32_2 backfill (Codex R2-#7).
'extract_facts',
// v0.33.2 W0c — within-file two-pass symbol resolution. Runs AFTER
// v0.33.3 W0c — within-file two-pass symbol resolution. Runs AFTER
// extract + extract_facts so any code edges sync emitted (still bare-token)
// get resolved into {resolved_chunk_id: N} / {ambiguous: true,
// candidates: [...]} edge_metadata entries before downstream phases read
@@ -112,7 +112,7 @@ const NEEDS_LOCK_PHASES: ReadonlySet<CyclePhase> = new Set([
'extract',
// v0.32.2 — wipes + re-inserts facts per affected page.
'extract_facts',
// v0.33.2 W0c — writes code_edges_symbol.edge_metadata + content_chunks.edges_backfilled_at.
// v0.33.3 W0c — writes code_edges_symbol.edge_metadata + content_chunks.edges_backfilled_at.
'resolve_symbol_edges',
'patterns',
// v0.29 — writes pages.emotional_weight column.
@@ -731,13 +731,13 @@ async function runPhaseExtractFacts(
}
/**
* v0.33.2 W0c — resolve_symbol_edges phase.
* v0.33.3 W0c — resolve_symbol_edges phase.
*
* Walks at most BATCH_SIZE*10 chunks per invocation where
* `edges_backfilled_at` is NULL or older than EDGE_EXTRACTOR_VERSION_TS.
* Resumable across cycles via the watermark; quick-cycle compatible.
*
* Source scoping: walks every registered source. Pre-v0.33.2 silently
* Source scoping: walks every registered source. Pre-v0.33.3 silently
* crossed sources; now each source is walked independently so symbol
* resolution stays within its source boundary (matches the W0a fix).
*/
@@ -1169,7 +1169,7 @@ export async function runCycle(
await safeYield(opts.yieldBetweenPhases);
}
// ── v0.33.2 W0c: resolve_symbol_edges (between extract_facts + patterns) ──
// ── v0.33.3 W0c: resolve_symbol_edges (between extract_facts + patterns) ──
// Walks chunks whose edges_backfilled_at is null/stale. Resumable
// across cycles via the watermark. Quick-cycle compatible — caps at
// BATCH_SIZE * 10 chunks per invocation so a 60s watchdog tick stays
+49
View File
@@ -2969,6 +2969,55 @@ export const MIGRATIONS: Migration[] = [
ON search_telemetry (date DESC);
`,
},
{
version: 58,
name: 'edges_backfilled_at_v0_33_3',
// v0.33.3 W0c — resumable symbol-resolution backfill watermark.
// (Originally claimed v55; renumbered to v58 on merge with master's
// v55/v56/v57 search-lite migrations.)
//
// The within-file two-pass resolver (src/core/chunkers/symbol-resolver.ts)
// walks every content_chunks row that has unresolved edges
// (rows in code_edges_symbol whose to_symbol_qualified has not been
// matched against same-file symbol_name_qualified yet) and writes the
// resolution outcome to code_edges_symbol.edge_metadata. On a 96K-chunk
// brain that is a 5-15 minute backfill the first time it runs.
//
// `edges_backfilled_at` is the resume watermark. Backfill runs in
// 200-chunk batches; on batch success the column is set to NOW() for
// every chunk in the batch. Resume picks up chunks where the watermark
// is NULL or older than EDGE_EXTRACTOR_VERSION_TS (a constant bumped
// when the extractor's shape changes). Crashes lose at most one batch.
//
// Composite + partial indexes for the lookup hot path (D11 from eng
// review):
// - idx_code_edges_symbol_resolver (source_id, to_symbol_qualified)
// — every code_edges_symbol row is unresolved by construction
// (the table has no to_chunk_id column; that lives on code_edges_chunk).
// This composite index supports the resolver's per-source lookups.
// - idx_content_chunks_symbol_lookup (page_id, symbol_name_qualified)
// WHERE symbol_name_qualified IS NOT NULL — file-batched lookup
// used by both the resolver and the cluster recompute phase (W4-5).
// - idx_content_chunks_edges_backfill (edges_backfilled_at)
// WHERE edges_backfilled_at IS NULL — find unresumed rows quickly.
//
// Idempotent: IF NOT EXISTS on column + indexes. Backfill itself runs
// separately via the resolve_symbol_edges cycle phase.
sql: `
ALTER TABLE content_chunks ADD COLUMN IF NOT EXISTS edges_backfilled_at TIMESTAMPTZ;
CREATE INDEX IF NOT EXISTS idx_code_edges_symbol_resolver
ON code_edges_symbol (source_id, to_symbol_qualified);
CREATE INDEX IF NOT EXISTS idx_content_chunks_symbol_lookup
ON content_chunks (page_id, symbol_name_qualified)
WHERE symbol_name_qualified IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_content_chunks_edges_backfill
ON content_chunks (edges_backfilled_at)
WHERE edges_backfilled_at IS NULL;
`,
},
];
export const LATEST_VERSION = MIGRATIONS.length > 0
+2 -2
View File
@@ -91,8 +91,8 @@ export const FIND_CONTRADICTIONS_DESCRIPTION =
"`gbrain eval suspected-contradictions` for that.";
// ──────────────────────────────────────────────────────────────────────────────
// v0.33.2 Cathedral III foundation — code-intelligence ops (MCP-exposed).
// Pre-v0.33.2 the callers/callees/def/refs commands were CLI-only — agents
// v0.33.3 Cathedral III foundation — code-intelligence ops (MCP-exposed).
// Pre-v0.33.3 the callers/callees/def/refs commands were CLI-only — agents
// reached for grep because the MCP surface didn't expose them. These
// descriptions are resolver-grade so the LLM tool-selection prompt routes
// plan-mode questions straight to the right op.
+4 -4
View File
@@ -1118,9 +1118,9 @@ const query: Operation = {
? undefined
: sourceIdParam
: ctx.sourceId;
// v0.32.x search-lite: route through hybridSearchCached so semantic cache
// + token budget + intent weighting fire automatically. Plain hybridSearch
// remains the bare API for callers that opt out.
// v0.32.x search-lite: route the query op through hybridSearchCached so
// semantic cache + token budget + intent weighting fire automatically.
// Plain hybridSearch remains the bare API for callers that opt out.
const results = await hybridSearchCached(ctx.engine, queryText, {
limit: (p.limit as number) || 20,
offset: (p.offset as number) || 0,
@@ -3079,7 +3079,7 @@ export const operations: Operation[] = [
find_contradictions,
// v0.33: expertise + relationship-proximity routing
find_experts,
// v0.33.2: Cathedral III code-intelligence (MCP-exposed; were CLI_ONLY pre-v0.33.2)
// v0.33.3: Cathedral III code-intelligence (MCP-exposed; were CLI_ONLY pre-v0.33.3)
code_callers, code_callees, code_def, code_refs,
// v0.34 W3: recursive code_blast + code_flow
code_blast, code_flow,
+2 -2
View File
@@ -381,7 +381,7 @@ describe('runCycle — yieldBetweenPhases hook', () => {
// v0.29: 10 phases (added `recompute_emotional_weight`).
// v0.31: 11 phases (added `consolidate` between recompute and embed).
// v0.32.2: 12 phases (added `extract_facts` between extract and patterns).
// v0.33.2: 13 phases (added `resolve_symbol_edges` between extract_facts and patterns) → 13 yield calls.
// v0.33.3: 13 phases (added `resolve_symbol_edges` between extract_facts and patterns) → 13 yield calls.
expect(hookCalls).toBe(13);
});
@@ -392,7 +392,7 @@ describe('runCycle — yieldBetweenPhases hook', () => {
throw new Error('synthetic hook error');
},
});
// v0.33.2: 13 phases (v0.32.2's 12 + resolve_symbol_edges).
// v0.33.3: 13 phases (v0.32.2's 12 + resolve_symbol_edges).
expect(report.phases.length).toBe(13);
});
});
+1 -1
View File
@@ -104,7 +104,7 @@ describeE2E('E2E: runCycle against real Postgres', () => {
// v0.29 = 10 (added `recompute_emotional_weight` between patterns and embed)
// v0.31 = 11 (added `consolidate` between recompute_emotional_weight and embed)
// v0.32.2 = 12 (added `extract_facts` between extract and patterns)
// v0.33.2 = 13 (added `resolve_symbol_edges` between extract_facts and patterns)
// v0.33.3 = 13 (added `resolve_symbol_edges` between extract_facts and patterns)
expect(report.phases.length).toBe(13);
// Nothing got written.
@@ -105,7 +105,7 @@ const EXPECTED_PHASES: CyclePhase[] = [
'synthesize',
'extract',
'extract_facts', // v0.32.2 — reconcile fence → DB facts index
'resolve_symbol_edges', // v0.33.2 — within-file symbol resolution
'resolve_symbol_edges', // v0.33.3 — within-file symbol resolution
'patterns',
'recompute_emotional_weight', // v0.29
'consolidate', // v0.31