Docs · graph schema
Graph schema
Nodes, edges, semantic layers, AST verification
Seven semantic layers
Every node carries a layer tag. The Structure agent picks one of seven, deliberately small enough that an LLM can pick reliably and large enough that the viewer can render distinct colors per layer.
| Layer | What lives here |
|---|---|
infra | Build, CI, deployment, hosting glue |
data | Schemas, migrations, persistence |
logic | Core domain code |
api | HTTP routes, RPC handlers, controllers |
ui | React/Vue/Svelte components, templates, styles |
test | Tests and test fixtures |
config | Settings, env, lockfiles, manifests |
The viewer paints each layer its own color (see LAYER_COLORS in src/lib/graph/types.ts).
Node kinds
interface CausalNode {
id: string;
label: string;
path?: string;
layer: SemanticLayer;
language?: string;
kind?: "file" | "module" | "function" | "type" | "external";
size?: number;
summary?: string;
}
file/module— source files or logical units (a directory with a role).function/type— AST-level entities for languages where we resolve them.external— npm / cargo / pip dependencies.
Edge kinds
Causalist ships five edge kinds. Each is typed — most "dep graph" tools collapse everything into a single "depends on" relationship and lose the structure that makes the graph useful.
| Edge | Semantics |
|---|---|
imports | X statically imports Y (AST-derivable in JS/TS via @babel/parser, regex+import scan in Python). |
calls | X invokes Y. Higher-level than imports; LLM-inferred today. |
reads | X reads state from Y (DB row, config key, cache). |
writes | X mutates Y. Narrower than reads; invariant-critical for security audits. |
extends | X inherits / implements / conforms to Y. |
interface CausalEdge {
source: string;
target: string;
kind: "imports" | "calls" | "reads" | "writes" | "extends";
verified?: boolean;
}
AST verification
Every edge gets a verified flag after the Oracle agent finishes. The verifier (src/lib/analyze/ast-verify.ts) walks the real source via @babel/parser for JS/TS and a line-scan for Python, then stamps:
verified: true— the AST contains a matching import/require/dynamic import statement that justifies the edge.verified: false— Oracle proposed the edge but no AST entry backs it up.
The viewer renders verified edges as solid lines and unverified ones at lower opacity with a thinner stroke, so users (and downstream agents) can trust-gate the graph at a glance. We chose this binary signal over a continuous confidence score because it's cheap to verify and unambiguous to display.
Importance tiers
The viewer ranks every node by fan-in (how many edges point at it) and bins them:
hot— top ~10%, the load-bearing files. Painted magenta.core— the next ~15%.leaf— everything else; nothing depends on these (safe to refactor).
See src/lib/graph/importance.ts::rankImportance. The Agent tab uses this implicitly — pointing a plan-mode agent at a hot node will surface more downstream impact than picking a leaf.
Rendering stack
The 3D and 2D viewers are built on Vasco Asturiano's 3d-force-graph and react-force-graph — Three.js + d3-force-3d for the WebGL canvas, with React bindings. Causalist consumes them via react-force-graph-3d and react-force-graph-2d (dynamically imported with ssr: false).
The lib's nodeThreeObject API caches each node's mesh on node.__threeObj and only invokes the factory once per node. We exploit that: the factory is useCallback([]) and pre-creates named children (core, stroke, ring, arc); a useEffect watches selection / hover / focus state and mutates those cached children directly via getObjectByName(...). This is the maintainer's recommended pattern (see 3d-force-graph#61 and react-force-graph#204) — keeps a custom mesh stable while still reflecting React state changes, without re-igniting the simulation on every hover.