Conversation
Tree-sitter-based code chunker for TS/JS/Python/Ruby/Go. Splits code at semantic boundaries (functions, classes, types, exports). Each chunk includes a structured header for embedding context. Multi-repo config: gbrain repos add/list/remove, gbrain sync --all. Strategy-aware sync: markdown (default), code, or auto. New PageType 'code' for code file pages. This is Layer 0 of the v0.18.0 code-indexing plan (see ~/.claude/plans cathedral plan). Subsequent layers add: tests, bun --compile WASM embedding + CI guard (A1), schema migrations v16 (pages.repo_name) + v17 (content_chunks code metadata), per-repo sync bookmarks, runCycle multi-repo, Chonkie chunker parity (E2a), incremental chunking (E2), doc↔impl linking (E1), markdown fence extraction (E3), symbol navigation commands (code-def, code-refs), cost preview, BrainBench code category, CHANGELOG, migration file, docs. Backward compatible: no config changes = existing behavior preserved.
…n bump Adds the structured error envelope (src/core/errors.ts) that downstream v0.19.0 commands (code-def, code-refs, sync --all cost preview, importCodeFile) all hand back to agents. The envelope follows the v0.17.0 CycleReport.PhaseResult.error shape so agent-consumption stays consistent across every gbrain surface. Test coverage for Wintermute's baseline (added in Layer 0): - test/errors.test.ts — envelope helper + GBrainError + serializeError - test/multi-repo.test.ts — config CRUD, dedup, file permissions - test/sync-strategy.test.ts — isSyncable strategy matrix + include/exclude globs + slugifyCodePath + pathToSlug with pageKind Bug fixes uncovered by the new tests: - src/core/sync.ts: globToRegex handles `src/**/*.ts` matching `src/foo.ts` (zero intermediate dirs). `**/` now compiles to `(?:.*/)?` instead of `.*/`. Also `?` now matches only non-slash chars (was `.`). - src/core/config.ts: configDir() respects GBRAIN_HOME env override so tests can isolate ~/.gbrain/. Matches GBRAIN_AUDIT_DIR convention. Bun's os.homedir() ignores $HOME on macOS, so we need an explicit override variable. Version bump: package.json 0.18.2 → 0.19.0. v0.18.0-2 were already released (multi-source brains + RLS + migration hardening), so the next free minor for code indexing is 0.19.0. Wintermute's baseline author label of 0.16.4 had been stale since v0.17.0 shipped; no user-visible regression from the jump. Per the rebased cathedral plan: Wintermute's multi-repo.ts and repos CLI are preserved at the baseline but will be superseded in Layer 4 by the v0.18.0 sources system (src/core/source-resolver.ts, src/commands/sources.ts). multi-repo tests stay valid for the baseline and will be removed alongside the code they cover.
The single highest-risk change in v0.19.0 code indexing. Before this, the
chunker loaded WASMs via `new URL('../../../node_modules/...', import.meta.url)`
which silently breaks in the compiled binary (no node_modules at runtime).
Users would see degraded chunking quality with no error, just fallback-
recursive chunks instead of real semantic chunks. Codex flagged this as
the #1 silent-failure mode.
Mechanics:
- `src/assets/wasm/tree-sitter.wasm` + 36 grammar WASMs committed to the
repo (50MB). Not a small check-in, but the alternative is a postinstall
script that runs before every dev bun run and fails fragile-ly on
network errors.
- `src/core/chunkers/code.ts` uses Bun's `import ... with { type: 'file' }`
import attribute. At runtime the imported value is a file path — the
actual repo path in dev, a bundler-synthesized path in the compiled
binary. The tree-sitter runtime's `Language.load(path)` reads it the
same way in both cases.
- Layer 2 keeps the 6-language support Wintermute shipped (TS/TSX/JS/Py/
Rb/Go). Layer 5 (E2a chunker parity) expands to all 36 bundled grammars.
- CHUNKER_VERSION=2 constant introduced. importCodeFile will fold this
into content_hash in Layer 3 so chunker-shape changes across releases
force clean re-chunks without the user needing `sync --force`.
CI guard — `scripts/check-wasm-embedded.sh` + `scripts/chunker-smoketest.ts`:
- Compiles a smoketest binary that calls chunkCodeText on a known TS
snippet.
- Asserts the output has `has_real_symbols: true`, a `[TypeScript]`
language tag, and the expected symbol name.
- If the chunker silently falls through to recursive chunks, the
assertions fail the build.
- Wired into `bun test` via package.json script pipeline. Also exposed
as `bun run check:wasm` for standalone invocation.
Verification:
- Dev: `bun -e '...'` smoke test returns 2 chunks with correct symbol
names in under 100ms.
- Compiled: `bash scripts/check-wasm-embedded.sh` passes end to end.
- Binary size: the gbrain binary grows from ~90MB to ~140MB, dominated
by the 50MB of grammar WASMs. Still well within normal for CLIs that
ship a language runtime.
…metadata
Adds two migrations to unblock C6/C7 (query --lang, code-def, code-refs)
and the orphans/auto-link branching in later layers.
v25 (pages_page_kind):
- ALTER TABLE pages ADD COLUMN page_kind TEXT NOT NULL DEFAULT 'markdown'
CHECK (page_kind IN ('markdown','code'))
- Postgres path uses ADD CONSTRAINT ... NOT VALID + VALIDATE CONSTRAINT
in a separate statement so tables with millions of pages don't hold a
write lock during the initial check. PGLite has no concurrent writers,
so its variant uses the simpler ALTER TABLE pattern.
- Existing rows carry DEFAULT 'markdown' — pre-v0.19 brains were
markdown-only by definition.
v26 (content_chunks_code_metadata):
- ALTER TABLE content_chunks ADD COLUMN language, symbol_name,
symbol_type, start_line, end_line (all nullable).
- Two partial indexes: idx_chunks_symbol_name WHERE symbol_name IS NOT
NULL, and idx_chunks_language WHERE language IS NOT NULL. Only code
chunks populate these columns, so partial indexes stay small even on
a 50K-chunk brain with mixed markdown+code.
- Markdown chunks leave all five columns NULL. Only importCodeFile
populates them, from the tree-sitter AST via chunkCodeText.
Wiring (both engines):
- PageInput gains `page_kind?: PageKind` ('markdown' | 'code'). Defaults
to 'markdown' when omitted so existing callers don't change. putPage
on both engines writes it through, with ON CONFLICT DO UPDATE updating
page_kind alongside the other fields.
- ChunkInput gains language, symbol_name, symbol_type, start_line,
end_line (all optional). upsertChunks on both engines writes them
through. Existing markdown call sites pass nothing and get NULLs —
zero behavior change for markdown pages.
importCodeFile updates:
- Sets page_kind='code' on the PageInput.
- Populates chunk metadata from the chunker's CodeChunk.metadata for
every chunk it persists. Columns line up 1:1 with the tree-sitter AST
output already produced by the chunker.
- Folds CHUNKER_VERSION=2 into content_hash so chunker shape changes
across releases force clean re-chunks without `sync --force`. The
hash was previously {title, type, content, lang} — now also
chunker_version.
Fresh-install path (src/schema.sql + pglite-schema.ts):
- Both include the page_kind column + CHECK constraint.
- Both include the five new content_chunks columns.
- Both ship the partial indexes so new brains have the same query
performance as migrated brains. Ran `bun run build:schema` to
regenerate src/core/schema-embedded.ts from schema.sql.
Naming: renamed our new Error subclass in src/core/errors.ts from
GBrainError to StructuredAgentError. The legacy GBrainError in
src/core/types.ts predates this change and has a different shape
(positional problem/cause/fix arguments) — keeping both under the same
name was inviting a year of import ambiguity. New v0.19.0 surfaces use
StructuredAgentError + the serializeError() helper.
Tests:
- test/migrations-v0_19_0.test.ts — 12 cases. Covers: MIGRATIONS array
shape (v25/v26 presence, NOT VALID pattern on Postgres, partial
index WHERE clauses), fresh-install schema (page_kind default, CHECK
constraint rejects invalid values, chunk metadata nullable), putPage
round-trip (markdown default + code explicit), upsertChunks
round-trip (code metadata preserved + markdown chunks leave NULLs).
- All 139 existing + new unit tests pass on PGLite (1.5 sec).
Replaces Wintermute's short-lived repos abstraction with the v0.18.0 sources subsystem. Codex flagged this during plan review: v0.18.0's sources table had already shipped the right shape (per-source last_commit, federated search config, RLS-friendly) while Wintermute coded against a ~/.gbrain/config.json repos array. Two systems solving one problem. Keep the surface, swap the backend: - src/cli.ts: `gbrain repos` routes through runSources with a one-line deprecation nudge on stderr. Scripts like `gbrain repos list` and `gbrain repos add .` keep working against the sources table. Removed the pre-engine-connect branch and added a case inside the handleCliOnly switch so repos gets the DB connection it now needs. - src/cli.ts help text: new SOURCES section replaces MULTI-REPO. References the canonical `sources` commands with `repos` tagged DEPRECATED. sync --all — was iterating ~/.gbrain/config.json repos; now iterates sources rows with local_path IS NOT NULL: - Reads id, name, local_path, config jsonb via executeRaw. - Honors config.syncEnabled=false (matching Wintermute's opt-out). - Honors config.strategy for per-source markdown/code/auto filtering. - Passes sourceId through to performSync so last_commit tracking lands on the right sources row (was clobbering a global bookmark before). Deletions: - src/core/multi-repo.ts deleted (120 lines of config CRUD now handled by sources table + RLS). - src/commands/repos.ts deleted (121 lines of CLI parsing now handled by src/commands/sources.ts). - test/multi-repo.test.ts deleted (25 tests against the deleted module; the schema-backed behavior is covered by test/sources.test.ts from v0.18.0 + test/repos-alias.test.ts added here). - src/core/config.ts: removed the `repos` field from GBrainConfig. Legacy installs with `repos` in ~/.gbrain/config.json will see that key ignored; no migration written because zero users are on that path (Wintermute's commit never shipped on master). Tests: - test/repos-alias.test.ts — round-trips add/list/remove through runSources to verify the alias path works. Also asserts the deleted module is actually gone (catches accidental resurrection during rebase conflicts). - All 162 prior unit tests + 2 new = 164 pass on PGLite. Codex's P0 #2 (per-repo sync state) and P0 #3 (slug collision) are both resolved here — sources.last_commit scopes bookmarks per source, and pages.slug uniqueness is (source_id, slug), which is what the v0.18.0 schema already shipped.
Expands Wintermute's 6-language chunker to 29 languages, swaps the heuristic tokenizer for the real thing, and adds small-sibling merging so a file of 20 tiny const declarations doesn't produce 20 embedding calls. This closes the Chonkie gap Garry called out in CEO review. Language coverage — 6 → 29: - Added grammars: rust, java, c_sharp, cpp, c, php, swift, kotlin, scala, lua, elixir, elm, ocaml, dart, zig, solidity, bash, css, html, vue, json, yaml, toml. All shipping in src/assets/wasm/ (committed in Layer 2). Bun's --compile bundles every import attributes path, so the compiled binary carries every grammar. - TOP_LEVEL_TYPES populated for the 11 most-used new languages (rust, java, c_sharp, cpp, c, php, swift, kotlin, scala, lua, elixir, bash, solidity) + the original 6. Tree-sitter loads the grammar but the chunker falls through to recursive chunking when TOP_LEVEL_TYPES isn't set — still correct output, just less semantic. Every grammar ships with a working fallback. - detectCodeLanguage extended for 29 extension families including .mts/.cts (TypeScript), .cc/.hpp/.cxx (C++), .kt/.kts (Kotlin), .scala/.sc (Scala), .ex/.exs (Elixir), etc. - DISPLAY_LANG table lookup replaces the inline 6-entry map; structured headers now read '[Rust]', '[C#]', '[PHP]' etc. Accurate tokenizer: - @dqbd/tiktoken with cl100k_base encoding (same encoder text-embedding-3-large uses). Lazy-loaded on first call via require() so dev and compiled binary share the init path. - Falls back to the old len/4 heuristic only if the encoder fails to initialize (vanishingly unlikely — keeps the chunker available instead of throwing). - Existing estimateTokens call sites (large-node threshold + sub-range splitting + new merge pass) all now see real counts. Real code is 2-3x more token-dense than prose; the old heuristic systematically under-split so large functions sometimes exceeded the embedding API's 8191-token hard cap. Small-sibling merging: - New mergeSmallSiblings post-pass runs on the chunk list after tree-sitter extraction. - Adjacent chunks under 40% of chunkSizeTokens get accumulated into one merged chunk up to the full budget. - Large chunks (functions, classes) pass through untouched. - Merged chunks get symbolName=null, symbolType='merged', startLine/endLine spanning the group. The header reads: '[Lang] path:N-M merged (K siblings)' so retrieval can still show coherent context. - Mirrors Chonkie's CodeChunker._group_child_nodes() + bisect_left accumulation. A Go file with 30 top-level imports + 5 functions no longer produces 30 separate import chunks. CHUNKER_VERSION bumped 2 → 3: - Any existing v0.18.x brain with code pages will re-chunk on next sync because content_hash folds CHUNKER_VERSION in. Without the bump, stale (2-3x token-off, non-merged) chunks would persist forever until manual 'sync --force'. CI guard + smoketest updates: - scripts/chunker-smoketest.ts replaced the tiny hello/Foo/Id fixture with a realistic TS snippet (calculateScore with branches + UserRegistry class) so at least one chunk has a concrete symbol name — small-sibling merging would otherwise collapse the old fixture and fail the assertion. - scripts/check-wasm-embedded.sh assertions updated: check has_symbol_names:true (at-least-one-real-symbol), still verify [TypeScript] header and specifically the calculateScore symbol. Tests — test/chunkers/code.test.ts (15 cases): - CHUNKER_VERSION=3 shape assertion (guards silent re-chunking across releases). - detectCodeLanguage across 29 extensions + unknown + case-insensitive. - chunkCodeText on TypeScript / Python / Rust / Go producing chunks with correct language tag + symbol names. - Fallback path for unsupported extension produces recursive-chunk module-kind output. - Small-sibling merging: 5 tiny consts → 1-2 chunks; big function passes through untouched; merged chunk line range spans group. - Structured header shape: starts with [Lang], contains file path, line range, symbol name. - Empty input returns empty array. All 177 unit tests pass + CI guard on compiled binary passes.
Two expansions from the plan's E1 + E2. E3 (markdown fence extraction)
deferred to a follow-up PR — the feature surface is small and doesn't
block the main cathedral.
E1 — Design-doc ↔ implementation linking:
- New extractCodeRefs() in src/core/link-extraction.ts. Scans markdown
prose for references like 'src/core/sync.ts:42'. Anchored on a
prefix allowlist (src|lib|app|test|tests|scripts|docs|packages|
internal|cmd|examples) + the 39-extension code file list so random
phrases like 'foo/bar.js' don't generate false-positive edges. Dedups
by path (first occurrence wins).
- importFromContent writes bidirectional edges for every code ref
found in compiled_truth + timeline:
markdown_slug --[documents]--> code_slug
code_slug --[documented_by]--> markdown_slug
Both use link_source='markdown', origin_page_id=markdown_slug,
origin_field='compiled_truth' so runAutoLink reconciliation scopes
edges correctly.
- addLink's inner SELECT naturally drops edges to non-existent pages,
so a markdown guide imported before the code repo is synced writes
no edges — they'll land when the code arrives via A3 reverse-scan
(deferred to a follow-up since it only activates for users who sync
markdown and code in opposite order).
E2 — Incremental chunking:
- importCodeFile reads existing chunks via engine.getChunks(slug)
before embedding.
- Keys existing chunks by `${chunk_index}:${chunk_text}`. Any new
chunk that matches verbatim at the same index reuses the existing
embedding (chunk.embedding + token_count). Only new/changed chunks
go to embedBatch.
- Cost impact: a daily autopilot on a stable repo touches ~2-5% of
chunks on each run. E2 cuts OpenAI embedding spend by ~95% vs
naive full re-embed. Stated before (Codex A2 decision) and now
actually implemented.
- Uses chunk_index + chunk_text as the key (not symbol_fqn) because
the tree-sitter chunker already makes chunk_index semantic — it's
AST-order. A blank line at the top of a file shifts start_byte
for every chunk below but leaves chunk_text identical, so the
cache still hits.
- Fallback: when embedBatch throws (rate-limit, network, etc.) the
existing warn-but-continue behavior stays. Un-embedded chunks land
in the DB with NULL embedding; a later `embed --stale` will fix
them.
Tests (test/link-extraction-code-refs.test.ts, 10 cases):
- :line suffix capture.
- Prefix allowlist (11 directories).
- Extension recognition (39 extensions).
- Rejects paths outside allowlisted prefixes.
- Rejects non-code extensions.
- Dedup by path (first occurrence wins).
- Different paths coexist.
- Real-markdown integration: guide with 4 code refs (one with line
number) produces the right set of paths.
- Doesn't match URL-like strings (word-boundary behavior).
Tests (test/incremental-chunking.test.ts, 3 cases):
- Identical content re-import skips entirely (content_hash match).
- Editing ONE function in a 3-function file preserves the other two
chunks verbatim (same chunk_text in DB). Verifies the cache-hit
path actually works end-to-end on PGLite.
- Fresh-file import embeds all chunks (nothing to reuse).
All 189 unit tests pass on PGLite.
Delivers the magical-moment commands for v0.19.0 code indexing. These
are the agent-facing endpoints that turn 'brain-first lookup' from a
markdown-only Iron Law into something that covers code too.
gbrain code-def <symbol>:
- Queries content_chunks.symbol_name = $1 AND page_kind = 'code' AND
symbol_type IN (function, class, interface, type, enum, struct,
trait, module, contract, export statement).
- Orders by symbol_type rank (function first, then class, etc.) then
page slug then line number — deterministic across runs.
- --lang <language> filter narrows to a single language.
- --limit N caps results (default 20).
- Returns Array<{ slug, file, language, symbol_type, start_line,
end_line, snippet }> — the 7-field shape the agent persona needs.
gbrain code-refs <symbol>:
- Bypasses the standard searchKeyword path, which uses DISTINCT ON
(slug) to collapse results to one chunk per page. That collapse is
right for markdown search but wrong for code-refs — a single file
typically has many usage sites, each interesting to the agent.
- Direct ILIKE scan over content_chunks + JOIN pages WHERE page_kind
= 'code'. Word-boundary precision is a follow-up (would need
tsvector or regex); for v0.19.0 the substring heuristic is good
enough because symbol names are distinctive by design.
- Same --lang / --limit / --json flag surface as code-def.
- Returns Array<{ slug, file, language, symbol_name, symbol_type,
start_line, end_line, snippet }> — 8 fields (code-def + the
containing symbol_name).
Agent-DX doctrine (from DX review):
- Auto-JSON on pipe: both commands emit JSON when stdout is not a
TTY (gh-CLI convention). Explicit --json forces JSON on TTY;
--no-json forces human output even when piped.
- Structured error envelope: missing symbol argument returns
{ class: 'UsageError', code: '..._requires_symbol', hint: '...' }
serialized as JSON in non-TTY mode, plain message in TTY.
Catch-all DB error path uses serializeError() — no raw stack
traces leak to the agent.
Tests — test/code-def-refs.test.ts (10 cases):
- Seeds a fixture repo (two TS files with deliberately large symbols
to stay independent under small-sibling merging).
- findCodeDef:
- Resolves interface + function by name to the right file.
- Empty-symbol query returns [].
- Language filter narrows to typescript; python returns [].
- findCodeRefs:
- Finds multiple usage sites across files (both src/engine.ts
and src/sync.ts appear when searching for BrainEngine — this
is the DISTINCT ON bypass working).
- Deterministic ordering by slug + line number.
- Unknown symbol returns [].
- --limit caps result count.
- Snippets are <= 500 chars (the agent doesn't get flooded).
CLI wiring:
- Added 'code-def', 'code-refs' to CLI_ONLY.
- New switch cases in handleCliOnly call runCodeDef / runCodeRefs.
- Help text gains a CODE INDEXING (v0.19.0) section.
All 199 unit tests pass.
Deferred from Layer 7 per the cathedral plan:
- sync --all cost preview with TTY detection — requires folding the
tokenizer into the sync path. Pushed to a follow-up.
- query --lang filter — requires changes to src/core/search/*.ts.
Pushed to a follow-up.
Retrieval-quality gate for v0.19.0 code indexing. Seeds a ~25-file
fictional corpus across 5 languages (TS, Python, Go, Rust, Java),
imports each via importCodeFile, and asserts code-def + code-refs
produce the expected shape. Runs against PGLite in-memory so no
OpenAI key or external Postgres is needed; reproducible on CI with
just Bun.
What the E2E covers:
- Corpus seeded: 25+ code pages, all page_kind='code'.
- code-def finds AuthService across multiple languages (≥2 of
TS/Rust/Java).
- code-def --lang typescript filters precisely (P@5=1.0 for
CacheService + typescript).
- code-refs surfaces multiple usage sites across files (the
DISTINCT ON bypass working in practice).
- code-refs over the shared "start" method across 5 languages
produces ≥3 language hits (ranking stability).
- Magical-moment assertion: code-refs completes in <500ms on a
25-file corpus (budget is 100ms; 500ms pad absorbs CI variance).
- MRR sanity: top result for exact symbol is the defining file.
- Edge cases: non-existent symbol returns [], not error. Language
filter with zero matches returns []. Re-import is idempotent.
Chunker retune:
- Small-sibling merge threshold dropped from 40% to 15% of
chunkSizeTokens. The 40% figure was collapsing 3-method classes
into 'merged' chunks, killing symbol_name lookups for the entire
class. 15% matches the original intent: merge truly tiny
declarations (const X = 1; import ... from ...;) while leaving
substantive symbols (functions, classes) independent. Verified
by the BrainBench test — AuthService is now its own chunk with
symbol_name='AuthService', so findCodeDef('AuthService') resolves.
- Unit test updated: 10 consts with a generous chunkSizeTokens=1000
still exercise the merge path.
Total v0.19.0 unit + E2E coverage: 91 tests across 9 new test
files, 357 assertions, all green.
Closes out the v0.19.0 cathedral. Total shipped across 10 layers: - 91 new unit + E2E tests (9 new files, 357 assertions, all green) - 2 schema migrations (v25 pages.page_kind + v26 content_chunks code metadata) - 4 new CLI surfaces (repos [alias] + code-def + code-refs + sources passthrough) - 1 new core module (src/core/errors.ts) - 36 tree-sitter grammar WASMs embedded via Bun --compile - 1 CI guard preventing silent-chunker regression - Wintermute's multi-repo replaced with v0.18.0 sources backend CHANGELOG.md — release-summary section in the GStack/Garry voice per CLAUDE.md "Release-summary template": bold two-line headline + lead paragraph + "The numbers that matter" table + "What this means for builders" + itemized changes + "To take advantage of v0.19.0" block. No em dashes, no AI vocabulary, no banned phrases. Numbers are from the v0.19.0 test-fixture benchmarks. CLAUDE.md — four new file entries in the Key files section (src/core/chunkers/ annotated with v0.19.0 additions, src/core/errors.ts, src/assets/wasm/, src/commands/code-def.ts + code-refs.ts). skills/migrations/v0.19.0.md — agent-readable migration walkthrough per the v0.11.0 convention. Tells the agent what to do after `gbrain upgrade` runs the orchestrator: verify schema v26, register a code source via `gbrain sources add`, run `sync --source <id>`, confirm `gbrain code-def` / `code-refs` both work. Notes the deprecated `gbrain repos` alias for scripts that used Wintermute's baseline. Flagged in pending-host-work.jsonl per the v0.11.0 convention so headless agents surface the prompt. VERSION — 0.18.2 → 0.19.0. All 91 v0.19.0 tests + the CI guard pass.
Lands the four items the v0.19.0 cathedral explicitly scoped out but that the /plan-ceo-review + /plan-devex-review + /plan-eng-review chain identified as genuine follow-ups rather than abandoned ideas. Items added under a new 'code-indexing (v0.19.0 follow-ups)' section: - P1 — sync --all cost preview with TTY detection. Closes DX fix #1 from the /plan-devex-review pass: the agent persona can't respond to stdin prompts. Non-TTY path must emit a parseable ConfirmationRequired envelope; TTY path uses [y/N]. File refs: src/commands/sync.ts:590, src/core/chunkers/code.ts estimateTokens, src/core/errors.ts buildError. - P2 — query --lang filter through src/core/search/*.ts. Column ships in v0.19.0 (migration v26 + partial index); the query path just needs to respect it. Keeps ranking honest when the user knows the language. File refs: src/core/search/, pglite-engine searchKeyword, test/e2e/code-indexing.test.ts language-filter pattern. - P2 — E3 markdown code-fence extraction. After parseMarkdown, iterate marked's lexer tokens for { type: 'code', lang, text } and chunk each through chunkCodeText with chunk_source='fenced_code'. ~40% of gbrain's brain is guides with substantial inline code — this lands those fences as first-class TS/Python/Go chunks in search instead of treating them as prose. - P2 — A3 reverse-scan backfill for doc↔impl. Companion piece to E1. Markdown-first → code-later import order currently loses edges because addLink's JOIN drops them when the code page doesn't exist yet. A3 makes importCodeFile scan existing markdown for references to the new code path and backfill edges both directions. Trade-off: per-file scan is expensive on first sync; batch 'gbrain reconcile-links' is an alternative shape. Each entry follows the CLAUDE.md TODOS format: What/Why/Pros/Cons/ Context with exact file refs/line numbers/Effort (S/M/L + human vs CC)/Depends on. All four are purely additive on top of v0.19.0 — nothing blocks.
Three pre-existing conditions surfaced when running the full suite and blocked a clean CI floor for Cathedral II work: 1. `bun run test` default 5s hook timeout fails under load. PGLite WASM init can exceed 5s when many test files spin up instances in parallel. The bunfig.toml `timeout = 60_000` key is honored by `bun test` but does not propagate to beforeEach/afterEach hooks when `bun test` runs behind `bun run typecheck` in the CI chain. Pass `--timeout=60000` explicitly on the command line, where it covers both per-test and per-hook timeouts. Before: 2136 pass / 30 fail (on-branch baseline) After: 2272 pass / 0 fail All 30 failures were `beforeEach/afterEach hook timed out for this test` → `TypeError: undefined is not an object (evaluating 'engine.disconnect')` — i.e. the hook never finished connecting PGLite, so the engine variable was never assigned, so afterEach tripped on `engine.disconnect()`. The new timeout gives PGLite WASM init enough headroom under concurrent load. 2. `test/repos-alias.test.ts` references the deliberately-deleted `src/core/multi-repo.ts` via a dynamic import inside a try/catch (the test asserts the module is no longer importable at runtime). TS 5.x module resolution flags this at typecheck time even inside try/catch. Build the path at runtime (`'../src/core/' + 'multi-repo.ts'`) so TS's compile-time module resolution doesn't fail on a path the test is EXPLICITLY verifying doesn't resolve. 3. `llms-full.txt` drifted from `bun run build:llms` output (earlier CLAUDE.md updates in v0.19.0 never regenerated). `bun run build:llms` now produces matching output. Zero behavior changes to production code. Test infrastructure only.
Layer 1 of 14 for the v0.20.0 "best code search in the world" cathedral.
Ships all Cathedral II DDL atomically so downstream layers have the
columns + tables + trigger they depend on. Schema-only; no consumer
behavior changes until Layer 5 (A1 edge extractor).
Reordered to Layer 1 after codex second-pass review (SP-4): previously
Layer 0b (chunk-grain FTS trigger) referenced columns added in the
former Layer 3 (Foundation), breaking bisectability. All schema DDL
now lands first; every subsequent layer's prerequisites exist.
### What this migration adds (one idempotent v27 transaction)
1. `content_chunks` gains 4 new columns:
- `parent_symbol_path TEXT[]` — scope chain for nested symbols (A3)
- `doc_comment TEXT` — extracted JSDoc/docstring (A4)
- `symbol_name_qualified TEXT` — 'Admin::UsersController#render' (A1)
- `search_vector TSVECTOR` — chunk-grain FTS (Layer 1b consumer)
All nullable; markdown chunks leave them NULL.
2. `sources.chunker_version TEXT` (SP-1 gate). Layer 10 will check this
against CURRENT_CHUNKER_VERSION and force a full sync walk on
mismatch, bypassing the git-HEAD up_to_date early-return that would
otherwise make a bare CHUNKER_VERSION bump a silent no-op.
3. `code_edges_chunk` — resolved call-graph + reference edges.
- `from_chunk_id` + `to_chunk_id` with FK CASCADE from content_chunks
- UNIQUE (from_chunk_id, to_chunk_id, edge_type) holds idempotency
- `source_id TEXT` matches `sources.id` actual type (codex F4 caught
the prior UUID typo)
- source scoping enforced in resolution logic, not the key, because
from_chunk_id → pages.source_id already determines it
4. `code_edges_symbol` — unresolved refs. Target symbol known by
qualified name; defining chunk not seen yet. Rows UNION with
code_edges_chunk on read (codex 1.3b); no promotion step (SP-7).
5. `update_chunk_search_vector` trigger — BEFORE INSERT/UPDATE OF
(chunk_text, doc_comment, symbol_name_qualified). Weights
doc_comment and symbol_name_qualified at 'A', chunk_text at 'B'.
Natural-language queries rank doc-comment hits above body text
(A4 intent, delivered via the trigger from day one even though
Layer 5 populates the doc_comment column).
### Engine interface + types
- `BrainEngine` gains 6 new methods for code edges, all stubbed in
both engines with explicit NotImplemented errors pointing at the
layer that will fill them (5, 7, or 1b):
addCodeEdges, deleteCodeEdgesForChunks, getCallersOf,
getCalleesOf, getEdgesByChunk, searchKeywordChunks
- `CodeEdgeInput`, `CodeEdgeResult` types added to src/core/types.ts
- `SearchOpts` extended with Cathedral II fields: language, symbolKind,
nearSymbol, walkDepth, sourceId (all optional; consumers wire in
Layer 5/7/10)
- `ChunkInput` extended with: parent_symbol_path, doc_comment,
symbol_name_qualified (populated by importCodeFile in Layer 5/6)
- `Chunk` read shape mirrors the added columns as optional fields
- `chunk_source` union widens to include 'fenced_code' for D2 fence
extraction (Layer 6 consumer)
### Tests
`test/migrations-v0_20_0.test.ts` — 17 structural assertions against
the v27 migration registry. Covers every column + table + index + the
trigger weight shape. E2E migration-application coverage lands in
`test/e2e/cathedral-ii.test.ts` alongside Layer 5.
### Status
- CEO + Eng + 2 codex passes CLEARED (see docs/designs/CODE_CATHEDRAL_II.md)
- 16 cross-model findings absorbed (7 codex pass 1 + 6 codex pass 2
+ 3 eng review)
- 13 more layers to go (0a → 14); see plan for full sequencing.
…SP-5 slug dispatch Codex F1: `sync.ts:35` v0.19.0 classified only 9 extensions as code. Rust/Java/C#/C++/Swift/Kotlin/etc. never reached the chunker on a normal repo sync, making v0.19.0's "29 languages" claim aspirational on the read path. Layer 2 widens the classifier so every language the chunker knows (~35 extensions) actually reaches it during sync. ### Changes 1. `src/core/sync.ts` CODE_EXTENSIONS widened from 9 to 35 extensions, matching the chunker's detectCodeLanguage coverage: adds .rs, .java, .cs, .cpp/.cc/.cxx/.hpp/.hxx/.hh, .c/.h, .php, .swift, .kt/.kts, .scala/.sc, .lua, .ex/.exs, .elm, .ml/.mli, .dart, .zig, .sol, .sh/.bash, .css, .html/.htm, .vue, .json, .yaml/.yml, .toml, .mts/.cts. 2. `src/core/sync.ts` adds `resolveSlugForPath(path)` — SP-5 fix. Before Cathedral II, sync delete/rename paths called `pathToSlug(path)` with default pageKind='markdown'. For the 9-ext classifier this was mostly fine (code files rare), but widening to 35 exts means Rust/Java/Ruby/etc. deletes and renames would mismatch on slug shape (pathToSlug markdown-style vs slugifyCodePath code-style). resolveSlugForPath dispatches on isCodeFilePath so delete/rename always hit the right page. Used in `src/commands/sync.ts` at the three slug-resolution sites (un-syncable delete, batch delete, rename from/to). 3. `src/core/chunkers/code.ts` adds `setLanguageFallback(fn)` + optional `content` arg to `detectCodeLanguage(path, content?)`. Pre-wires the Magika fallback hook that Layer 9 (B2) will consume for extension-less files (Dockerfile, Makefile, shell shebangs). Null default → no behavior change today; Layer 9 sets it at bootstrap. Fallback throws are swallowed (recursive chunker is always an acceptable degradation). ### Tests - `test/sync-classifier-widening.test.ts` — 20 cases covering the full widened extension set, resolveSlugForPath dispatch, and the Magika fallback hook contract (including throw-swallow and null-pass-through). - `test/sync-strategy.test.ts` updated: `.json` is no longer rejected (the chunker's language map includes JSON for structured-data chunking). Test clarifies Cathedral II semantics; adds .svg + .zip as non-code examples. ### CI result 2292 pass / 0 fail via `bun run test`, 388s wall time.
…rain wrap Codex F2 caught that v0.19.0's searchKeyword ranked via pages.search_vector, so doc-comment content living on a chunk couldn't influence ranking and A2 two-pass retrieval had no way to find the best matching chunk. Layer 3 moves the FTS primitive to content_chunks.search_vector (the column + trigger added in Layer 1/v27), dedups-to-best-chunk-per-page on return so every external caller still sees the v0.19.0 page-grain contract (SP-6), and exposes searchKeywordChunks as the raw chunk-grain primitive A2 two-pass will consume (Layer 7). ### Backfill migration v28 Layer 1's trigger only fires on INSERT/UPDATE — rows inserted before v27 applied had NULL search_vector. v28 backfills every existing chunk with the same weight shape the trigger uses (doc_comment + symbol_name_qualified at weight A, chunk_text at B). Idempotent via `WHERE search_vector IS NULL`; re-runs pick up only remaining NULL rows. ~2-3s on a 20K-chunk brain. ### searchKeyword rewrite (both engines) CTE chain: rank chunks by cc.search_vector → DISTINCT ON (slug) picks best chunk per page → order by score → limit. External shape identical to v0.19.0: one row per matched page, score comes from the best chunk on that page, chunk metadata attached. Zero breaking changes for backlinks counting, enrichment-service.countMentions, list_pages, etc. Inner fetch limit is 3x the requested page limit so dedup has enough chunks to produce N distinct pages (a co-occurring-term cluster in one page can't eat the result set). Postgres keeps the SET LOCAL statement_timeout='8s' from v0.12.3 search timeout scoping. PGLite gets the same CTE shape minus the transaction- scoped GUC (PGLite has no pool). ### searchKeywordChunks (new internal primitive) Same chunk-grain ranking WITHOUT dedup. Returns raw top-N chunks by FTS score regardless of page. Used by A2 two-pass retrieval (Layer 7) as its anchor-discovery primitive — two-pass wants top chunks, not best-per-page. Most callers should prefer searchKeyword. ### Tests - test/chunk-grain-fts.test.ts: 11 cases covering migration v28 shape, page-grain external contract (dedup preserves invariants), chunk-grain primitive (no dedup, score-ordered), and the doc-comment weight-A precedence over body weight-B — the A4 ranking win validated today even though Layer 5 is what populates doc_comment from AST. - test/pglite-engine.test.ts existing "tsvector trigger populates search_vector on insert" updated: v0.19.0 searched pages.search_vector (built from title + compiled_truth) so two-word queries matching non-chunk text worked. Cathedral II ranks chunks only — test updated to search 'AI agents' which is in the chunk_text directly. - test/migrations-v0_20_0.test.ts "v27 is highest" relaxed to "v27 is the foundation migration; max >= 27" so later layers can land migrations without breaking this assertion. ### CI result 2553 tests / 0 fail via `bun test --timeout=60000`, 422s wall time.
Consolidate the 29-way GRAMMAR_PATHS + parallel DISPLAY_LANG record into
a single LANGUAGE_MANIFEST keyed on SupportedCodeLanguage. Each entry is
a LanguageEntry with { displayName, embeddedPath?, lazyLoader? }.
### Why this matters for Cathedral II
Before: adding a language meant editing two maps (path + display name)
AND adding a new `import G_X from ...` at the top, for every new lang.
After: one manifest entry + one `with { type: 'file' }` import (embedded)
or one registerLanguage() call at boot (lazy). loadLanguage() consults
the manifest uniformly — it doesn't know or care whether a grammar is
embedded in the compiled binary or resolved from node_modules at runtime.
### The 3 extension points
- `embeddedPath` — Bun `with { type: 'file' }` asset. Ships with
`bun --compile` output; already in place for the 29 core grammars.
- `lazyLoader` — async function returning path or Uint8Array. Used at
first reference, then cached in `languageCache` like embedded grammars.
Forward-compat for v0.20.x+ full tree-sitter-wasms (~136 more langs).
- `registerLanguage(lang, entry)` / `unregisterLanguage(lang)` /
`listRegisteredLanguages()` — runtime registration hook. Layer 9
(B2 Magika) will wire detection for extensionless files through
this API. Dynamic registrations win over core manifest on conflict
so hot-fix overrides during a session work without restart.
### Behavior guarantees preserved
- All 29 v0.19.0 core grammars continue to ship embedded — no binary-size
growth, no runtime network dependency for the core set.
- `detectCodeLanguage` untouched; its output key still maps 1:1 through
LANGUAGE_MANIFEST.
- `displayLang()` now derived from the manifest. Chunk headers read
"[Python]" / "[TypeScript]" / "[Ruby]" just as before — one source of
truth, manifest-derived.
### Tests (test/language-manifest.test.ts, 8 cases)
- Manifest covers all 29 v0.19.0 languages (typescript/tsx/js/py/rb/go/
rust/java/c_sharp/cpp/c/php/swift/kotlin/scala/lua/elixir/elm/ocaml/
dart/zig/solidity/bash/css/html/vue/json/yaml/toml).
- registerLanguage does NOT invoke the lazy loader at registration time
(proves the loader fires at most on first chunkCodeText() call).
- Dynamic registrations override core manifest entries (hot-fix path).
- unregisterLanguage removes a dynamic entry and clears its parser cache.
- chunkCodeText still loads core grammars (TypeScript / Python / Ruby)
end-to-end; chunk headers use the manifest displayName ("[Python]",
not "[python]").
### What's NOT shipped here
Adding the additional ~136 languages from tree-sitter-wasms is
deliberate v0.20.x+ follow-up work. The manifest infrastructure is in
place; expanding coverage is now a data-only PR (one entry per language).
### CI result
2561 tests / 0 fail via `bun test --timeout=60000`, 425s wall time.
…firmationRequired envelope Closes the v0.19.0 DX review's #1 pain point: "first sync surprise bill." Before Cathedral II, `gbrain sync --all` on a fresh multi-source brain could spin up tens of thousands of OpenAI embedding calls before anyone saw a cost number. Agent callers (OpenClaw, Hermes, etc.) had no way to gate the operation behind a spend check. ### Behavior Before `sync --all` touches a single source, walk the working trees of every registered source with `local_path`, sum tokens per file via the same cl100k_base tokenizer text-embedding-3-large actually uses, and compute a USD estimate. Gate on that: - **TTY + !--json + !--yes** → interactive `[y/N]` prompt. - **non-TTY OR --json OR piped** → emit `ConfirmationRequired` envelope to stdout via the v0.18 `errorFor` builder, exit code 2. Reserves exit 1 for runtime errors so agent callers can distinguish "awaiting user call" from "something crashed." - **--yes** → skip prompt entirely. Agent/CI path. - **--dry-run** → print preview, exit 0 without syncing. - **--no-embed** → skip the cost gate entirely (user already opted out of OpenAI spend; they'll run `embed --stale` later). ### Preview shape One stderr line or one JSON payload: sync --all preview: <N> files across <M> source(s), ~<T> tokens, est. $<X> on text-embedding-3-large. Conservative overestimate: full working-tree content, not just the incremental diff. A source never embedded before WILL embed everything on first sync; already-synced sources with small diffs get a ceiling, not a floor. False-high bias is intentional — users never get surprised by MORE cost than the preview claimed. ### Files - `src/core/chunkers/code.ts`: `estimateTokens` now exported (was module-private). Same cl100k_base tokenizer, just a public symbol. - `src/core/embedding.ts`: add `EMBEDDING_COST_PER_1K_TOKENS = 0.00013` + `estimateEmbeddingCostUsd(tokens)`. Single source of truth for cost math; every cost-preview surface reads this constant, so a pricing change is a one-line edit. - `src/commands/sync.ts`: - new `estimateSyncAllCost(sources)` helper walks trees, sums tokens per active source, returns breakdown. - new `walkSyncableFiles(repo, cb, strategy)` recursive walker. Honors the same `isSyncable` rules as the real sync so preview and execution agree on scope. Skips hidden dirs, node_modules, ops/, and files over 5MB. Best-effort file-read errors don't block the preview. - new `promptYesNo(question)` readline wrapper — resolves false on non-'y' answer OR EOF. - `--yes` and `--json` flags parsed at sync argv layer. - cost preview runs before the per-source sync loop on `--all`, gates via the TTY / --json / --yes / --dry-run matrix above. ### Tests `test/sync-cost-preview.test.ts` (6 cases): - EMBEDDING_COST_PER_1K_TOKENS pinned to $0.00013. - `estimateEmbeddingCostUsd` scales linearly across 0 → 1M tokens. - `estimateTokens` round-trips (empty → 0, short → <10, 100x text → >50x). ### CI result 2567 tests / 0 fail via `bun test --timeout=60000`, 424s wall time.
~40% of gbrain's brain is docs + guides + architecture notes with
substantial inline code. In v0.19.0 those fenced code blocks chunked as
prose, so querying "how do we handle errors in TypeScript" ranked
paragraphs ABOUT the import above the actual import example. D2 walks
the marked lexer tokens, extracts each recognized code fence, and
persists them as extra chunks on the parent markdown page with
`chunk_source='fenced_code'` and full code-metadata (language,
symbol_name, symbol_type, start/end line).
### Behavior
In `importFromContent`, after `parseMarkdown` returns compiled_truth,
we additionally run the text through `marked.lexer()` and walk for
`{ type: 'code', lang, text }` tokens. For each:
- Map the fence language tag (`ts`/`typescript`/`js`/...) to a
pseudo-path (`fence.ts`/`fence.js`/...) so `detectCodeLanguage`
picks the right grammar.
- Call `chunkCodeText(text, pseudoPath)` — one or more code chunks
depending on fence size. Tree-sitter-aware chunking means a big
TS fence splits at function boundaries, not character count.
- Persist each chunk with `chunk_source='fenced_code'`. Extends the
existing chunk_source enum; schema allows it via the TEXT column.
### Fence-bomb DOS guard
`MAX_FENCES_PER_PAGE = 100` by default, overridable via
`GBRAIN_MAX_FENCES_PER_PAGE` env var. A malicious markdown page with
10K ```ts blocks could otherwise force 10K embedding API calls.
Beyond the cap, remaining fences skip with a one-line console warn
so operators can see the event.
### Per-fence error isolation
Each fence runs through its own try/catch. One malformed fence (e.g.
marked lexer choking on edge-case markdown) doesn't abort the whole
page import — the other fences + the prose chunks from
compiled_truth all still land.
### Recognized fence tags (29 languages + 7 aliases)
ts/typescript, tsx, js/javascript, jsx, py/python, rb/ruby,
go/golang, rs/rust, java, c#/cs/csharp, cpp/c++, c, php, swift,
kt/kotlin, scala, lua, ex/elixir, elm, ml/ocaml, dart, zig,
sol/solidity, sh/bash/shell/zsh, css, html, vue, json, yaml/yml,
toml.
Unknown tag → skipped (no synthetic chunk, no crash). Missing tag
(```\n...\n```) → skipped. Empty body → skipped.
### Collateral fix
`rowToChunk` in src/core/utils.ts now maps the code-chunk metadata
columns (language, symbol_name, symbol_type, start_line, end_line)
+ the v0.20.0 Cathedral II additions (parent_symbol_path,
doc_comment, symbol_name_qualified) out of the DB. Pre-Cathedral II
the code columns were written via upsertChunks but never read back
— caught by the new fence test assertions.
### Tests (test/fence-extraction.test.ts, 7 cases)
- TS fence → language='typescript' chunk
- Python fence → language='python', chunk_text contains def
- Ruby fence → language='ruby'
- Unknown tag (```mermaid, ```unknown-xyz) → no fenced_code chunks
- Missing tag → no fenced_code chunks
- 3 fences on one page, mix of langs → 3+ fenced_code chunks
- Empty fence body → no chunks
### CI result
2574 tests / 0 fail via `bun test --timeout=60000`, 434s wall time.
Closes the v0.19.0 Layer 6 doc↔impl order-dependency: when a markdown
guide imports BEFORE the code it cites (common — docs land first, code
sync runs second), the Layer 6 E1 forward-scan calls addLink but its
inner JOIN silently drops the edge because the code page doesn't exist
yet. The guide and the code eventually both exist in the brain, but
the edge never materialized.
### New CLI surface
gbrain reconcile-links [--dry-run] [--json]
Walks every markdown page, re-runs `extractCodeRefs` on
compiled_truth+timeline, and calls addLink(md, code, ..., 'documents')
+ reverse for each hit. ON CONFLICT DO NOTHING at the links table
makes the operation idempotent — existing edges stay, new edges land.
### Per-lang coverage via extractCodeRefs
Inherits the regex from `src/core/link-extraction.ts` which already
recognizes code paths for 29 extensions (ts/tsx/js/py/rb/go/rust/java/
c#/cpp/c/php/swift/kotlin/scala/lua/elixir/elm/ocaml/dart/zig/sol/sh/
css/html/vue/json/yaml/toml). Fence-extraction (D2) and classifier-
widening (Layer 2) keep this in sync with the chunker's actual reach.
### Why batch over per-import reverse-scan
Codex's two-pass review flagged per-import reverse-scan as O(N)
ILIKE/JOIN queries per code file imported — on a 47K-page brain first-
syncing 5K code files that's 5K ILIKE scans. A user-triggered batch
run on an already-synced brain is one walk, slug-indexed via addLink's
existing lookup. Same correctness, much faster.
### Behavior
- Dry-run: counts refs, attempts = 0, writes nothing.
- auto_link=false in config: returns status='auto_link_disabled' +
no-op. Users who disabled auto-linking on put_page don't want
reconcile-links silently re-populating edges either.
- Missing code target: counted as `edgesTargetsMissing`, not thrown.
The ref exists in the guide, but the code page hasn't been synced
yet. Re-run after the next code sync to materialize.
- Progress reporter: `reconcile_links.scan` phase, one tick per
markdown page, with rolling summary `guides/foo (+N refs)` per tick.
### Tests (test/reconcile-links.test.ts, 6 cases)
- Extracts code refs and creates bidirectional edges (guide→code +
code→guide).
- Idempotent: second run inserts zero new edges.
- Dry-run reports counts without writing.
- Markdown page with no code refs is a no-op.
- Respects auto_link=false.
- Missing code target is counted, not thrown.
### CI result
2580 tests / 0 fail via `bun test --timeout=60000`, 432s wall time.
Codex's second-pass review caught that bumping CHUNKER_VERSION alone is a silent no-op on an unchanged repo: performSync short-circuits at `up_to_date` before reaching importCodeFile's content_hash check. Layer 12 adds a sources.chunker_version gate that forces a full re-walk when the version mismatches, regardless of git HEAD equality. - CHUNKER_VERSION 3 → 4 (src/core/chunkers/code.ts:99), folded into content_hash via v0.19.0 Layer 5 wiring — any bump forces clean re-chunks. - src/commands/sync.ts: readChunkerVersion/writeChunkerVersion helpers; version-mismatch gate runs BEFORE the up_to_date early-return and forces a full walk; writeChunkerVersion called after every last_commit anchor. - test/chunker-version-gate.test.ts: 3 pinning tests (constant value, import stability, v27 migration shape). - test/chunkers/code.test.ts: update v0.19.0 CHUNKER_VERSION=3 assertion to Cathedral II v0.20.0 CHUNKER_VERSION=4. Full CI: 2333 pass / 250 skip / 0 fail / 6155 expect() / 408s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rchestrator Ships the user-facing explicit-backfill path. v0.19.0 → v0.20.0 brains get CHUNKER_VERSION 3→4 rolled over automatically via Layer 12's gate on next sync. Users who want the benefits NOW (before their next sync) run `gbrain reindex-code --yes`. - New src/commands/reindex-code.ts. runReindexCode(engine, opts) walks code pages from the DB in batches of 100 (Finding 4.4 OOM protection), reads compiled_truth + frontmatter.file, re-runs importCodeFile. --dry-run reports cost + token count without importing. --force bypasses importCodeFile's content_hash early-return. --source filters to one sources row. Pages without frontmatter.file fail cleanly (counted, not thrown). runReindexCodeCli parses argv, wires the D1 cost-preview gate (TTY prompt or ConfirmationRequired envelope for non-TTY/JSON), delegates. - src/core/import-file.ts: importCodeFile gains opts.force flag. When true, skips the content_hash === hash early-return so a paranoid full reindex always re-chunks + re-embeds even when content hasn't changed. - src/cli.ts: register 'reindex-code' case + CLI_ONLY entry. - src/commands/migrations/v0_20_0.ts: orchestrator with 3 phases (schema → backfill_prompt → verify). Phase B prints the two backfill choices directly (automatic via sync vs immediate via reindex-code). Follows v0.12.2/v0.18.1 idempotent-resumable pattern. - src/commands/migrations/index.ts: registers v0_20_0 after v0_18_1. - skills/migrations/v0.20.0.md: agent-facing post-upgrade instructions. - test/reindex-code.test.ts: 5 cases (count, dry-run, walk+failures, empty brain, batch pagination). - test/migration-orchestrator-v0_20_0.test.ts: 5 cases (registry wiring, feature-pitch content, __testing exports, dry-run skips, is-latest). - test/apply-migrations.test.ts: extend skippedFuture pins with 0.20.0. Full CI: 2343 pass / 250 skip / 0 fail / 6193 expect() / 426s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…/ --symbol-kind Ships the cheap half of the C tier: language + symbol-kind filters on hybrid search. The content_chunks.language and content_chunks.symbol_type columns have existed since v0.19.0 Layer 5 (code chunker populates both); Layer 10 exposes them as filter flags on the 'query' operation. The expensive half (C3 --near-symbol, C4 code-callers, C5 code-callees) is blocked on Layer 5 A1 edge extractor — those need the code_edges_chunk + code_edges_symbol tables populated. They ship in a follow-up. - src/core/pglite-engine.ts: searchKeyword / searchKeywordChunks / searchVector all accept opts.language + opts.symbolKind. Filters added via parameterized $N indices; unknown values return zero results (no false positives). - src/core/postgres-engine.ts: same three methods, same filters, threaded through the postgres.js sql-fragment pattern. Honors SET LOCAL statement_timeout discipline. - src/core/search/hybrid.ts: threads opts.language + opts.symbolKind into per-engine searchOpts so filters fire at SQL level (not post-filtered in-memory). - src/core/operations.ts: query op params gain lang + symbol_kind entries. Handler maps them into hybridSearch opts.language / opts.symbolKind. - src/cli.ts: updated --help CODE INDEXING section to list the new flags + reconcile-links + reindex-code commands. - test/search-lang-symbol-kind.test.ts: 9 cases (no filter, lang-only, symbolKind-only, combined AND, searchKeywordChunks variant, unknown lang/kind return zero, operation schema check). Full CI: 2352 pass / 250 skip / 0 fail / 6216 expect() / 432s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… emission
Ships the chunk-granularity change codex called out in the second-pass
review. Before Cathedral II, `export class BrainEngine { m1() {} m2() {} }`
emitted ONE chunk for the whole class. Retrieval returned the entire
class body for a symbol-specific query like "how does searchKeyword
work" — the agent had to re-read the whole thing. A3 extends the
chunker to emit each method as its own chunk carrying
`parentSymbolPath: ['BrainEngine']`, with a `(in BrainEngine)` suffix in
the header so the embedding captures scope context. The class-level
parent chunk still ships (slim body: declaration line + member digest)
so class-level queries still hit something.
Recursive expansion: Ruby `module Admin { class UsersController { def
render } }` emits 3 chunks — Admin (parent=[]), UsersController
(parent=[Admin]), render (parent=[Admin, UsersController]).
- src/core/chunkers/code.ts:
- CodeChunkMetadata gains `parentSymbolPath?: string[]`.
- NESTED_EMIT_CONFIG map per language (TS, TSX, JS, Python, Ruby,
Rust impl blocks, Java class/interface/record). Maps parent types
(class_declaration / class_definition / module / impl_item) to
child types (method / method_definition / function_definition /
singleton_method / constructor_declaration).
- findNestableParent unwraps TS export_statement to reach the inner
class_declaration — the export wrapper was a classic gotcha.
- emitNestedScoped: recursive, builds full parent-chain path, pushes
a slim scope-header chunk for each parent level + leaf chunks for
methods. Handles module → class → method chains.
- buildChunk emits "(in ClassName.method)" header suffix when
parentSymbolPath is non-empty.
- mergeSmallSiblings now bails on any file that has parent-scoped
chunks. Methods emitted by A3 are intentionally small and
individually addressable; merging them would erase the scope
context Layer 6 just established.
- src/core/import-file.ts: importCodeFile passes parent_symbol_path
from chunker metadata into ChunkInput so it lands in content_chunks.
- src/core/pglite-engine.ts + src/core/postgres-engine.ts: upsertChunks
extends the column list to persist parent_symbol_path (TEXT[]),
doc_comment (TEXT), symbol_name_qualified (TEXT). All three existed
as schema columns from Layer 1 but the writers weren't plumbed yet.
ON CONFLICT DO UPDATE includes all three so re-imports refresh
metadata correctly.
- test/parent-scope.test.ts: 9 cases covering TypeScript class method
expansion, Python class, Ruby module+class, top-level function
passthrough, and round-trip through upsertChunks to verify text[]
persistence.
Full CI: 2361 pass / 250 skip / 0 fail / 6270 expect() / 439s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…names (8 langs)
The 10x leap. v0.19.0 shipped symbol-column filtering and could find "the
definition of X"; v0.20.0 Layer 5 captures who CALLS X. Walk the tree-sitter
tree during chunking, harvest call-site edges, persist to code_edges_symbol
with the callee's short-name as to_symbol_qualified. `getCallersOf("helper")`
now returns every call site, ready for Layer 7 two-pass retrieval to expand
into structural neighbors.
Scope: precision 80, recall 99. We don't try to resolve receiver types at
capture time (obj.method() stores "method", not "ObjClass.method"). That
receiver-type inference is a future optimization; the edges are captured,
which is the whole point. Cross-file resolution is also deferred — all
Layer 5 edges land unresolved in code_edges_symbol.
Per-language shipped: TypeScript, TSX, JavaScript, Python, Ruby, Go, Rust,
Java. ~85% of real brain code. Other languages chunk normally, edges just
empty.
- src/core/chunkers/qualified-names.ts (new): per-language delimiter
conventions. Ruby `Admin::UsersController#render` (instance) vs Python
`admin.users.UsersController.render` vs Rust `users::UsersController::render`.
Unknown languages dot-join as fallback (never drop).
- src/core/chunkers/edge-extractor.ts (new): iterative AST walk (no
recursion — tree-sitter trees can be deep, stack overflow risk on
generated code). Per-language CALL_CONFIG maps node types to callee
field names. extractCalleeName unwraps member_expression, scoped_identifier,
field_expression to reach the innermost identifier. findChunkForOffset
maps a byte offset to the innermost chunk for from_chunk_id resolution.
- src/core/chunkers/code.ts: CodeChunkMetadata gains
symbolNameQualified. buildChunk folds in qualified-name from parents +
name. New chunkCodeTextFull API returns (chunks, edges); chunkCodeText
stays as back-compat wrapper.
- src/core/import-file.ts: call chunkCodeTextFull, build ChunkInput list
with symbol_name_qualified, after upsertChunks run findChunkForOffset
to map call-site byte offsets to resolved chunk IDs, call
deleteCodeEdgesForChunks (codex SP-2 inbound invalidation) then
addCodeEdges. Edge persistence is best-effort — failure logs a warn
but does not fail the import.
- src/core/pglite-engine.ts + src/core/postgres-engine.ts: implement the
5 stub methods. addCodeEdges splits resolved vs unresolved by
to_chunk_id presence, inserts with ON CONFLICT DO NOTHING. getCallersOf
/ getCalleesOf UNION code_edges_chunk + code_edges_symbol (codex 1.3b:
no promotion, UNION-on-read forever). getEdgesByChunk honors direction
{in, out, both}. deleteCodeEdgesForChunks wipes both tables in both
directions (codex SP-2).
- test/qualified-names.test.ts: 9 cases (TS/Ruby instance method/Python/
Rust/Java/unknown-lang fallback).
- test/edge-extractor.test.ts: 11 cases (per-language call capture +
findChunkForOffset mapping + unknown-language empty-list).
- test/code-edges.test.ts: 7 cases (addCodeEdges insert + idempotency,
getCallersOf short-name match, resolved path, getEdgesByChunk
direction filters, deleteCodeEdgesForChunks both-direction wipe).
Full CI: 2391 pass / 250 skip / 0 fail / 6308 expect() / 449s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ode-callees CLI Exposes Layer 5's call-graph edges as user-facing agent commands. The existing code-def / code-refs pair answers "where is X defined?" and "where is X referenced?"; Layer 10 rest adds "who CALLS X?" and "what does X CALL?" — the structural questions v0.19.0 couldn't answer. Conventions follow the code-def / code-refs precedent: - Auto-JSON on non-TTY (gh-CLI convention) - StructuredAgentError envelope on usage / runtime failure - Exit 2 on UsageError, exit 1 on runtime - --all-sources to widen beyond the anchor's source; default source-scoped - src/commands/code-callers.ts (new) — wraps engine.getCallersOf. - src/commands/code-callees.ts (new) — wraps engine.getCalleesOf. - src/cli.ts — register both cases, update CLI_ONLY list, update --help CODE INDEXING section to list the two new commands. - test/code-callers-cli.test.ts — 2 cases (module exports, callable). The --near-symbol / --walk-depth flags on query ship with Layer 7 (A2 two-pass retrieval) in a follow-up layer commit. Full CI: 2393 pass / 250 skip / 0 fail / 6310 expect() / 448s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The capstone of the retrieval-side upgrade. Layer 5 captured edges at
chunk time; Layer 7 uses them. Given a query like "how does
searchKeyword handle N+1", standard hybrid search returns the function
body; A2 expansion additionally surfaces:
- the 3 functions that call it (1-hop)
- the 2 functions it calls (1-hop)
- the anchor set's neighbors' neighbors (2-hop, optional)
All ranked together with 1/(1+hop) score decay. One walk. Code-aware
brain, not RAG-over-code.
Default OFF per codex F5. Activation:
- `--walk-depth N` (1 or 2) walks N hops from the anchor set.
- `--near-symbol <qualified-name>` adds chunks matching the symbol's
qualified name as extra anchors, enabling "expand around this
specific symbol" without a keyword query.
Caps (codex F5):
- depth capped at 2 (max blast radius).
- neighbor cap 50 per hop (high-fan-out protection: console.log has
100k callers and should not flood the result set).
- per-page dedup cap lifts from 2 → min(10, walkDepth × 5) when
walking — structural neighbors from the same class are the point.
- src/core/search/two-pass.ts (new): expandAnchors walks
code_edges_chunk + code_edges_symbol, hydrating unresolved edges by
matching symbol_name_qualified on lookup. hydrateChunks fetches
SearchResult rows for expanded chunk IDs.
- src/core/search/hybrid.ts: gate the two-pass step on opts.walkDepth
> 0 OR opts.nearSymbol set. Expansion runs before dedup so neighbors
survive; dedup cap widens when walking. Best-effort — expansion
failure falls back to base hybrid retrieval.
- src/core/operations.ts: query op params gain near_symbol (string) +
walk_depth (number). Handler threads both into hybridSearch opts.
- test/two-pass.test.ts: 8 cases (walkDepth 0/1/2/5-clamp, nearSymbol
anchoring, hydrateChunks round-trip, operation schema).
Full CI: 2401 pass / 250 skip / 0 fail / 6332 expect() / 449s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ry tests
Pins the retrieval-quality behaviors Layer 5 and Layer 6 added, so any
accidental regression surfaces on CI rather than silently eroding search
quality.
Sub-categories:
- call_graph_recall — importCodeFile captures calls edges
end-to-end; getCallersOf + getCalleesOf round-trip through real
edge extraction; re-import idempotency via codex SP-2 per-chunk
invalidation.
- parent_scope_coverage — nested methods persist parent_symbol_path
through the upsertChunks path; qualified symbol names resolve
correctly for nested declarations.
doc_comment_matching is deferred: the chunk-grain FTS trigger from
Layer 1b already weights doc_comment 'A', but chunker doc_comment
extraction (A4 full implementation) is a follow-up. The column exists,
the ranking is ready — waiting on extraction.
type_signature_retrieval deferred with C6 to v0.20.1 per plan.
- test/cathedral-ii-brainbench.test.ts (new): 6 cases covering the
two sub-categories against real PGLite + importCodeFile.
Full CI: 2407 pass / 250 skip / 0 fail / 6345 expect() / 467s.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rsion bump) The capstone commit. Ships v0.20.0 — Code Cathedral II — with a full release-summary in CHANGELOG.md covering the 13 layers that landed (Layer 9 / Magika deferred to v0.20.1 per plan risk gate), migration guidance under "To take advantage of v0.20.0", and itemized changes grouped by layer with real numbers. - VERSION: 0.19.0 → 0.20.0 - package.json: 0.19.0 → 0.20.0 - CHANGELOG.md: new [0.20.0] entry with release-summary (two-line bold headline, lead paragraph, numbers-that-matter table with before/after delta, per-language call-capture table, "what this means for builders" closer), "To take advantage of v0.20.0" section with verify commands + issue-reporting template, and the full itemized changes section grouped by layer (1 / 2 / 3 / 4 / 5 / 6 / 7 / 8 / 10 / 11 / 12 / 13 / 9-deferred). Credits 2 codex passes + eng + ceo reviews — 16 cross-model findings absorbed. - TODOS.md: retire the 4 v0.19.0 follow-ups (all landed in v0.20.0 Layer 8 + Layer 10). Add 4 new Cathedral II follow-ups: - B2 Magika (Layer 9 deferred) - A4 full doc_comment extraction at chunk time - C6 code-signature - Cross-file edge resolution (Layer 5 precision upgrade) Full CI: 2407 pass / 250 skip / 0 fail / 6345 expect() / 465s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Master shipped v0.19.0 → v0.20.4 (smoke-test, BrainBench extraction, supervisor, queue resilience, gbrain-jobs merge) in parallel with this branch's Cathedral II work, which had been planned as v0.20.0. Renumbered to v0.21.0 (MINOR bump — appropriate for a 14-layer feature ship adding call-graph edges, two-pass retrieval, parent-scope chunking, chunk-grain FTS, file-classifier widening, language manifest, and cost preview). Conflict resolutions: - VERSION → 0.21.0 - package.json version → 0.21.0 - CHANGELOG.md: my Cathedral II entry promoted to [0.21.0] and slotted ABOVE master's [0.20.4]/[0.20.3]/[0.20.2]/[0.20.0]/[0.19.1]/[0.19.0] entries. Body prose updated v0.20.0 → v0.21.0 throughout. - TODOS.md: my "code-indexing v0.21.0 Cathedral II follow-ups" section preserved at top; master's "## Completed" section preserved. - src/cli.ts CLI_ONLY: union of both — kept master's additions (skillpack, routing-eval, skillify, smoke-test) and my Cathedral II additions (code-callers, code-callees, reindex-code) plus v0.19.0 carry-overs (repos, code-def, code-refs). - src/core/types.ts PageType: union — master added 'email', 'slack', 'calendar-event'; my v0.19.0 added 'code'. All four kept. Migration file rename: - src/commands/migrations/v0_20_0.ts → v0_21_0.ts - skills/migrations/v0.20.0.md → v0.21.0.md - test/migration-orchestrator-v0_20_0.test.ts → v0_21_0.ts - test/migrations-v0_20_0.test.ts → migrations-v0_21_0.test.ts - src/commands/migrations/index.ts: import v0_21_0 - migration version string '0.20.0' → '0.21.0' inside the orchestrator - test/apply-migrations.test.ts skippedFuture pin: 0.20.0 → 0.21.0 Inline source comments retain their original "v0.20.0 Cathedral II Layer N" tags as historical markers — the work was planned and built under the v0.20.0 codename; renaming the comments would touch ~30+ files for cosmetic-only consistency. The Cathedral II tag is unique enough to remain meaningful. Type fixes (postgres-engine.ts) introduced by master's stricter sql template typing on getCallersOf / getCalleesOf / getEdgesByChunk — extracted scopedSource as a non-optional const before the template literal so postgres.js's `ParameterOrFragment<never>` shape lines up. Full CI: 2663 pass / 250 skip / 0 fail / 6877 expect() / 522s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
importCodeFile / importFromContent's E1 doc↔impl forward-link path was
calling tx.addLink() expecting the pre-v0.18 silent-no-op behavior on
missing pages. Master tightened addLink in postgres-engine.ts to throw
when either endpoint is missing — which is correct for explicit callers,
but the doc↔impl case is intentionally order-agnostic: a guide that
cites src/core/sync.ts can land before the code repo syncs (and vice
versa).
Result on CI: 21 E2E tests failed in test/e2e/mechanical.test.ts because
the fixture corpus has prose pages citing code paths the corpus doesn't
include, so each importFromContent threw "addLink failed: page X or Y
not found" and aborted before downstream assertions could run.
Fix: wrap each tx.addLink call (forward + reverse edge) in try/catch.
Match the existing pattern in src/commands/extract.ts:547 and
src/core/operations.ts:453,470 — both run try { addLink } catch { skip }
for exactly this reason. Missing edges land later via
`gbrain reconcile-links` (Layer 8 D3), which forward-scans every
markdown page and idempotently inserts the edges that resolve.
Comment refresh: the old comment ("addLink's inner SELECT naturally
drops edges to non-existent pages") was true pre-v0.18; updated to
reflect the current throwing behavior + the reconcile-links recovery
path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The v8 (links_dedup) + v9 (timeline_dedup_index) regression tests time
the FULL `runMigrations` chain from version 7 → LATEST_VERSION. Their
5s budget was sized when the chain ended at v8/v9 themselves and v8 +
the helper-btree-index O(n log n) work were the dominant cost.
Cathedral II added v27 (TSVECTOR column + GIN index + plpgsql trigger
compile + 2 new tables w/ FK CASCADE) and v28 (UPDATE backfill of
search_vector). On PGLite WASM in CI, the full v7 → v28 chain now
takes ~30-40s — schema-creation overhead, not v8/v9 dedup itself.
Locally the chain ran in 2.75s; CI's container cold-start hit 33s.
The original O(n²) regression v8 had would have taken MINUTES on 1000
duplicate rows (the original incident was multi-minute, not multi-tens-
of-seconds). Bumping the budget to 90s preserves the regression gate
("if v8 reverts to O(n²), this test catches it because the run blows
past the budget by orders of magnitude") while accommodating Cathedral
II's longer schema chain.
CI: 33758ms (v8 test) + 33343ms (v9 test) → both under 90s. The 5s
assertion was failing them, not the test runner timeout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two new tables added by v27 (Cathedral II foundation) shipped without
RLS enabled. The E2E test "RLS is enabled on every public table (no
hardcoded allowlist)" caught this — Supabase exposes the public schema
via PostgREST so any table without RLS is anon-readable. Same security
gap as the v0.18.1 RLS hardening pass that v24 closed for the original
10 gbrain-managed tables.
Three CI failures fixed by this migration:
1. "RLS is enabled on every public table" — direct fail on the new
tables.
2. "GBRAIN:RLS_EXEMPT comment with valid reason exempts a non-RLS
public table" — was failing because doctor saw the unrelated
code_edges tables ALSO un-RLS'd, so the exempt-comment fixture
wasn't the only no-RLS table and doctor stayed in fail status.
3. "gbrain doctor exits 0 on healthy DB" — same cause, doctor was
emitting a fail check for the missing-RLS tables on every healthy
run.
Pattern: matches v24 exactly. DO $$ block with BYPASSRLS guard so a
non-bypass session can't accidentally lock itself out of its own data;
RAISE EXCEPTION on guard fail leaves schema_version at the prior value
so the next initSchema retries. Postgres-only via sqlFor — PGLite
doesn't enforce RLS the same way and the E2E gate runs only against
real Postgres.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-existing test bug surfaced when the E2E job ran on the Cathedral II branch (and would have surfaced on master too once anyone ran the Tier 1 Mechanical job). The test rolls schema_version back to 23, runs init, then asserts the version becomes exactly '24'. The intent was to prove v24 didn't crash on missing budget_* tables — not to pin a specific final version. But initSchema runs every pending migration. With v25 + v26 (v0.19.0) and now v27 + v28 + v29 (v0.21.0 Cathedral II) shipped, init advances schema_version to LATEST_VERSION (currently 29) regardless of where it started. The exact-match `'24'` assertion has been wrong since v25 landed; only the lack of an E2E run on master CI hid it. Fix: parse the final version as int and assert `>= 24`. Same intent (prove v24 ran cleanly + didn't roll back), forward-compatible with future schema growth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…moments Discoverability hint for engineering agents running on GStack. Cathedral II (v0.21.0) shipped call-graph edges + two-pass retrieval, but a GStack agent running /investigate or /review won't reach for them unless someone tells it gbrain has these surfaces. The new subsection slots between Remote MCP and the Skills index, lists the 5 commands verbatim (code-callers, code-callees, code-def, code-refs, query --near-symbol --walk-depth), and links to the v0.21.0 CHANGELOG entry for context. Tradeoff acknowledged: gbrain README serves both standalone and agent-platform users, so the GStack section is kept tight (16 lines) and slotted with the other agent-integration paths rather than at the top. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The build-llms regen-drift guard caught that the committed llms files were stale after the README "Using gbrain with GStack" addition + the v0.21.0 CHANGELOG promotion. Running `bun run build:llms` rebuilds both deterministically from llms-config.ts so the test passes. No source content changed in this commit — just the generator output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Cathedral II — your brain walks the code graph now.
15 bisectable feature commits + 1 merge commit ship the second-largest code-search upgrade in gbrain history. Cathedral II turns code retrieval from grep-class chunk lookup into a structural graph walk: who calls X, what does X call, what's X's parent class, all ranked together with 1/(1+hop) decay.
Foundation
parent_symbol_path TEXT[],doc_comment TEXT,symbol_name_qualified TEXT,search_vector TSVECTORoncontent_chunks. New tables:code_edges_chunk(resolved, FK CASCADE) +code_edges_symbol(unresolved, qualified-name keyed).sources.chunker_version TEXTfor SP-1 gate.setLanguageFallbackhook for Layer 9 Magika.resolveSlugForPathdispatcher (codex SP-5).update_chunk_search_vectorplpgsql trigger. Weights doc_comment + symbol_name_qualified'A'over chunk_text'B'. ExternalsearchKeywordshape stays page-grain; newsearchKeywordChunksexposes raw chunk-grain for A2 two-pass.LanguageEntryregistry. Forward-compat lazy-load path for the full 165-language pack.Structural edges (the 10x leap)
code_edges_symbol(unresolved).getCallersOf/getCalleesOf/getEdgesByChunk/addCodeEdges/deleteCodeEdgesForChunksimplemented on both engines. UNION-on-read forever (codex 1.3b).class Foo { m1(), m2() }now emits 3 chunks (class scope header + each method withparentSymbolPath: ['Foo']). Recursive expansion: Rubymodule Admin { class Users { def render } }emits 3 chunks across the chain.--walk-depth 1..2or--near-symbol <qualified>. 1/(1+hop) decay. Neighbor cap 50 per hop. Dedup widens tomin(10, walkDepth × 5)when walking.Bridge items (D tier — shipping the 4 v0.19.0 deferrals)
sync --allcost preview +ConfirmationRequiredenvelope (TTY prompt or exit 2 for non-TTY/JSON).chunk_source='fenced_code').reconcile-linksbatch command (forward-scan viaextractCodeRefs, idempotent ON CONFLICT).CLI surfaces
query --lang typescript,query --symbol-kind function|class|method,query --near-symbol <qualified> --walk-depth N, pluscode-callers <symbol>andcode-callees <symbol>(auto-JSON on non-TTY).Backfill + release
CHUNKER_VERSION 3 → 4+sources.chunker_versiongate (codex SP-1: forces full re-walk on version mismatch regardless of git HEAD).gbrain reindex-code [--source <id>] [--dry-run] [--yes] [--force] [--json]for explicit backfill. Migration orchestrator atsrc/commands/migrations/v0_21_0.ts.call_graph_recall+parent_scope_coverageend-to-end throughimportCodeFile.Layer 9 (B2 Magika) — DEFERRED to v0.21.1. Hook (
setLanguageFallback) is in place. Plan explicitly allowed this risk gate.Merge with master. Master shipped v0.19.0 → v0.20.4 in parallel. Cathedral II renumbered v0.20.0 → v0.21.0 in the final commit. CHANGELOG entry slotted above master's v0.20.x band; CLI_ONLY set is the union of both sides; PageType union absorbs master's
email/slack/calendar-eventplus mycode. Migration files renamedv0_20_0.ts→v0_21_0.ts.Numbers
calls(per-file, 8 langs)code-callers <sym>/code-callees <sym>query --near-symbol X --walk-depth 2Test Coverage
Plan Completion
Reviewed against
~/.claude/plans/system-instruction-you-are-working-foamy-kazoo.md:To take advantage of v0.21.0
gbrain upgraderunsapply-migrations→ schema v27 + v28 land. Next sync auto-detectssources.chunker_versionmismatch and forces full re-walk. Orgbrain reindex-code --dry-runpreviews cost;--yesruns it.Test plan
bun test— 2663 pass / 0 fail (155 unit/integration files)bun run typecheck— cleancheck-jsonb-pattern.sh,check-progress-to-stdout.sh,check-wasm-embedded.shall passbun run test:e2eagainst real Postgres before merge for thecode_edges_chunk/code_edges_symbolround-trip🤖 Generated with Claude Code