<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Carl Ward</title>
    <description>The latest articles on DEV Community by Carl Ward (@carl_ward_2d0e6fee9693587).</description>
    <link>https://dev.to/carl_ward_2d0e6fee9693587</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3960028%2Fbcbdb814-bd54-42cf-9c13-8ff904e64982.jpg</url>
      <title>DEV Community: Carl Ward</title>
      <link>https://dev.to/carl_ward_2d0e6fee9693587</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carl_ward_2d0e6fee9693587"/>
    <language>en</language>
    <item>
      <title>Modelling a codebase as a requirements ontology in Neo4j, keeping AI coding agents oriented</title>
      <dc:creator>Carl Ward</dc:creator>
      <pubDate>Sat, 30 May 2026 13:57:26 +0000</pubDate>
      <link>https://dev.to/carl_ward_2d0e6fee9693587/modelling-a-codebase-as-a-requirements-ontology-in-neo4j-keeping-ai-coding-agents-oriented-1db</link>
      <guid>https://dev.to/carl_ward_2d0e6fee9693587/modelling-a-codebase-as-a-requirements-ontology-in-neo4j-keeping-ai-coding-agents-oriented-1db</guid>
      <description>&lt;p&gt;AI coding agents have an expensive habit: before they write a single line, they re-read source files to work out what already exists — which modules there are, what each one provides, what's tested, and what's currently being changed. On a small repo that's tolerable. Run several agents in parallel on one codebase and it becomes both a token sink and a coordination problem: two agents start the same feature, a test gets added that nobody can map to a requirement, an architectural decision made in one session is invisible to the others.&lt;/p&gt;

&lt;p&gt;I kept hitting this running multiple Claude Code sessions against a single codebase, and ended up solving it the way you'd expect on a graph-shaped problem: model the codebase's &lt;em&gt;requirements chain&lt;/em&gt; as an ontology in Neo4j, and let the agents query the graph instead of re-reading the source.&lt;/p&gt;

&lt;p&gt;This post is about the data model and the queries — and why a graph is the right tool here rather than a table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model
&lt;/h2&gt;

&lt;p&gt;The core idea is full requirements traceability, from a user story down to the unit test that verifies a routine. Every artefact is a node; the relationships carry the meaning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysUserStory&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:REALIZED_BY&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysUseCase&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysUseCase&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:REQUIRES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysFeature&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysModule&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:PROVIDES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysFeature&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysModule&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:CONTAINS_SYMBOL&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysSymbol&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// :SysSymbol / :SysEndpoint -[:IMPLEMENTS]-&amp;gt;(:SysFeature)&lt;/span&gt;
&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysTest&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:VERIFIES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysFeature&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;                &lt;span class="c1"&gt;// tier via t.testType, or (:SysTestPackage {testCategory})-[:CONTAINS_TEST]-&amp;gt;(:SysTest)&lt;/span&gt;
&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysArchDecision&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:ADDRESSES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysArchStd&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;SysFeature&lt;/code&gt; isn't a ticket — it's a capability a &lt;code&gt;SysModule&lt;/code&gt; provides. A &lt;code&gt;SysUseCase&lt;/code&gt; isn't a description — it's a user-visible flow that realises a story. Every test carries its V-model tier — component, integration, use-case, or e2e — and a &lt;code&gt;VERIFIES&lt;/code&gt; edge tying it to the feature it covers. So "is this feature covered at every tier?" stops being a judgement call and becomes a reachability question.&lt;/p&gt;

&lt;p&gt;Reachability — and its mirror, &lt;em&gt;absence&lt;/em&gt; — is exactly what a graph answers cheaply, and it's the whole reason this lives in Neo4j rather than a table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a graph, not a table
&lt;/h2&gt;

&lt;p&gt;The questions you actually want to ask of a codebase's requirements are reachability and absence questions, and those are one traversal in Cypher and an awkward pile of &lt;code&gt;NOT EXISTS&lt;/code&gt; joins in SQL.&lt;/p&gt;

&lt;p&gt;Which features have no use case covering them?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;f:&lt;/span&gt;&lt;span class="n"&gt;SysFeature&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysUseCase&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:REQUIRES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;f.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f.name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which architecture standards have no decision addressing them — i.e. the genuine architecture gaps?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;std:&lt;/span&gt;&lt;span class="n"&gt;SysArchStd&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysArchDecision&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:ADDRESSES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;std.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;std.name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which features are missing integration-tier coverage?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;SysModule&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:PROVIDES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;f:&lt;/span&gt;&lt;span class="n"&gt;SysFeature&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="ow"&gt;EXISTS&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;t:&lt;/span&gt;&lt;span class="n"&gt;SysTest&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:VERIFIES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t.testType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'integration'&lt;/span&gt;
     &lt;span class="n"&gt;OR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:SysTestPackage&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;testCategory:&lt;/span&gt;&lt;span class="s1"&gt;'integration'&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:CONTAINS_TEST&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;f.id&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f.name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A gap is just a node with no incoming edge of a given type (here, no verifying test in a given tier). That framing is what makes "is this actually tested?" a query rather than an opinion — the &lt;code&gt;VERIFIES&lt;/code&gt; edge either exists or it doesn't. Run a one-day pass of agents over a real codebase and you can watch coverage fill in as a &lt;em&gt;shape&lt;/em&gt; across the four V-model tiers (unit → integration → use-case → e2e), not as a single misleading percentage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feeding the graph to the agent
&lt;/h2&gt;

&lt;p&gt;Here's the part that matters for the agents. Instead of letting a session open a 2,800-line handler file to orient, it runs a query and gets a compact briefing — coverage by module, open work, what's in progress — serialised to a few hundred tokens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Coverage-by-module briefing for one agent's scope&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;m:&lt;/span&gt;&lt;span class="n"&gt;SysModule&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;instance:&lt;/span&gt;&lt;span class="n"&gt;$instance&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:PROVIDES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;f:&lt;/span&gt;&lt;span class="n"&gt;SysFeature&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;f.status&lt;/span&gt; &lt;span class="ow"&gt;IN&lt;/span&gt; &lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Superseded'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Deprecated'&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;OPTIONAL&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;t:&lt;/span&gt;&lt;span class="n"&gt;SysTest&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:VERIFIES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;m.id&lt;/span&gt;            &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
       &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
       &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;m.id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is GraphRAG, just pointed at a codebase's specification instead of a document corpus: the graph is the retrieval layer, and what it returns is structured, current, and small.&lt;/p&gt;

&lt;p&gt;I measured the effect on the open-source &lt;a href="https://github.com/formbricks/formbricks" rel="noopener noreferrer"&gt;Formbricks&lt;/a&gt; repo. Closing a real defect took roughly &lt;strong&gt;70% fewer orientation tokens&lt;/strong&gt; when the agent queried the graph versus reading the files it would otherwise have opened. (Method and figures are written up at &lt;a href="https://www.org-edge.com/sysgraph.html" rel="noopener noreferrer"&gt;https://www.org-edge.com/sysgraph.html&lt;/a&gt; — and because the repo is public, the comparison is reproducible.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordination falls out of shared state
&lt;/h2&gt;

&lt;p&gt;The multi-agent win is almost a side effect. Because the graph is shared, mutable state, marking a work item in progress is a write that every other agent sees on its next query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;e:&lt;/span&gt;&lt;span class="n"&gt;SysEnhancement&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt;&lt;span class="n"&gt;$id&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;e.status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'in-progress'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e.startedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$now&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next agent sees that item flagged &lt;code&gt;in-progress&lt;/code&gt; in its worklog and routes to something else, so two sessions don't build the same thing. No human reconciling a dozen context files. The graph is the source of truth, and "what's left to do?" is a query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes from running it
&lt;/h2&gt;

&lt;p&gt;A few things that surprised me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coverage as &lt;code&gt;VERIFIES&lt;/code&gt; edges per tier, not a single percentage,&lt;/strong&gt; meant gaps couldn't hide. A feature with integration tests but no component tests shows the hole rather than reporting "covered" — agents reported catching gaps they'd otherwise have rationalised away ("integration tests exist, so it feels covered").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MERGE-only writes&lt;/strong&gt; for the agents, with destructive operations kept entirely out of their reach, was non-negotiable once multiple sessions shared one graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The seed is the cost.&lt;/strong&gt; Mapping an existing codebase into the initial node set is the one real setup step; everything compounds after that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The CLI that drives this is free and source-available (Neo4j Community under the hood): &lt;a href="https://github.com/org-edge/sysedge" rel="noopener noreferrer"&gt;https://github.com/org-edge/sysedge&lt;/a&gt;. If you're doing anything with LLM agents on a real codebase, I'd genuinely like to hear how others are modelling this — the ontology here is opinionated and I'm sure it can be sharpened.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built on Neo4j + a thin Python CLI. Works across Go, TypeScript, Python, Java, and C#.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>neo4j</category>
      <category>claudecode</category>
    </item>
  </channel>
</rss>
