Pipeline
GRAPHIFY_BUILDER
|
+--------------+---------------+
| | |
GRAPHIFY_FS (file lists) |
| |
v v
Pass 1a: GRAPHIFY_ECF_SCANNER Pass 1b/1c: GRAPHIFY_CLASS_SCANNER
library nodes class nodes (simple_eiffel_parser)
depends_on edges inherits edges (+external parents)
cluster topology supplier scan: uses/creates/mentions
containment map rationale mining (NOTE:/WHY:/TODO:)
deep mode: feature nodes
+--------------+---------------+
v
Pass 2: GRAPHIFY_DOC_SCANNER markdown -> document nodes,
references edges to classes/libraries
v
Pass 3: GRAPHIFY_ANALYZER degrees, communities (label propagation),
god nodes, surprising connections
v
GRAPHIFY_GRAPH (analyzed)
|
+--------+-----+------+----------+
v v v v
graph.json GRAPH_ graph.html graph.dot
(JSON_IO) REPORT.md (HTML) (DOT via simple_graphviz)
Design decisions
Deterministic by construction
Every ordering is defined (ids sorted, edges keyed by from|to|kind, stable merge
sort throughout; label propagation is synchronous with ordinal seeds and
smallest-label tie-breaks). Identical input yields byte-identical
graph.json apart from the generated_at header. This is
what makes graphs git-diffable and team-mergeable, and it is enforced by a test
that builds twice and compares serializations.
No LLM in the binary
Upstream Graphify's third pass sends documents to an LLM. simple_graphify keeps the
executable fully local; semantic enrichment is delegated to the AI assistant via
annotate, which merges assistant-authored edges tagged
inferred. The assistant is the semantic pass.
Parser strategy
simple_eiffel_parser (Gobo-backed, lightweight fallback) provides class
structure: names, flags, parents with rename/redefine, features with types.
Contract depth is now read structurally from the full Gobo AST. Each
file is parsed with ET_EIFFEL_PARSER; per feature the tool counts the
actual require/ensure assertion clauses (ET_ASSERTIONS.count) and per class
the invariant clauses, exposed as the preconditions,
postconditions, invariant_clauses and
contract_clauses metrics. This counts individual clauses, so a
single require block with three tagged assertions scores 3 — where a
keyword-line count would score 1. The AST factory sets
set_keep_all_comments (True) (the mechanism Gobo's own gedoc
uses for pretty-printing), so comments are retained in the AST as well. A keyword-line
count survives only as a floor for the small number of files Gobo cannot parse (e.g.
a C3 character constant that trips the decorated factory), which fall back to a plain
factory. Class descriptions are still taken from a raw-source scan of the
note description: clause; mining descriptions from the now-retained AST
comments is the remaining structural step.
(Both the exclusion of free_elks/thread from the Gobo
analysis and this structural rewrite are courtesy of corrections from Eric Bezault.)
Supplier scan, comment-aware
Each source line is split into code and comment parts (string-literal aware,
%-escape aware). ALL_CAPS tokens in code matching the scanned class
set become uses edges (creates when written
create {X}); tokens appearing only in comments become
mentions edges tagged inferred. Matching is restricted
to the project's own class set to avoid false positives on arbitrary uppercase
words — the residual weakness is project classes with short keyword-like
names (e.g. a Rosetta task class named FOR).
Communities from usage, not folders
Label propagation runs over topology edges only (inherits/uses/creates/references/
mentions), excluding contains/depends_on. Including
containment would collapse each library into one star-shaped community and hide
the actual coupling structure. Community labels come from majority library
membership, resolved through the containment chain.
ECF parsing without an XML library
ECFs are scanned with a tolerant tag/attribute string scanner (attribute order and namespaces irrelevant; prolog/comments/closing tags skipped). Precedent: claude_tools' UUID scanner. This dodges XML-namespace pitfalls for a format that is machine-written and highly regular.
Hand-emitted JSON out, simple_json in
Serialization is hand-emitted to guarantee field order and byte stability; loading
uses simple_json. The seam is a single class
(GRAPHIFY_JSON_IO).
Node identity scheme
| Kind | Id form | Example |
|---|---|---|
| library | library:<name> | library:simple_http |
| cluster | cluster:<lib>/<name> | cluster:simple_http/src |
| class | class:<NAME> | class:SIMPLE_HTTP |
| feature | feature:<CLASS>.<name> | feature:SIMPLE_HTTP.get |
| document | doc:<relative/path> | doc:docs/design.md |
| rationale | tag:<CLASS>#<n> | tag:SIMPLE_HTTP#2 |
Performance characteristics
- Build is linear in total source size; the Gobo parse dominates. Measured: 107-file library in ~4 s; 5,717-file ecosystem in ~40 s (2,332 classes, 28,410 edges).
- Query commands load graph.json once and run in memory; sub-second on ecosystem-scale graphs.
- graph.json for an ecosystem-scale build is a few MB; single libraries are tens to hundreds of KB.
Extension points
- Incremental update: the deterministic pipeline makes a content-hash cache straightforward (planned; full rebuilds are currently fast enough).
- MCP server: the query engine returns plain strings, ready to wrap as MCP tools.
- Git hook: a post-commit rebuild is a one-line hook calling
build. - New artifact emitters: implement against
GRAPHIFY_GRAPH's sorted accessors (see GRAPHIFY_DOT for the pattern, ~80 lines).