Architecture

Three deterministic passes, one analyzed graph, four artifacts

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

KindId formExample
librarylibrary:<name>library:simple_http
clustercluster:<lib>/<name>cluster:simple_http/src
classclass:<NAME>class:SIMPLE_HTTP
featurefeature:<CLASS>.<name>feature:SIMPLE_HTTP.get
documentdoc:<relative/path>doc:docs/design.md
rationaletag:<CLASS>#<n>tag:SIMPLE_HTTP#2

Performance characteristics

Extension points