galaxy-brain
Health Uyari
- License — License: Apache-2.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Gecti
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
The deterministic harness a coding agent should be standing on: code shape, call graph, diff impact and crash state in milliseconds, offline, with no model in the loop. One CLI, 17 languages, zero runtime dependencies.
galaxy-brain
The deterministic harness a coding agent should be standing on.
Facts about your code — what shape it has, who calls what, what a diff moved, and (in Python)
where it died and with what state — delivered in milliseconds, offline, with no model in the loop.
Your agent stops guessing and starts reading.
Take the model out of your architecture diagram. Everything still visible is the harness.
galaxy-brain is that harness. Not a better prompt, not an autonomous loop: the layer underneath
both, which supplies the evidence they need and can be trusted because nothing in it is inferred by
a model.
The graph reads 17 languages — Python with the stdlib ast, plus 16 more through ast-grep,
integrated by reference: JS, TS, TSX, Go, Rust, Java, Kotlin, Swift, Ruby, PHP, Lua, Scala, Elixir,
C#, C and Dart. Every one has a conformance probe in the suite, so "supported" is a claim you can
re-verify, not a list of intentions — and what a language can't do is printed in its own output
rather than hidden. The error console is multi-language too, but only where a runtime offers a
hook that observes without handling — same exit code, same trace
(ADR 0012): Python, JS/TS (locals included, through the
inspector), the JVM, C#, Ruby, PHP, Lua and C natively; Go and Rust through a stderr wrapper
(gb-run.py). Python arms itself with gb on; the rest need one variable in the shell that
launches them (NODE_OPTIONS, JAVA_TOOL_OPTIONS…), which gb on --lenguajes prints — gb cannot
export it for you. gb status says, per language, whether it is armed and what it cannot see.
In a mixed repo, both engines run. Python is parsed with the stdlib ast, the other 16 throughast-grep, and in a repo that has both, the two graphs are merged into one — a Python backend
and a TypeScript frontend are in the same picture, and a cycle among TypeScript files blocks the
gate exactly like a Python one. Edges between language families exist only where the source
names both ends literally: a Tauri invoke("cmd") to its #[tauri::command], an ElectronipcRenderer.invoke("channel") to its ipcMain.handle("channel"), a Python import of a pyo3#[pymodule], and a test that launches another program. What is not done is guess the rest: afetch("/users") cannot be tied to a Flask view without resolving the runtime, so the report says
so in not_covered instead of faking a link. Name collisions keep Python's name and suffix the
other (web/app.py stays web.app, web/app.ts becomes web.app:ts). ast-grep remains
optional: without it you get the complete Python report plus a line naming what was skipped.
Until 15 Aug 2026 this was not true — Python excluded the rest, so a mixed repo was
analyzed half-way and said nothing about it. Measured on a 2 .py + 2 .ts
bench: 2 modules of 4, gate green, zero warnings. That was a false green, and
ADR 0010 is the fix and the
autopsy.
One tool, gb. A single Python package, zero model calls on the hot path, zero
dependencies beyond the standard library. An exception is a fact; the state at the moment of
failure is a fact; the shape of the import graph is a fact. Reporting facts requires no judgment,
which is why it can be instant and cannot fail in expensive ways.
Disambiguation: in the 2026 "harness / loop / graph" vocabulary, graph means agent
orchestration. The graph here is a graph of your code — modules, symbols, call edges, parsed
from the AST.
v0.7.0 · 1159 tests · 19.3k LOC source / 13.2k LOC tests · clean gate · ruff · Python ≥ 3.9 · zero runtime dependencies (ast-grep optional, only for non-Python graphs) · CLI output is Spanish by default — GB_LANG=en switches the console (capture notice, gb show/last, graph anchor) and the verifier (gb tests selection + checkpoint, gb check) to English; floor and map still Spanish, coming next
The map, live: four agents at once.
20s excerpt · ▶ full clip with controls (1m46s, no audio)
Four agents editing one repository at the same time — a small demo project, four git worktrees, and
no orchestrator telling the map anything. gb who --html --watch re-derives the whole picture
from disk every few seconds: which symbols each agent touched, where two of them cross on the
same node, each agent's own console, and the verdict ladder it leaves behind when it finishes. When
they were done, gb tests --union --run verified each branch alone and then all four together —
54 tests, every row green.
Every card on that screen is a fact read off the disk. Nothing is declared, nothing is inferred, and
no model was asked.
Table of contents
- The thesis
- Wired into the agent
- What it saves you
- Installation
- Day one
- Command reference
- The error console
- The map
- What a change did
- The floor
- Cross-repo memory
- How it is verified
- Cost, measured
- Settings
- Secrets
- Known limits, stated up front
- The design law
- Development
- Documents
The thesis
An agent working on your code is limited by one thing: what it knows about the code it cannot
see. Given a 50-module repo, it opens files to find out what is there, greps to guess who calls
what, and re-runs a crashed program with print statements to learn what already happened. Each of
those is a guess standing in for a fact that a parser could have handed over in 200 ms.
So the bet is not a better prompt or a smarter loop. It is this:
A deterministic ecosystem underneath. The AI as the cherry, not the engine.
Putting a model on the path that always runs makes that path expensive, slow and optional — and
optional things get abandoned. Every fact gb serves comes from the AST, from git, or from the
interpreter's own hooks. No inference, no API key, no network, nothing to be wrong about.
Don't iterate on trust; iterate on evidence. gb is the evidence supplier. The loop and the
orchestration belong to whatever is driving — Claude Code, your CI, you. gb provides; it does not
orchestrate, and SCOPE.md says so as a limit, not as a roadmap.
What it upgrades, concretely
| Without it | With it |
|---|---|
| Agent greps for a function, opens 4 files, guesses the call sites | gb calls <symbol> --depth 2 — callers and callees with file:line, exact |
Agent re-runs the crashed script with print to see the values |
The values were already captured at death: gb show <id> |
| Agent starts each session blind to the repo's shape | SessionStart hook injects the whole map: ~110 tokens, 162 ms |
| Agent searches, gets file paths, reads them to learn the signatures | Every search carries the matching symbol cards along |
| "Did my change break something far away?" | gb check --staged — the blast wave, before the commit |
| A fact learned last week, re-learned today | gb memory — durable notes, surfaced in any repo |
| Quality gate that flags style and gets bypassed | Only facts gate; proxies inform (rule 11) |
The last row is the one that took a rewrite to learn. Gating proxies — coupling churn, smells,
test-shape heuristics — manufactures false positives, false positives train people to type--no-verify, and after that the gate protects nothing. So the only two things that stop a commit
here are facts you cannot argue with: a new import cycle, or a crossing of a boundary you
declared yourself.
What it is not
- Not an autonomous loop, and not a bundle of skills or agent prompts. There is no
skills/
directory and there never will be. Prompt scaffolding depends on a model obeying instructions; gb
is the half that holds when it doesn't. - Not a code reviewer. It does not decide whether your code is good. It reports what is there.
- Not a model wrapper. Zero API calls, zero dependencies beyond the standard library. It works
on a plane. - Not useful for failing tests — pytest catches the exception, so it never reaches
sys.excepthook. Usepytest -l, which prints the same locals. That negative result is written
down in docs/pruebas-de-uso.md rather than hidden. - Not a server, not an MCP server. SCOPE.md has the reasoning, including the one
condition that would reopen the MCP question. - Not multi-language everywhere. The graph reads 17 languages and so does the gate — import
cycles and declared boundaries hold in any of them, and a bank of 18 real open-source repos across
the 17 languages pins symbols, edges, calls and inheritance against code nobody wrote for gb. The
error console captures where the runtime has an observing hook (see above); Dart and Swift do
not yet. Narrowing test selection is licensed per language — Python,js,ts,go,c#,java,php,lua,rustandrubytoday, each earned with a bench of real failures. The rest
have not failed a measurement: they have no bench yet, sogb testsruns the whole suite and says
why. A call graph with holes doesn't cost you savings; it costs you a false green — so when the
caller chain dies on a method the graph cannot see being called (value.method()outside Python),gb testsruns everything instead of narrowing.
The two halves
Both are the same discipline — exact answers about your code — pointed in different directions:
The backbone: the graph. Modules and symbols derived from the AST on every look — never
declared, never maintained by hand. Who calls what, what imports what, with file:line.graph · symbols · calls
Everything else lands on its nodes. Backward, at what already happened: the error console
records an uncaught exception anywhere in your Python environment with the state around it — and
anchors it to its node and its callers, so the failure that happened once while you were not looking
does not have to be reproduced. last · list · show · on · off · status
Forward, at what you are about to touch: the blast wave of a diff and the tests it selects, the
classic errors a change added, the project's missing scaffolding, and the facts that outlive a
session. check · tests · delta · floor · memory
The hot path, drawn
The six boxes across the top are the real hot path and they are exactly what ships: an observed
Python process (gb on), capture through the three native hooks — sys.excepthook,threading.excepthook, sys.unraisablehook — raw append-only facts in ~/.galaxy-brain, the
derived graph plus the floor, and an agent that reads facts instead of guessing. No model is
consulted anywhere in that row; the budget under it (<1 s per edit, <10 s per commit) is
architecture, not aspiration. The certainty ladder and the fact/proxy/human split are rule 11 in
picture form: only facts gate, patterns inform.
What the diagram draws ahead of the code: of the adaptive learning loop, only the
replay bench ships (bucle/replay.py: it re-runs the recorded runs against the
current verifier — 13/13 reproduce, and blinding the verifier turns it red). Clustering and
hypothesis generation do not exist, and graph generations with rollback never will: versioning
the graph means persisting it as the source of truth, which this project banned — what is learnable
is the ruleset, not the graph. The catalogue of known LLM error patterns as built-in
detectors is likewise design intent; today gb delta reports classic errors a diff introduced, and
the "actas" box belongs to the loop runner in bucle/, deliberately outsidegb (gb provides, it does not orchestrate). Everything else
in the poster is shipped and measured.
Wired into the agent
The facts are useless if someone has to remember to ask for them. The norm goes in the default,
not in the prompt: a rule that requires typing a flag depends on someone remembering, and sooner
or later it fails. So one hook makes the harness ambient — the agent gets the facts without asking,
and without knowing gb exists.
| Hook | What it injects | Measured |
|---|---|---|
SessionStart |
The compressed map of the repo, a count of unread captures when there are any, and any broken wiring (a hook calling a gb flag that no longer exists, a pre-commit that is not engaged, gb off the PATH) |
~110 tokens, 162 ms |
There used to be three. A hook after each edit (only what changed in the shape) and one on everyGrep/Glob (gb calls --hook, the matching symbol cards) were retired on 2026-08-13 with the
measurement in front: informing per action changed nothing (0/6), and a default that changes no
outcome is paid noise. gb calls --hook still exists for anyone who wires it by hand; floor --init
no longer does.
A symbol card is what lets an agent call code it has not read — signature straight from the AST
(args, defaults, *, async, the decorators that change the call), location, first docstring line,
and who depends on it, with sources split from tests:
galaxybrain.store.parse_ts(value) · function · src\galaxybrain\store.py:270 — El `ts` de una entrada…
le llaman (7 — 6 de src, 1 de tests):
The wiring ships per project — gb floor --init writes it into .claude/settings.json, merging
with each machine's own settings, so a fresh clone gets an aware agent with no global setup. The
format is the cross-tool one (AGENTS.md), read natively by Claude Code, Codex, Cursor, Copilot and
Aider, so the awareness is not tied to one vendor.
The model does not know gb exists; its context does. And because every hook is a CLI command,
none of this is Claude-specific: point any orchestrator at gb <command> --json and you have the
same evidence.
What it saves you
A traceback tells you where. This also tells you with what:
KeyError: 'empresa'
hace 1min · facturacion/precios.py:6 · mi-api
facturacion/precios.py:6 in precio_total
4 |
5 | def precio_total(cliente, cupon=None):
→ 6 | base = TARIFAS[cliente["plan"]]
7 | unidades = cliente["asientos"]
8 | return base * unidades
cliente = {'nombre': 'Beto', 'plan': 'empresa', 'asientos': 12}
cupon = None
The step that disappears is relaunching the program with a print in it. The failure happens once,
often while you are not looking; reproduction is the expensive work this removes.
Installation
pip install -e . # from this repo
pip install git+https://github.com/Llicklair/galaxy-brain.git # straight from GitHub
gb on # enables capture in this Python environment
gb status # verifies it stuck
gb on drops a .pth file into site-packages. From then on there is nothing to remember: every
Python process in that environment is covered, without touching any project's code.
Bringing it to another project is one command, with that project's venv active:
# macOS / Linux — instala.sh (locates itself; python3 by default, PYTHON=... to pick another)
sh <path-to-this-repo>/instala.sh
# Windows — instala.ps1, the same thing
powershell -ExecutionPolicy Bypass -File <path-to-this-repo>\instala.ps1
With a venv active they install locally, into that venv; without one, globally, into the
Python on your PATH. Two things the scripts handle so you don't have to: Homebrew's python3 and
Ubuntu 23.04+ refuse a global pip install (PEP 668, "externally-managed-environment"), so there it
goes to your user site — which that same interpreter loads at startup; and a user-site install can
leave gb off your PATH, so the scripts drive everything through python -m galaxybrain.cli and
tell you where gb landed. Every combination (macOS, Windows, Linux × venv, system Python) is
installed for real in CI, a real exception is raised, and its capture on disk is required
(instalacion.yml).
Coverage is per Python environment, not per repo. Being editable (-e), a git pull here
updates every environment with no reinstall. To remove it: gb off — one line, no residue. Cheap
removal is deliberate (rule 10: abandonment is data, not something to armor against).
Day one
git init my-project && cd my-project
gb floor # the floor: what is missing before you build, and why each piece matters
gb floor --init # drops the base documents — and the pre-commit hook
On a fresh git repo the session map suggests this path by itself — one line, only in the
unambiguous case (git present, no code, no floor docs), silence otherwise. --init leaves seven
pieces, never overwriting anything:
| Piece | Why it is there |
|---|---|
AGENTS.md |
Executable context in the cross-tool format read by Claude Code, Codex, Cursor, Copilot and Aider — including the gb usage contract for agents |
SCOPE.md |
What is in, what is out, and the done criterion |
ARCHITECTURE.md |
The design law, so later decisions have something to cite |
docs/adr/README.md |
An ADR folder in MADR form: one file per decision that was expensive to make |
docs/evidencia.md |
The evidence log, so choices cite measured numbers instead of folklore — including the failures, because a project that records only what worked has advertising, not evidence |
.githooks/pre-commit |
The gate wired in ratchet mode: inherited debt does not block, only new debt does. Hook it once with git config core.hooksPath .githooks |
.claude/settings.json |
Wires the agent at project level — session map, edit delta and symbol cards travel with the repo, merging with each machine's own settings |
One thing no tool can write for you, and gb says so out loud: the done criterion in SCOPE.md.
You write it before the first line of code, because not knowing when to stop is the number one
cause of over-engineering, and the cure costs one sentence.
Command reference
Seventeen subcommands, and every one belongs to a family. A command that does not fit a family does not
ship — there is no "small exception", because small exceptions are exactly how a monster gets built.
Where it crashed, and with what state
| Command | What it does |
|---|---|
gb last |
This project's latest failure, with its state |
gb last --full |
The same, with every frame kept |
gb list -n 20 |
The history grouped by signature: what breaks, and how often |
gb list --chrono |
The raw timeline, most recent first |
gb list --all |
Every project, not just this one |
gb list --efimeros |
Include python -c / stdin captures (hidden by default, and said so) |
gb show <id> |
One specific failure — the id comes in the capture notice |
gb on / gb off |
Enable / disable capture in this environment |
gb status |
What is active right now, and how many captures are unread |
gb status --cobertura |
Runs 8 real failure modes and shows which ones leave a record |
What shape it has
| Command | What it does |
|---|---|
gb graph <path> |
Coupling map: imports, cycles, hotspots |
gb graph --gate |
Exit code ≠ 0 on cycles or declared-boundary crossings — for pre-commit |
gb graph --gate --since HEAD |
Ratchet: only new debt fails |
gb graph --boundaries FILE |
Layering rules (defaults to .gb-boundaries at the root) |
gb graph --smells |
Over-engineering proxies — advisory, never blocks |
gb graph --self-test |
Injects known defects and fails if the gate does not see them |
gb graph --context |
The compressed map as a session payload; silent when there is nothing to say |
gb symbols <path> |
Symbol graph: who calls whom, with its resolution coverage |
gb symbols --since REF |
What grew since that ref, marked apart |
gb calls <symbol> |
Callers and callees of a symbol, with file:line |
gb calls <symbol> --depth 2 |
The wave: also who calls the callers |
gb calls --hook |
PreToolUse mode: reads hook JSON from stdin, silent when there is nothing |
Shared flags worth knowing: --json on every reporting command for raw output (gb on/gb off flip state and take none). (The HTML canvas was retired
on 2026-08-13 after two tied A/Bs — and came back on 2026-08-14 as gb who --html, renderer only,
writing the project's mapa.html; gb who --watch --html keeps it live by foreground polling. The
self-managed watcher machinery, which was the measured culprit, stays retired — the amended
sentence lives in SCOPE.md.)
What each change did
| Command | What it does |
|---|---|
gb check |
What a diff did to tests, coupling, and its wave (default range HEAD~1..HEAD) |
gb check --staged |
Reviews the index instead of a range — the only correct thing in a pre-commit |
gb check --brief |
One line when there are no signals, for hooks |
gb tests |
Which tests to run for what changed, derived from the call graph |
gb tests --staged |
The same over the index, for a pre-commit |
gb tests --staged --run |
Also executes them — exit code is pytest's |
check informs and never blocks. Its signals are proxies, and proxies that gate manufacture the
false positives that end in --no-verify.
What it is missing at the base
| Command | What it does |
|---|---|
gb floor |
The minimum scaffolding a project needs before building |
gb floor --init |
Drops the seven base pieces, never overwriting |
gb floor --time |
Times the suite against the DORA threshold — runs the tests, so it is opt-in |
What was learned, across repos
| Command | What it does |
|---|---|
gb memory index |
The compact index, one line per note |
gb memory recall <words> |
Full text of the most relevant notes |
gb memory context |
The session payload (what the SessionStart hook calls) |
gb memory add --name x --description "..." --scope always |
Add or overwrite a note (body via --body or stdin) |
The error console
The backward-looking half: the state at the moment of death, kept so nobody has to reproduce it.
An agent handed this does not re-run your program with print statements; it reads what already
happened. That is the single most expensive guess it makes, removed.
With no arguments, gb last and gb list filter by the repo you are standing in. And the failure
card ends in the graph — the crash anchored to the symbol whose body contains the line, with
its blast wave one command away:
en el grafo: lib.base · function · lib.py:5
le llaman (1): lib.ayuda
The anchor is honest about time: it resolves against today's code, and if the file changed
after the capture it says so, with the exact commit — instead of silently pointing at whatever
occupies that line now.
One failure type, three exit doors. "Uncaught exception" is not a synonym for sys.excepthook:
the interpreter lets failures out through three doors, and all three are covered.
| Door | When | Status |
|---|---|---|
sys.excepthook |
The exception kills the main thread and the process | covered |
threading.excepthook |
It kills a threading thread; the process lives on |
covered (GB_NO_THREADS=1 opts out) |
sys.unraisablehook |
Python could not propagate it: __del__, weakrefs, GC |
covered |
The third door was the only one that vanished without a trace: the interpreter prints it, the
process does not die, and nothing was left to show. None of the three costs anything while your
program works.
What triggers a capture and what does not — executed, not documented:
gb status --cobertura # runs 8 real failure modes and shows which leave a record
LO QUE SI deja registro LO QUE NO (y es correcto)
+ excepcion no capturada - asyncio: tarea suelta que nadie espera
+ excepcion en un hilo - sys.exit(1)
+ excepcion en __del__ - KeyboardInterrupt
+ asyncio fuera de run() - excepcion atrapada por try/except
The boundary is not documented — documents age. It is demonstrated every time you run it.
The history lives in ~/.galaxy-brain, append-only, outside the observed repo: the harness never
dirties the project it is watching (rule 7).
The map
The forward-looking half: what shape the project has and who calls whom — the questions an agent
answers today by opening files and grepping. All deterministic, zero models, zero dependencies.
Clicking a node answers with facts: its description (taken from the docstring, not from a
model), who calls it, whom it calls, what it imports, whether it sits in a cycle — plus the layers
of history: the error-cycle rings (captured → read → intervened → silent) and a halo on whatever is
in progress, uncommitted, right now. Imports are drawn differently from calls because an
import is exact and a call is inferred — merging them into one number would mean gating on a
proxy.
Two honest numbers ship with every run: gb symbols declares what it could not resolve
(object.method() calls require type inference, and here nothing is guessed: a false edge gets
believed, a missing one gets noticed), and measured against an inference-based index it scores
93% recall with zero dependencies. Details and the negative results live in
docs/pruebas-de-uso.md.
The graph is always derived, never persisted. A stored map is a map that goes stale and lies;
recomputation is fast enough that caching would buy milliseconds and cost correctness.
What a change did
gb check --staged # before committing
gb check HEAD~5..HEAD # what the last five commits moved
It reports what a diff did to the tests, to coupling, and to the blast wave of the symbols it
touched. Its whole output is advisory. The one thing in this area that can stop a commit lives ingraph --gate, and only for two facts: a new import cycle, or a crossing of a boundary you
declared in .gb-boundaries.
A crossing is checked on both ways of depending on a module — importing it and calling into
it. A -/-> B promises "A does not depend on B", and there are languages where you reach another
module without importing it: crate::b::f() in Rust, or the same package in Java, C# and Kotlin.
Checking only imports left that door open, and a real agent walked through it.
The same fact has a second consequence, on the other side of the tool: in those languages the module
graph can legitimately come back empty. Run gb graph on a Java project whose classes share a
package and you get 3 modules, 0 internal edges — not because nothing is coupled, but because
there is no import to derive it from. So gb says which of the two it is:
3 modulos, 0 aristas internas, 0 ciclo(s)
OJO con el 0: hay modulos y ninguna arista, y el motor de este arbol tiene un limite conocido:
- dos ficheros del mismo paquete se usan SIN import en Java, asi que no dejan arista de
modulo: 0 aristas aqui NO significa 0 acoplamiento. Las llamadas entre ellos si se ven
Every language declares what its engine cannot see, and gb prints it next to the number it qualifies
— only for the languages actually present in the tree. A zero that means "nothing here" and a zero
that means "not visible from here" used to print identically; that is the failure mode
ADR 0008 exists to prevent.
Dependencies the code never confesses
The graph is derived, never declared — but it can only derive what an import statement writes down. A
repo that talks to its Go service over HTTP, to a worker over subprocess, or to another module
through a CLI has real dependencies that no static analyser can see, and they are missing from the
cycles, the fan-in, the test selection and the map. A => B writes one down by hand:
app.web => svc.payments # declared: HTTP, subprocess, CLI, IPC — anything static analysis misses
app.web -/-> app.db # forbidden: A must not depend on B
ENTRY = app.web, app.api # a group, expandable in both forms above
FUERA = app # reserved group: these exact modules are deliberately outside the
# "no rule mentions them" warning — the decision prints, the nagging stops
A declared edge is an edge like any other: it is injected before anything is computed, so it
closes cycles, counts in fan-in/out, reaches the test selection and gets drawn on the map — and the
boundaries govern it, so => is no back door around a -/-> you declared.
The token is => and deliberately not -->: you get --> by dropping the slash from -/->, and
that one-character typo would have turned "A must not depend on B" into "A depends on B" — the exact
opposite of the rule you meant to write, passing green. --> stays a malformed line that gets
reported.
Which tests to run for what changed
gb tests --staged # the selection and why those
gb tests --staged --run # and run them; exit code is pytest's
The call graph already knows who calls whom, so walking up from the symbols a diff touches reaches
the tests that exercise them. Measured on this repo: 5 symbols, 5 of 5 the same exit code as the
full suite, saving between 20% and 97% of the wall clock (32 s → 1–26 s). The saving collapses
exactly where the symbol is heavily coupled, which is honest information about the design rather
than a failure of the method.
The rule that governs it: when in doubt, everything. A selection that leaves out a test that
would have failed is not "less coverage", it is a false green — so a touched conftest.py, a
change outside every symbol, an unreadable diff, or a symbol no test reaches all return the whole
suite, with the reason stated out loud. Silence would read as a saving that never happened.
Two details that cost a measurement to learn, and that any implementation of this needs: select
files, not test ids (one non-existent id makes pytest exit 4, no tests ran, which in a gate
reads exactly like "everything passed"), and filter on the name starting with test_ rather than
on living under tests/ — a helper called _generar lives in a test file, the graph sees it, and
pytest does not collect it.
This is the one place gb runs code from the observed project, so --run is explicitly opt-in: the
default prints the list and lets you decide.
The floor
gb floor reads a repo and reports what a project needs before it can be worked on reliably —
whether there is a declared test command, a lint or type gate, CI, isolation, an agent context file,
a record of past decisions. It detects what you already use rather than prescribing a stack:
ruff, flake8, pylint, eslint, biome, golangci-lint, mypy, pyright, tsconfig, prettier, black,
rustfmt, GitHub Actions, GitLab CI, CircleCI, Jenkins, Docker, devcontainers. Nothing is hardwired
and nothing is vendored — a project-specific path would be a bug (rule 6), and external tools are
integrated by reference, never bundled.
Then --init writes only what is missing, never overwriting, and the pre-commit it drops is wired in
ratchet mode: inherited debt passes, new debt fails. A gate that fails on day one over debt you
did not create gets disabled on day two.
The one thing it cannot write for you — the done criterion — it tells you to write yourself. Not
knowing when to stop is the number one cause of over-engineering, and the cure costs one sentence.
Cross-repo memory
A fact learned in one repo should not die there. gb memory is a vault of durable markdown notes
(in ~/.claude/memory-global, hand-editable, with [[wikilinks]] so Obsidian opens the graph)
that surface in any project via a SessionStart hook.
Lean by design: session start injects the index of all notes but the full text only of always
notes and this project's; the rest is pulled on demand with recall. The vault is never dumped
whole — context is the scarce resource, and spending it on notes nobody asked for is the failure
mode this design avoids.
How it is verified
Three layers, because tests alone only pin what you already knew how to check.
1. The suite — 1159 tests, ~480 s (measured 2026-09-24). Runs on every commit via the pre-commit
hook, still under the 600 s DORA threshold — but no longer far under it.
2. The gate is verified by breaking it. A gate degrades in silence: it keeps returning zero and
stops looking at anything.
gb graph --self-test # injects 6 known defects and fails if the gate does NOT see them
gb graph src --self-test # additionally: relations that must hold on YOUR code
3. Metamorphic relations — "these two ways of asking must agree", evaluated on your real repo:
the same folder spelled c: or C: yields the same shape; a cycle's imports are edges that exist;graph and symbols see the same modules. That is where most of the real defects lived.
4. Behavior is demonstrated, not asserted. gb status --cobertura executes eight failure modes
and shows which leave a record. A document claiming the same thing would eventually be wrong without
anyone noticing.
Cost, measured
The budget is measured, not estimated (rule 4). Python 3.11, Windows 10, median of 20 cold starts:
| Clean interpreter startup | 21.2 ms |
| With the hook installed | 27.7 ms |
| Hook cost | 6.4 ms (A/B measured: 5.2 ms) |
— of which, import threading |
5.2 ms |
While your program works, the cost is zero. Not "low": zero. The hooks only run once the
process has already failed. For a tiny CLI that never touches threads: GB_NO_THREADS=1.
Other measured numbers: session map ~110 tokens in 162 ms · symbols 93% recall · search hook
430 ms (this repo) / 330 ms (600 synthetic modules) · gb show with anchor 158 ms · ruff 130 ms ·
the suite in ~30 s.
The budget is architecture, not performance: < 1 s per edit, < 10 s per commit. Exceeding it is
a design violation to be fixed by removing something, not an optimization task for later.
Settings
Everything via environment variables; there is no config file to maintain.
| Variable | Default | What it does |
|---|---|---|
GB_DISABLE |
off | Turns capture off without uninstalling anything |
GB_QUIET |
off | Silences the one-line notice printed after the traceback |
GB_HOME |
~/.galaxy-brain |
Where the history lives |
GB_NO_THREADS |
off | Skip thread exceptions (saves 5.2 ms of startup) |
GB_ALL_FRAMES |
off | Also keep locals of library frames |
GB_MAX_FRAMES |
20 | Frames kept (innermost survive) |
GB_CONTEXT_LINES |
2 | Source lines around the failing one |
GB_OPEN_CMD |
browser | What opens the map (--open); receives the path as last argument |
GB_OPEN_CMD exists because gb knows no editor and will not maintain a list: a hardwired
command is a bug (rule 6 — nothing project-specific, everything detected at runtime). Point the map
wherever you want (GB_OPEN_CMD="firefox --new-window").
Secrets — redaction by name, with an honest residue
The state around a failure is exactly where credentials live. The console redacts by name
(password, token, api_key, secret, auth, credential, session, cookie…). The trigger
is always the name, never the content: guessing whether a string is a secret is expensive and
fallible; the name was written by a human, on purpose.
The residue, said plainly: a secret without a sensitive name next to it reaches disk — a
positional literal (connect("hunter2")), a password inside a URL (user:pass@host), a secret
value under an innocent name. Closing that would require content heuristics, which this project
rejects deliberately because they fail and sell false safety. That is why the golden rule does not
depend on redaction: the history lives in your $HOME in plain text; treat it as sensitive and
upload it nowhere.
Known limits, stated up front
- Uncaught exceptions only. An
except: passthat swallows the failure is invisible here — and
rightly so: a handled exception is, by definition, one its author decided was not a failure. - Failing tests are not covered. pytest (and jest, vitest…) catches the exception, so no hook
ever sees it. Usepytest -l. - Main thread,
threadingthreads, and finalizers (__del__, weakrefs, GC).asynciowith
stray tasks nobody awaits stays out, as doesmultiprocessing; if the exception propagates out
ofasyncio.run(), it is captured. - No source file, no code context.
python -c,exec()and the REPL keep type, message,
frames and state, but not the surrounding lines (gb listsets them apart as ephemeral). - The state is the state at death, not a time-travel debugger.
- Unrepresentable objects are described, not reconstructed. A
__repr__that blows up leaves<Type: repr() failed with X>and does not take the rest of the frame down with it. - Non-Python deaths — a segfault, an OOM kill, a
kill -9— raise no exception, so no hook
ever sees them. - Not in Ubuntu's system Python. Its
sitecustomize.pyinstalls apport's excepthook after
every.pth, replacing gb's;gb onnames it and refuses instead of pretending. Use a venv
there, where apport is not visible. macOS and Windows, venv or global, are covered. - Local only. Code running in a browser is out of reach of the console. No CI, no UI, no
server, no MCP server (see SCOPE.md for
why, and for the single condition that would reopen it). - Call edges are inferred.
object.method()needs type inference; unresolved calls are
declared, not guessed. Python resolves more of them than the other 16 languages, which is why
test selection saves less there (46–62% on the JS, C# and Rust benches) — safely, not falsely. - Adoption is the one thing not measured. Latency, overhead, recall and coverage all have
numbers behind them. Whether people keep using it does not — and by rule 10, if you stop, that
gets investigated, never blocked with a hook that forces you back.
The design law
Eleven rules, in ARCHITECTURE.md; a change that violates one is rejected in
review rather than argued about. The load-bearing ones:
- Zero models on the hot path. Capturing, storing, showing and analyzing consult nobody.
- Return, don't rule. Every run ends by handing over something the user wanted. A function
whose only output is a verdict is misplaced — anything that only says no is a tax. - Latency budget, non-negotiable. < 1 s per edit, < 10 s per commit.
- Overhead on the observed process measured, not estimated.
- One runtime, one failure type — multi-language in both halves. Local execution, uncaught
exceptions only; a language joins the graph with its conformance probe, and the console only
through a hook that observes without handling. - Facts stored raw. Exception, trace and state persist exactly as captured.
- History local, append-only, outside the observed repo.
- AI only after the fact, explicit and optional.
- Fail silently toward the safe side. If capture breaks, the observed program continues as if
gb were not installed. - Abandonment is data, not a bug to armor against.
- Proxies inform, they don't block. Only facts gate.
Two working principles sit above all of them: subtract before you polish (maintenance cost does
not grow with size, it grows worse — every part rubs against every other, so the first question
about a problem is what to remove), and evidence over folklore (a decision cites a measured
fact; "other frameworks do it" is not a reason).
Development
python -m pytest tests/ -q # the suite — 1159 tests, ~480 s
python -m ruff check src tests # lint (catches defects, holds no style opinions)
gb graph src --gate # the gate, clean
The pre-commit (.githooks/pre-commit) runs lint + suite + gate + gb check --staged — ~8.5 min measured on 2026-09-06: the suite dominates and the gate's ast-grep sweep adds
~11 s (was ~18 s before lenguajes.analyze learned to not re-derive an unchanged tree twice in one
invocation), so rule 3's < 10 s commit budget is currently exceeded here, and that is a known open
debt, not a rounding error. Hook it once with git config core.hooksPath .githooks. git commit --no-verify skips it — and that skip is a datum, not a rule.
The console's layering rules live in src/.gb-boundaries: the core (capture,
store, analysis) does not import the presentation (cli, render). A new crossing stops
the commit; a test-softening signal only informs.
Commits are type: short description (feat, fix, refactor, docs, chore), one logical
change each, with behavior changes and documentation changes kept apart.
Documents
| Document | What it holds |
|---|---|
| ARCHITECTURE.md | The design law: the thesis, the command families, the eleven rules |
| SCOPE.md | What is in, what is deliberately out, the done criteria per family, and the failure criteria written while they do not hurt yet |
| docs/research-report.md | The measured evidence decisions cite |
| docs/pruebas-de-uso.md | The usage notebook, including the negative results |
| CLAUDE.md | The contract for agents working in this repo |
Decision documents are in Spanish for coherence; anything published is in English.
License
Apache License 2.0 — Copyright 2026 Marcos Recio. You may use, modify, fork and
distribute this, including commercially, provided you keep the notice and state your changes; it
also grants you the contributors' patent rights. See NOTICE.
ast-grep, used by reference for the non-Python graph, is never bundled and stays under its own
license.
Facts first. Reasoning after.
We do not make the model smarter. We make the system less vulnerable to its mistakes.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi