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.

LayerWhat lives here
infraBuild, CI, deployment, hosting glue
dataSchemas, migrations, persistence
logicCore domain code
apiHTTP routes, RPC handlers, controllers
uiReact/Vue/Svelte components, templates, styles
testTests and test fixtures
configSettings, 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.

EdgeSemantics
importsX statically imports Y (AST-derivable in JS/TS via @babel/parser, regex+import scan in Python).
callsX invokes Y. Higher-level than imports; LLM-inferred today.
readsX reads state from Y (DB row, config key, cache).
writesX mutates Y. Narrower than reads; invariant-critical for security audits.
extendsX 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.