9. Playbook: effective multi-repo graphs¶
A checklist distilled from this exercise.
Before building¶
- Graph each repo separately first. Per-repo graphs are cheaper to update (
--updatere-extracts only changed files) and easier to read. - Put the contract in prose. The cross-repo link in this study came from a docstring that named the other service. A short
INTEGRATIONS.mdper repo ("we call X at$LLM_API_URL, OpenAI shape, 20-minute timeout") gives semantic extraction something to work with. - Make data visible if it matters.
.db/.csvfiles are skipped. If the schema is central, keep aschema.mdor rely on theCREATE TABLEstrings in code.
Building¶
- Default mode for code-heavy repos. AST does most of the work, and semantic extraction only touches docs.
- Deep mode where the docs carry architecture. See the measured comparison. It adds INFERRED edges from prose but can't see HTTP calls written only in code.
- Review AMBIGUOUS and low-score INFERRED edges. Treat them as a queue: each one is either a missing import resolution or a doc claim that needs a real target.
Merging¶
- Merge with
graphify merge-graphsinto a separate file, and query it with--graph, so per-repo graphs stay intact. - Filter external-module nodes before counting or pathing across repos.
- Keep a cross-repo edge register and re-apply it after every merge:
// cross_repo_edges.json
[
{"source": "drone-okg::gem_insight_generator_mine_specs_llm_call_local_llm",
"target": "modal_io_serve::src_qwen3_serve_servel40s",
"relation": "calls_http", "confidence": "EXTRACTED", "confidence_score": 1.0,
"evidence": "mine_specs_llm.py L133-137: LLM_API_URL default"}
]
import json
g = json.load(open("cross-repo-graph.json"))
ids = {n["id"] for n in g["nodes"]}
for e in json.load(open("cross_repo_edges.json")):
assert e["source"] in ids and e["target"] in ids, f"stale edge: {e}" # catches renames
g["links"].append({**e, "_origin": "manual", "weight": 1.0})
json.dump(g, open("cross-repo-graph.json", "w"), indent=2)
The assert matters. If a refactor renames ServeL40S, the register fails loudly instead of the edge disappearing without anyone noticing.
Finding cross-repo edges semi-automatically¶
Grep both repos for the strings that indicate a network boundary:
grep -rnE "os\.environ\.get\(\"[A-Z_]*(URL|HOST|ENDPOINT)" repo-a/
grep -rnE "@app\.(get|post)|@modal\.web_server|FastAPI\(" repo-b/
Match client URL defaults to server route and app names, then add one edge per match.
Querying¶
- Start with
queryfor orientation, usepathto test a hypothesis, and useexplainfor a single node. - When the graph says "no connection", search rationale nodes next. Comments and docstrings often name the other system.
- Name the communities. It takes a few minutes, and every later report and query reads better for it.
Visualizing¶
- Above roughly 5,000 nodes graphify switches to an aggregated community view. Below that, check that labels are visible (see Communities).