Polyglot CPG-Stack (Code Property Graph) fΓΌr lokales Live-Coding und
Agent-Guardrails. lmc bringt beide Backends selbst mit:
- Joern-Backend (Docker, echtes CPGQL / Data-Flow) β startbar via
lmc up. - tree-sitter-Gateway (polyglot, instant Navigation) β
find/callers/callees/impact/source/context.
Kein externer Server nΓΆtig.
Regel #1: Bei strukturellen Fragen ("Wer ruft Methode X auf?") raten Agenten nie β sie fragen
lmc.
c, cpp, csharp, go, java, javascript, kotlin, php, python,
ruby, scala, swift, typescript.
Spracherkennung pro Worktree per Datei-Endung; dominierende Sprache wird in
lumos.yml abgelegt. Joern selbst spricht zusΓ€tzlich Data-Flow/Taint (via
lmc query).
Zweigleisig (ehrlich):
- Navigation (
find/callers/callees/impact/source/context) β tree-sitter, namensbasierte Call-AuflΓΆsung, instant, polyglot. Keine dynamische Dispatch-Analyse. lmc query "<CPGQL>"β echtes Joern-CPGQL (Data-Flow, Taint, beliebige Joern-Queries) gegen den gebauten CPG im Joern-Container.
BenΓΆtigt Python >=3.10, uv und Docker (fΓΌrs Joern-Backend).
uv sync # AbhΓ€ngigkeiten aus uv.lock
uv run lmc --help # CLI (Entry-Point: lmc = lmc.cli:app)Oder global als Tool:
uv build
uv tool install dist/lumos_code-1.0.1-py3-none-any.whl --force
lmc up # baut einmalig das Joern-Image + startet Backend + GatewayAgent / Mensch
β typer CLI (--json)
βΌ
βββββββββββββββββββββββββββββββββ
β Lumos-Gateway (Starlette 4243)β tree-sitter: find/callers/callees/
β MCP tools/call β impact/source/context (instant, polyglot)
βββββββββββββββββ¬ββββββββββββββββ
β (lmc query / run_cpgql_query)
βΌ
βββββββββββββββββββββββββββββββββ
β Joern-Backend (Docker, 8085) β echtes CPGQL / Data-Flow
β Container lmc-joern β CPGs im Volume lmc-cpgs (<hash>.bin)
β Image lmc-joern:latest β (docker/Dockerfile, Joern v4.0.601)
βββββββββββββββββββββββββββββββββ
lmc up startet beide; lmc down stoppt beide (Volume + CPGs bleiben erhalten).
This section explains what is running, who starts what, and when an AI agent
(pi or Claude Code) reaches for which lmc command.
your machine (one-time setup)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β uv tool install dist/lumos_code-1.0.1-*.whl β ~/.local/bin/lmc β global CLI
β docker build -t lmc-joern:latest docker/ β image (β2.4 GB) β built on first `lmc up`
β ~/.pi/agent/skills/lumos-code/SKILL.md β pi skill
β ~/.claude/skills/lumos-code/SKILL.md β Claude Code skill
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
runtime (per project)
lmc up
ββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββ
β Lumos Gateway β β Joern Backend β
β Starlette + uvicorn β β Docker container β
β 127.0.0.1:4243/mcp β β lmc-joern (joern --server)β
β in-memory: hash β Index β β 127.0.0.1:8085 REST β
β (tree-sitter CPG) β β volume lmc-cpgs/*.bin β
βββββββββββββββ¬βββββββββββββ βββββββββββββββ¬ββββββββββββββ
β JSON-RPC tools/call β POST /query β poll /result/<uuid>
β (find/callers/callees/ β (raw CPGQL, data-flow, taint)
β impact/source/context/ β
β methods-of/callees-of-class) β
ββββββββββββββββββ¬ββββββββββββββββββββ
β
ββββββββββββ΄βββββββββββ
β lmc CLI (typer) β stateless; --path gives context
β ~/.local/bin/lmc β --hash overrides, --url points elsewhere
ββββββββββββ¬βββββββββββ
β --json (agents always use --json)
βββββββββββββββββββββ΄βββββββββββββββββββββ
β AI agent (pi or Claude Code) β
β loads skill lumos-code on-demand β
ββββββββββββββββββββββββββββββββββββββββββ
The gateway is the fast brain (tree-sitter, instant, polyglot, works even
if Joern is down). The Joern container is the deep brain (real CPGQL, data
flow, taint β slower, needs Docker). Both are started by lmc up and stopped
by lmc down. The CLI itself is stateless; the worktree β CPG mapping lives in
lumos.yml (per worktree) and ~/.cache/lumos/worktrees.json (registry).
time βββΊ
T0 first time ever (once per machine)
lmc up βββΊ builds lmc-joern:latest image (downloads Joern archive)
βββΊ starts joern container (8085) + gateway (4243)
T1 entering a project
lmc init --auto --path . βββΊ scans file extensions β writes lumos.yml
{language, codebase_hash}
lmc build --path . βββΊ tree-sitter index (gateway, in-memory)
βββΊ joern-parse (volume: <hash>.bin)
βββΊ registers worktree in worktrees.json
T2 working (repeated, cheap)
lmc callers / callees / impact / find / source / context / methods-of
(default engine = treesitter β instant, in-process)
βββΊ agent re-runs these while editing, no rebuild needed
T3 planning a risky change (once, expensive)
lmc impact <m> --engine joern --depth 3 βββΊ full blast radius via Joern
lmc query "<CPGQL>" βββΊ data-flow / taint via Joern
T4 before commit
lmc check-diff --path . (alias: lmc precommit)
βββΊ reads `git diff` β maps changed files onto CPG β lists affected methods
T5 leaving / shutting down
lmc down βββΊ stops container + gateway (volume + *.bin survive)
The skill lumos-code is loaded on-demand (matching task, or forced via
/skill:lumos-code). Rule #1 from the skill: never guess on structural
questions β ask lmc. The agent then runs this loop:
agent gets a coding task
β
ββ new/unknown worktree?
β ββ lmc init --auto --path <w> --json (writes lumos.yml)
β
ββ no CPG yet, or files/methods changed?
β ββ lmc build --path <w> --json (refresh index + <hash>.bin)
β
ββ PLANNING a signature/logic change on a widely-used method
β ββ lmc impact <m> --engine joern --depth 3 --json β the BIG, accurate view
β β (Joern: type-aware call graph, full blast radius)
β ββ optional: lmc query "<CPGQL>" --json (data-flow / taint depth)
β
ββ CODING β quick repeated checks while editing
β ββ lmc callers / callees / impact <m> --json β default = treesitter (instant)
β
ββ "who calls X?" / "where is X defined?" / "what does X call?"
β ββ lmc callers / source / callees <m> --json (treesitter)
β ββ need it accurate? add --engine joern
β
ββ about to commit
β ββ lmc check-diff --path <w> --json (or lmc precommit)
β status: safe β commit Β· review β fix warnings first
β
ββ done for now
ββ lmc down (optional; leaves volume intact)
βββββββββββββββββββββββββββββββββββββββ
β structural question? β
ββββββββββββββββββββ¬βββββββββββββββββββ
β
planning / first analysisβ quick check while coding
(need accuracy) β (need speed)
β
--engine joern β (default) treesitter
βΌ βΌ βΌ
βββββββββββββββ ββββββββββββββββ
β Joern 8085 β β Gateway 4243 β
β CPGQL query β β tree-sitter β
β ~2β5 s β β <50 ms β
β type-aware β β name-based β
β data-flow β β β data-flow β β
βββββββββββββββ ββββββββββββββββ
lmc up
docker build (if image missing) β docker run -d lmc-joern (joern --server, 8085)
β wait for REST up + warmup query (consumes REPL banner)
β start python -m lmc.server (gateway, 4243) as background process (pidfile)
lmc init --auto --path .
walk tree, count file extensions β dominant language
β write lumos.yml {language, codebase_hash = sha1(abspath)[:16]}
lmc build --path .
gateway: graph.build_index() β in-memory Index (methods + call edges)
joern: docker run --rm -v <worktree>:/code:ro -v lmc-cpgs:/cpgs \
lmc-joern <frontend> /code --exclude /code/vendor ... -o /cpgs/<hash>.bin
worktree: register hash β {path, language, joern_built, joern_bin}
lmc <nav> --path . (default engine = treesitter)
CLI β POST /mcp {tools/call, find_methods|get_call_graph|get_source|...}
β gateway answers from in-memory Index β JSON
lmc <nav> --engine joern (accurate engine)
CLI builds CPGQL β POST joern:8085/query β poll /result/<uuid>
β parses `fullName|name|file|line` records β same JSON shape as treesitter
lmc query "<CPGQL>" --path .
CLI β importCpg("/cpgs/<hash>.bin"); <CPGQL> β Joern REST β cleaned stdout + result
lmc check-diff --path .
git diff --name-only β gateway find_methods(.* ) β filter to changed files
β warnings = CPG methods sitting in changed files β status safe|clean|review
lmc down
kill gateway pid (pidfile) β docker rm -f lmc-joern (volume + *.bin kept)
lmc CLI (identical) skill discovery
ββββββββββββββββββ βββββββββββββββ
pi ~/.local/bin/lmc ~/.pi/agent/skills/lumos-code/SKILL.md
Claude ~/.local/bin/lmc ~/.claude/skills/lumos-code/SKILL.md
Code
Both harnesses discover lumos-code from their global skills dir, load the
same SKILL.md, and drive the same lmc binary. The agent never imports
lmc as a Python package in normal use β it shells out to the CLI with
--json. The library API (lmc.server.graph, lmc.joern, β¦) is there for
scripts and power users.
lmc up # Joern-Image bauen + Backend + Gateway starten
lmc init --auto --path . --json # lumos.yml schreiben (Sprache erkennen)
lmc build --path . --json # tree-sitter-Index + Joern-CPG (<hash>.bin)
lmc find "Order" --path . --json # Methoden finden (instant)
lmc impact OrderController.store --path . --depth 3 --json # Blast-Radius (instant)
lmc query 'cpg.method.name.l.mkString("\n")' --path . --json # echtes Joern-CPGQL
lmc check-diff --path . --json # Precommit-Guardrail
lmc down # Backend + Gateway stoppenGlobale Flags (fΓΌr alle Befehle): --url <gateway> und --hash <cpg> (sonst
Auto-AuflΓΆsung aus --path). FΓΌr Agenten immer --json.
| Befehl | Zweck |
|---|---|
lmc up |
Joern-Image (einmalig) + Joern-Container (8085) + Gateway (4243) starten |
lmc down |
Beide stoppen (Volume/CPGs bleiben) |
lmc serve [--host] [--port] |
Gateway im Vordergrund (Debug) |
| Befehl | Zweck |
|---|---|
lmc init --auto --path <w> --json |
Sprache erkennen + lumos.yml schreiben |
lmc build --path <w> [--scope sub] --json |
tree-sitter-Index + Joern-CPG bauen; --scope fuer Teilbaeume |
lmc status --path <w> --json |
Gateway- + Joern-Status + CPG-Frische |
| Befehl | Zweck |
|---|---|
lmc find <pattern> --path <w> [--engine joern] --json |
Klassen/Methoden per Regex finden |
lmc callers <m> --path <w> [--engine joern] --json |
Wer ruft diese Methode auf? |
lmc callees <m> --path <w> [--engine joern] --json |
Was ruft diese Methode auf? |
lmc source <m> --path <w> [--engine joern] --json |
Quelltext + Datei:Zeile |
lmc context <m> --path <w> [--engine joern] --json |
Caller + Callee + Source gebΓΌndelt |
lmc methods-of <Class> --path <w> [--engine joern] --json |
Alle Methoden einer Klasse |
lmc callees-of-class <Class> --path <w> [--engine joern] --json |
Alle von einer Klasse aufgerufenen Methoden |
lmc impact <m> --path <w> --depth N [--engine joern] --json |
Blast-Radius (rekursiv) |
Planungs-Disziplin: Beim Planen/Refactoring zuerst --engine joern (groΓe,
genaue Sicht, langsam), danach den Default tree-sitter fΓΌr die schnellen
iterativen Checks beim Coden.
| Befehl | Zweck |
|---|---|
lmc check-diff --path <w> --json |
git diff auf CPG mappen (Precommit) |
lmc precommit --path <w> --json |
Alias fΓΌr check-diff |
| Befehl | Zweck |
|---|---|
lmc query "<CPGQL>" --path <w> --json |
Rohe Joern/CPGQL-Abfrage gegen den CPG (Data-Flow/Taint mΓΆglich) |
- Backend:
lmc upeinmalig (baut Joern-Image, startet Container + Gateway). - Kontext managen: Neues Verzeichnis? Auf
lumos.ymlprΓΌfen, sonstlmc init --auto. Bei Monorepos immer--pathmitgeben. - Vor Refactoring:
lmc impact <Method> --jsonund alle Depth-1-Aufrufer anpassen. - Pre-Commit:
lmc check-diff --jsonβ betroffene Methoden beheben. - Cross-Boundary: Frontend-Γnderung mit HTTP-Call β Route extrahieren,
--path ./backendwechseln, Backend-Controller perlmc impactprΓΌfen. - Frische-Disziplin: Neue Dateien/Methoden β
lmc build --path .. - Data-Flow/Taint: FΓΌr tiefe Analysen
lmc query(Joern-CPGQL).
Language-Detection, tree-sitter-Index und Joern-CPG filtern dieselben Pfade heraus, bevor sie in den Graph gelangen:
.git, node_modules, vendor, .venv, venv, dist, build, __pycache__,
.idea, .vscode, target, bower_components, storage, var, cache
Das verhindert, dass Dependency-BΓ€ume (Laravel vendor, npm node_modules,
Python .venv β¦) den CPG aufblΓ€hen und die Analyse lahmlegen. Nur
Projekt-Quelldateien landen im Index.
Eigenes Image lmc-joern:latest aus docker/Dockerfile (basiert auf
eclipse-temurin:21-jdk + offiziellem Joern-Release v4.0.601). Beim ersten
lmc up wird es automatisch gebaut (~einmalig, Download des Joern-Archivs).
- Container
lmc-joernlΓ€uft mitjoern --server(REST auf 8085). - CPGs liegen im Docker-Volume
lmc-cpgsals<hash>.bin. lmc builderzeugt sie per<frontend> --exclude ...(php2cpg, jssrc2cpg, pysrc2cpg, β¦ je nach Sprache β keine Excludes mehr im genericjoern-parse);lmc querylΓ€dt sie perimportCpgund fΓΌhrt CPGQL aus.
Manuell bauen (optional, vorab):
docker build -t lmc-joern:latest docker/from lmc.server.graph import build_index
idx = build_index("hash", "php", "./src") # tree-sitter
idx.impact("OrderController.save", 3)
from lmc.joern import run_cpgql, joern_parse # Joern-Backend
joern_parse("./src", "hash")
run_cpgql("hash", 'cpg.method.name.l.mkString("\\n")')Module: lmc.server.graph (Extractor/Index: find/callers/callees/source/context/impact/methods_of/callees_of_class), lmc.server.store,
lmc.server.app (Gateway), lmc.server.lifecycle (up/down),
lmc.joern (Joern-REST + parse + nav_*), lmc.diff (check_diff als Funktion),
lmc.worktree (State-Registry), lmc.config (lumos.yml/Spracherkennung).
uv build # dist/lumos_code-1.0.1-py3-none-any.whl + .tar.gztyper >=0.27 # CLI
rich >=13.0.0 # Terminal-Output
httpx >=0.25.0 # HTTP-Client (Joern REST + Gateway JSON-RPC)
pyyaml >=6.0.1 # lumos.yml parsing
tree-sitter >=0.26 # CPG-Parsing (polyglot)
tree-sitter-language-pack >=1.14 # Parser-Factory fΓΌr alle Sprachen
starlette >=0.27 # Gateway (Starlette/uvicorn)
uvicorn >=0.23 # ASGI-Server fΓΌr Gateway
lmc/
βββ __init__.py
βββ cli.py # typer CLI (alle Befehle + globale Flags)
βββ client.py # CodebadgerClient (httpx JSON-RPC an Gateway)
βββ config.py # lumos.yml + polyglote Sprach-Erkennung
βββ diff.py # check_diff Funktion (git diff β CPG mapping)
βββ joern.py # Joern-REST-Client + joern-parse (Docker)
βββ worktree.py # Worktree-State-Registry (~/.cache/lumos/worktrees.json)
βββ server/
βββ __init__.py
βββ __main__.py # `python -m lmc.server` (uvicorn-Gateway)
βββ app.py # Starlette JSON-RPC-Gateway (tree-sitter Tools)
βββ graph.py # tree-sitter CPG-Extractor + Index/Queries
βββ store.py # in-memory Hash -> Index
βββ lifecycle.py # up/down (Joern-Container + Gateway)
docker/Dockerfile # eigenes Joern-Backend-Image (lmc-joern:latest)
pyproject.toml # Hatchling-Build + Entry-Point `lmc`
uv.lock
SKILL.md # Guardrail-Regeln fΓΌr Coding-Agenten
tests/
βββ test_graph_nonascii_offsets.py # Regression: Non-ASCII Byte-Offset-Fix
βββ test_joern_parse_excludes.py # Regression: Dependency-Exclude-Fix
Siehe LICENSE.