mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-29 19:01:39 +00:00
CLAUDE.md had grown to 592KB / ~147k tokens auto-loaded every session (~77% of
the llms-full.txt single-fetch bundle). The per-file index was append-only by
mandate. This is the exact thin-dispatcher-vs-fat-blob anti-pattern gbrain exists
to fix, so CLAUDE.md becomes a thin orientation + resolver that points at
on-demand docs.
This commit is the VERBATIM move (content-preserving — the next commit compresses):
- docs/architecture/KEY_FILES.md <- ## Key files + the calibration key-files
cluster + Schema Cathedral v3 impl detail
- docs/architecture/thin-client.md <- ## Thin-client routing
- docs/TESTING.md <- ## Testing
- ## Commands DROPPED (18 'added in vX.Y' history blocks; current surface is
gbrain 0.41.38.0 -- personal knowledge brain
USAGE
gbrain <command> [options]
SETUP
init [--pglite|--supabase|--url] Create brain (PGLite default, no server)
migrate --to <supabase|pglite> Transfer brain between engines
upgrade Self-update
check-update [--json] Check for new versions
doctor [--json] [--fast] Health check (resolver, skills, pgvector, RLS, embeddings)
integrations [subcommand] Manage integration recipes (senses + reflexes)
PAGES
get <slug> Read a page
put <slug> [< file.md] Write/update a page
delete <slug> Delete a page
list [--type T] [--tag T] [-n N] List pages
SEARCH
search <query> Keyword search (tsvector)
query <question> [--no-expand] Hybrid search (RRF + expansion)
ask <question> [--no-expand] Alias for query
IMPORT/EXPORT
import <dir> [--no-embed] Import markdown directory
sync [--repo <path>] [flags] Git-to-brain incremental sync
sync --watch [--interval N] Continuous sync (loops until stopped)
sync --install-cron Install persistent sync daemon
export [--dir ./out/] Export to markdown
export --restore-only [--repo <p>] Restore missing supabase-only files
[--type T] [--slug-prefix S] With optional filters
FILES
files list [slug] List stored files
files upload <file> --page <slug> Upload file to storage
files upload-raw <file> --page <s> Smart upload (size routing + .redirect.yaml)
files signed-url <path> Generate signed URL (1-hour)
files sync <dir> Bulk upload directory
files verify Verify all uploads
EMBEDDINGS
embed [<slug>|--all|--stale] Generate/refresh embeddings
LINKS
link <from> <to> [--type T] Create typed link
unlink <from> <to> Remove link
backlinks <slug> Incoming links
graph <slug> [--depth N] Traverse link graph (returns nodes)
graph-query <slug> [--type T] Edge-based traversal with type/direction filters
[--depth N] [--direction in|out|both]
TAGS
tags <slug> List tags
tag <slug> <tag> Add tag
untag <slug> <tag> Remove tag
TIMELINE
timeline [<slug>] View timeline
timeline-add <slug> <date> <text> Add timeline entry
TOOLS
extract <links|timeline|all> Extract links/timeline (idempotent)
[--source fs|db] fs (default) walks .md files; db iterates engine pages
[--dir <brain>] brain dir for fs source
[--type T] [--since DATE] filters (db source)
[--dry-run] [--json]
publish <page.md> [--password] Shareable HTML (strips private data, optional AES-256)
check-backlinks <check|fix> [dir] Find/fix missing back-links across brain
lint <dir|file> [--fix] Catch LLM artifacts, placeholder dates, bad frontmatter
orphans [--json] [--count] Find pages with no inbound wikilinks
salience [--days N] [--kind P] v0.29: pages ranked by emotional + activity salience
anomalies [--since D] [--sigma N] v0.29: cohort-based statistical anomalies (tag, type)
transcripts recent [--days N] v0.29: recent raw .txt transcripts (local-only)
dream [--dry-run] [--json] Run the overnight maintenance cycle once (cron-friendly).
See also: autopilot --install (continuous daemon).
check-resolvable [--json] [--fix] Validate skill tree (reachability/MECE/DRY)
report --type <name> --content ... Save timestamped report to brain/reports/
BRAIN (capture / ideate / explore — v0.37/v0.38)
capture [content] [--file PATH] Single entrypoint for getting content into the brain
[--stdin] [--slug s] [--type t] Inline content / file / stdin; writes to inbox/ by default
[--source ID] [--quiet|--json] Multi-source brains: route to a non-default source
brainstorm <question> [--json] Bisociation idea generator (hybrid search + far-set + judge)
[--save|--no-save] [--limit N]
lsd <question> [--json] Lateral Synaptic Drift: inverted-judge brainstorm
[--save|--no-save] [--limit N] rewarding far-from-obvious + axiomatic inversions
SOURCES (multi-repo / multi-brain)
sources list Show registered sources
sources add <id> --path <p> Register a source (id = short name, e.g. 'wiki')
sources remove <id> Remove a source + its pages
sync --all Sync all sources with a local_path
sync --source <id> Sync one specific source
repos ... DEPRECATED alias for 'sources' (v0.19.0)
CODE INDEXING (v0.19.0 / v0.20.0 Cathedral II)
code-def <symbol> [--lang l] Find the definition of a symbol across code pages
code-refs <symbol> [--lang l] Find all references to a symbol (JSON-first)
code-callers <symbol> Who calls this symbol? (v0.20.0 A1)
code-callees <symbol> What does this symbol call? (v0.20.0 A1)
query <q> --lang <l> Filter hybrid search to one language (v0.20.0)
query <q> --symbol-kind <k> Filter to symbol type (function|class|method|...) (v0.20.0)
reconcile-links [--dry-run] Batch-recompute doc↔impl edges (v0.20.0)
reindex-code [--source id] [--yes] Explicit code-page reindex (v0.20.0)
sync --strategy code Sync code files into the brain
JOBS (Minions)
jobs submit <name> [--params JSON] Submit background job [--follow] [--dry-run]
jobs list [--status S] [--limit N] List jobs
jobs get <id> Job details + history
jobs cancel <id> Cancel job
jobs retry <id> Re-queue failed/dead job
jobs prune [--older-than 30d] Clean old jobs
jobs stats Job health dashboard
jobs work [--queue Q] Start worker daemon (Postgres only)
ADMIN
stats Brain statistics
health Brain health dashboard
history <slug> Page version history
revert <slug> <version-id> Revert to version
features [--json] [--auto-fix] Scan usage + recommend unused features
autopilot [--repo] [--interval N] Self-maintaining brain daemon
config [show|get|set] <key> [val] Brain config
storage status [--repo <path>] Storage tier status and health
[--json] (git-tracked vs supabase-only)
serve MCP server (stdio)
serve --http [--port N] HTTP MCP server with OAuth 2.1
--token-ttl N Access token TTL in seconds (default: 3600)
--enable-dcr Enable Dynamic Client Registration
--public-url URL Public issuer URL (required behind proxy/tunnel)
call <tool> '<json>' Raw tool invocation
version Version info
--tools-json Tool discovery (JSON)
Run gbrain <command> --help for command-specific help. + the per-command KEY_FILES entries; content stays in git)
CLAUDE.md gains: a Reference map (resolver), a Maintaining section (the
anti-disease rule), and a Cross-cutting invariants subsection under Architecture
so the must-never-violate rules (trust fail-closed, sourceScopeOpts isolation,
JSONB trap, engine parity, contract-first, migrations, multi-source) still
auto-load after the index moved out.
Result: CLAUDE.md 592KB -> 61KB; llms-full.txt 740KB -> 210KB (new docs link-only
until compressed). build-llms drift + budget test green; verify 29/29 green.
The pre-move content is recoverable at git show <this^>:CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 KiB
10 KiB
Thin-client routing (remote MCP)
On-demand reference (see CLAUDE.md Reference map). Current behavior + invariants
only; release history lives in CHANGELOG.md + git.
gbrain init --mcp-only (v0.29.2) sets up a thin-client install: no local
brain content, just an OAuth client pointing at a remote gbrain serve --http.
v0.29.2/v0.30.0 only refused 9 obvious local-only commands; the other ~25
silently fell through to connectEngine() and opened the empty local PGLite,
returning "No results." against a populated remote brain. v0.31.1 fixes the
silent-empty-results bug class for every operation surface.
Key files:
src/cli.ts— Routing seam INSIDE the existing op-dispatch path (CDX-1: no parallelsrc/core/thin-client/module; routing is a ~80-line conditional inrunThinClientRouted). DetectsisThinClient(cfg)BEFOREconnectEngineso thin-client installs never open the empty PGLite. localOnly ops on thin-client refuse viarefuseThinClient(with pinpoint hint tableTHIN_CLIENT_REFUSE_HINTS). Banner viaprintIdentityBannerBestEffortbefore each routed call (suppressed by--quiet,GBRAIN_NO_BANNER=1, non-TTY default). Exhaustive TSneverswitch onRemoteMcpError.reasonfor canned, actionable error messages. ENG-2 renderer parity: local-engine path runsJSON.parse(JSON.stringify(result))so renderers see the same shape on both paths (kills Date/bigint/Buffer drift class).src/core/mcp-client.ts—callRemoteTool(config, toolName, args, opts). Hardened in v0.31.1 (CDX-4): all transport errors normalized toRemoteMcpErrorvia thetoRemoteMcpErrorfunnel. NewCallRemoteToolOptions {timeoutMs, signal};buildAbortControllercomposes external signal with timeout. NewRemoteMcpErrorReasonstable union,RemoteMcpErrorDetail.kind('timeout' | 'aborted' | 'unreachable') sub-tag,RemoteMcpErrorDetail.codefield carrying server-supplied error codes (e.g.missing_scope).extractToolErrorCodeparses JSON envelopes first, falls back to substring detection for legacy server messages.unpackToolResult<T>(res)unchanged (parses tool-call JSON content)._clearMcpClientTokenCache()test escape.src/core/cli-options.ts—parseGlobalFlagsadds--timeout=Ns(accepts30s,2m,500ms, plain ms). Defaultnull= per-command default (30s for most ops, 180s forthink).parseTimeout(s)exported helper.src/core/doctor-remote.ts—gbrain remote doctoradds theoauth_client_scopes_probecheck (CDX-5). Probes the read tier viaget_brain_identityand admin tier viaget_health; reports per-tier status with pinpoint remediation when admin is missing.buildScopeCheckScopeProbeResultexported for test access. Skippable viaGBRAIN_DOCTOR_SKIP_SCOPE_PROBE=1for fixtures that mock /mcp at JSON-RPC initialize level only (MCP SDK Client hangs on shape mismatch).
src/core/ssrf-validate.ts(v0.36 Commit 0) — DNS-rebinding-defended URL validation.validateAndResolveUrl(url)resolves the hostname viadns.lookup({all: true, family: 0}), checks EVERY A AND AAAA record against the internal-IP deny list, returns the resolved IP so callers fetch by IP (defeats DNS rebinding: validation IP === fetch IP).fetchWithSSRFGuard(url, opts)does redirect-aware fetching with per-hop re-validation, max 3 hops by default. Reusable across all URL-fetching features. Test seam__setDnsLookupForTestsfor hermetic tests.src/core/search/query-intent.tsextension (v0.36 cross-modal wave) — newsuggestedModality: 'text' | 'image' | 'both'axis onQuerySuggestions. Module-scopeCROSS_MODAL_PATTERNSregex array (compiles once at module load).isAmbiguousModalityQuery(query)heuristic gate fires when a visual noun + reference marker combination indicates genuinely ambiguous routing — used by the Commit 4 LLM tie-break to bound LLM calls to <1% of queries.src/core/search/mode.tsextension (v0.36 cross-modal wave) —ModeBundleextended with 7 cross-modal knobs:cross_modal_both_text_weight/cross_modal_both_image_weight(D6 weighted RRF for'both'mode, defaults 0.6/0.4),image_query_text_refinement_weight/image_query_image_refinement_weight(D13 hybrid intersect forsearchByImagequery refinement, defaults 0.4/0.6),unified_multimodal+unified_multimodal_only(Phase 3 unified column routing flags),cross_modal_llm_intent(Commit 4 opt-in escalation).SEARCH_MODE_CONFIG_KEYSextended with 7 corresponding config keys.KNOBS_HASH_VERSIONbumped 2→3 (D2 — closes the silent cache-hit class where a cached text-mode result could leak to an image-mode caller).src/core/search/hybrid.tsextension (v0.36 cross-modal wave) — cross-modal routing branch at the embed step. ResolveseffectiveModalityfrom per-callopts.crossModal(normalized: literal'auto'→ undefined per D22-1) →suggestions.suggestedModality→'text'default. Image route:embedQueryMultimodal+searchVector({embeddingColumn: 'embedding_image'}), skip expansion + keyword (D9 mode-bundle override). 'both' route: parallel text + image vector searches merged viarrfFusionWeightedwitheffectiveRrfK(baseRrfK, weight)from the configured cross-modal weights. Phase 3 unified routing fires whencfg.search.unified_multimodal === true— bypasses dual-column branching, runsembedQueryMultimodal+searchVector({embeddingColumn: 'embedding_multimodal'}), D8 fail-open on zero rows + not strict-mode falls through to dual-column. Commit 4 LLM escalation fires only when (no explicit per-call opt) AND (regex returned 'text') AND (cfg.search.cross_modal.llm_intentis true) AND (isAmbiguousModalityQueryreturns true). Fail-open on every error.src/core/search/image-loader.ts(v0.36 Phase 2) —loadImageInput(input, opts)accepts local path,data:URI, orhttp(s)://URL. Magic-byte sniff for PNG/JPEG/WebP. Hard size cap (default 10 MB, configurable viasearch.image_query.max_bytes). For URLs: routes throughfetchWithSSRFGuardso DNS rebinding + redirect chains are defeated. Pre-flight Content-Length check + post-fetch size guard for lying servers.ImageLoadErrorwith discriminatedcode(INVALID_FORMAT / OVERSIZED / INVALID_URL / FETCH_FAILED / TIMEOUT / SSRF_BLOCKED / NOT_FOUND).src/core/search/by-image.ts(v0.36 Phase 2) —searchByImage(engine, input, opts). Always runs image branch (embedQueryMultimodalImage+searchVector(embedding_image)). D13 hybrid intersect: when caller provides optionalquery, runs parallel text branch viaembedQueryMultimodal(query)and merges viarrfFusionWeightedwith weights from resolved mode. Phase 3 widens to unified column oncesearch.unified_multimodal=true(transparently upgrades the retrieval quality post-reindex).src/core/spend-log.ts(v0.36 Phase 2 D23-#6) — per-OAuth-client paid-API spend tracking against themcp_spend_logtable (migration v74).checkBudget(engine, clientId, capCents)is the pre-flight gate; throwsBudgetExceededErrorwhen today's spend has hit the cap.recordSpend(engine, entry)is best-effort post-call. UTC day-aligned aggregation so caps roll over deterministically regardless of server timezone. Local CLI callers (no clientId) bypass the gate. Pre-v0.36 brains without the table fail open to spend=0.VOYAGE_MULTIMODAL_3_PER_IMAGE_CENTS= 0.12 cents per image embed.src/core/search/llm-intent.ts(v0.36 Commit 4) — opt-in LLM tie-break.classifyModalityWithLLM(query, fallback)routes throughgateway.chat()with a fixed single-word-output system prompt. 1s timeout via AbortController.parseModality(raw, fallback)is the pure parser — tolerates trailing punctuation + casing. Fail-open on every error (gateway unavailable, timeout, parse failure, unrecognized output) — returns fallback so a misbehaving LLM can never break search. Cost-bounded by the ambiguity heuristic inquery-intent.ts(fires <1% of queries when on).src/commands/reindex-multimodal.ts(v0.36 Phase 3) —gbrain reindex --multimodal [--limit N] [--dry-run] [--cost-estimate] [--no-embed] [--yes] [--json]. Walkscontent_chunks WHERE embedding_multimodal IS NULL, batches viaembedMultimodalSafe(Commit 0 partial-failure-aware), persists. D7 lock acquisition viatryAcquireDbLock('gbrain-reindex-multimodal', 360min). Cost prompt + 10s Ctrl-C grace window in TTY.GBRAIN_NO_REEMBED=1bypass. Checkpoint at~/.gbrain/reindex-multimodal-checkpoint.jsonfor resume. D23-#2 auto-flip prompt at coverage=100% completion (TTY: interactive; non-TTY: stderr hint with paste-ready command).src/core/backfill-registry.tsextension (v0.36) — newmodalitybackfill kind. SQL filter requireschunk_source='image_asset'ANDembedding_image IS NOT NULLAND(modality IS NULL OR modality != 'image'). D22-7 defensive guard: never flag a non-image chunk that happens to haveembedding_imagepopulated. Idempotent — second run finds zero rows.src/core/migrate.tsv74 (mcp_spend_log) + v75 (embedding_multimodal_column) — Phase 2 spend-log table + Phase 3 unified column ALTER. v75 is column-only (no HNSW index — deferred to post-reindex per pgvector best practice). v74 uses BTREE on(client_id, created_at)+(token_name, created_at)—date_trunc('day', TIMESTAMPTZ)is NOT IMMUTABLE so can't appear in index expressions; range scan on created_at covers the per-day rollup query.src/core/operations.ts—get_brain_identityop (read scope, no params, banner-only): cheap counter packet{version, engine, page_count, chunk_count, last_sync_iso}for the thin-client identity banner. Reusesengine.getStats(); banner's 60s client-side TTL bounds frequency to ≤1/60s per CLI process (well below the Fly.io health-check cadence that motivated the originalgetStatscost warning).src/commands/{salience,anomalies,graph-query,think}.ts— Per-command thin-client routing branches. These commands bypass the operation-layer dispatch in cli.ts (callengine.foo()directly), so each gets its ownif (isThinClient(cfg)) { callRemoteTool(...) }branch that maps CLI flags to op params.thinkis a special case: the server'sthinkop intentionally disables--save/--takefor remote callers (operations.ts:1103-1135 trust-boundary gate); thin-clientthinkwarns loudly when those flags are set.