<?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: Gert </title>
    <description>The latest articles on DEV Community by Gert  (@gert_).</description>
    <link>https://dev.to/gert_</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4006936%2F97372527-be4b-42a1-9c19-8ffdd4d961f2.png</url>
      <title>DEV Community: Gert </title>
      <link>https://dev.to/gert_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gert_"/>
    <language>en</language>
    <item>
      <title>Building Enola, Part 2: From Source Code to an Architectural Fact Model</title>
      <dc:creator>Gert </dc:creator>
      <pubDate>Tue, 04 Aug 2026 19:09:12 +0000</pubDate>
      <link>https://dev.to/gert_/building-enola-part-2-from-source-code-to-an-architectural-fact-model-djm</link>
      <guid>https://dev.to/gert_/building-enola-part-2-from-source-code-to-an-architectural-fact-model-djm</guid>
      <description>&lt;p&gt;In Part 1, I explained why Enola extracts deterministic architectural facts before an AI agent begins reasoning.&lt;/p&gt;

&lt;p&gt;That leaves the next design question:&lt;/p&gt;

&lt;p&gt;How should those facts be represented?&lt;/p&gt;

&lt;p&gt;The obvious answer is to build a graph containing every symbol, dependency, route, service, and repository.&lt;/p&gt;

&lt;p&gt;But the difficult part is not putting nodes and edges into a graph.&lt;/p&gt;

&lt;p&gt;It is preserving what those relationships mean.&lt;/p&gt;

&lt;p&gt;A function call is not the same as a package dependency. A route registration is not an import. A type reference is not proof that two services share a contract.&lt;/p&gt;

&lt;p&gt;If every relationship becomes a generic connection, the resulting graph may be traversable, but it is no longer reliable enough for architectural analysis.&lt;/p&gt;

&lt;p&gt;Enola therefore begins with a typed architectural fact model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing gives us evidence, not architecture
&lt;/h2&gt;

&lt;p&gt;A parser can tell us that a function call, string literal, import, annotation, or method declaration exists.&lt;/p&gt;

&lt;p&gt;That is necessary, but it is not yet an architectural fact.&lt;/p&gt;

&lt;p&gt;Consider a Go application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Subrouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;registerCourseRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elsewhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;registerCourseRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/courses"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;listCourses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser can expose both string literals and both function calls.&lt;/p&gt;

&lt;p&gt;The architectural fact is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /api/courses
  -&amp;gt; handled_by listCourses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Producing that fact requires understanding the framework, how routers compose paths, and how values move through function calls.&lt;/p&gt;

&lt;p&gt;The same problem appears in Spring annotations, Rails scopes, Axum routers, Next.js file conventions, generated clients, dependency-injection frameworks, and message-bus configuration.&lt;/p&gt;

&lt;p&gt;Enola therefore separates parsing from architectural extraction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source code
    ↓
Language and framework interpretation
    ↓
Architectural facts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser provides syntax.&lt;/p&gt;

&lt;p&gt;The extractor determines what the syntax means within the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository boundaries are extraction scopes
&lt;/h2&gt;

&lt;p&gt;A Git repository is a convenient place to begin analysis.&lt;/p&gt;

&lt;p&gt;It provides a source revision, configuration boundary, files, build metadata, and stable source locations.&lt;/p&gt;

&lt;p&gt;But it is not necessarily a unit of architectural truth.&lt;/p&gt;

&lt;p&gt;A monorepo may contain several independently deployed services. A small repository may depend on schemas, infrastructure, or generated clients maintained elsewhere. Runtime behavior may also depend on deployment configuration outside the application repository.&lt;/p&gt;

&lt;p&gt;Enola therefore treats a repository as an independently addressable &lt;strong&gt;extraction scope&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Within that scope, Enola may establish facts such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repository contains module
Module contains file
File declares symbol
Function calls function
Package imports package
Type implements interface
Route is handled by symbol
Producer publishes to topic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These facts remain useful even when no other repositories are loaded.&lt;/p&gt;

&lt;p&gt;The scope gives each entity a local identity and provenance. It does not imply that the entire architecture is contained inside the repository.&lt;/p&gt;

&lt;p&gt;That distinction matters because source ownership boundaries and architectural boundaries are rarely identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Different facts require different identity rules
&lt;/h2&gt;

&lt;p&gt;There is no universal identifier that works for every architectural concept.&lt;/p&gt;

&lt;p&gt;A symbol may require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;extraction scope;&lt;/li&gt;
&lt;li&gt;language;&lt;/li&gt;
&lt;li&gt;qualified name;&lt;/li&gt;
&lt;li&gt;source location;&lt;/li&gt;
&lt;li&gt;source revision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An HTTP route may require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;method;&lt;/li&gt;
&lt;li&gt;normalized path;&lt;/li&gt;
&lt;li&gt;service context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A gRPC method may require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;package;&lt;/li&gt;
&lt;li&gt;service;&lt;/li&gt;
&lt;li&gt;method.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Kafka topic may require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resolved topic name;&lt;/li&gt;
&lt;li&gt;namespace or environment context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why names alone are insufficient.&lt;/p&gt;

&lt;p&gt;Two repositories may both contain &lt;code&gt;UserDTO&lt;/code&gt; without referring to the same contract. Conversely, a Go type called &lt;code&gt;PublicUser&lt;/code&gt; and a Swift type called &lt;code&gt;ProfileResponse&lt;/code&gt; may represent two sides of the same API.&lt;/p&gt;

&lt;p&gt;Enola keeps those entities separate until there is evidence establishing a relationship between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed relationships preserve the reason two things are connected
&lt;/h2&gt;

&lt;p&gt;A generic graph might represent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A -&amp;gt; B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But architectural analysis needs to know why that edge exists.&lt;/p&gt;

&lt;p&gt;Compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CheckoutController
  -&amp;gt; calls PaymentService.authorize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;checkout
  -&amp;gt; imports payments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /checkout
  -&amp;gt; handled_by CheckoutController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These relationships support different questions.&lt;/p&gt;

&lt;p&gt;A call edge may be useful for reachability.&lt;/p&gt;

&lt;p&gt;An import edge may be useful for dependency-cycle detection.&lt;/p&gt;

&lt;p&gt;A route-to-handler edge may be useful for tracing request execution.&lt;/p&gt;

&lt;p&gt;Direction matters as well. A client consumes a route. The route does not consume the client.&lt;/p&gt;

&lt;p&gt;Enola therefore represents relationships as typed and directed facts rather than generic connectivity.&lt;/p&gt;

&lt;p&gt;That still does not make every extracted edge equally strong.&lt;/p&gt;

&lt;p&gt;A relationship should also retain the evidence used to produce it, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;source locations;&lt;/li&gt;
&lt;li&gt;extractor;&lt;/li&gt;
&lt;li&gt;resolution method;&lt;/li&gt;
&lt;li&gt;source revision;&lt;/li&gt;
&lt;li&gt;configuration;&lt;/li&gt;
&lt;li&gt;whether it was directly extracted or derived.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that evidence, the graph becomes another opaque answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  One model, several analysis projections
&lt;/h2&gt;

&lt;p&gt;The complete fact model contains more relationships than any single analysis should traverse.&lt;/p&gt;

&lt;p&gt;The question determines which subset is relevant.&lt;/p&gt;

&lt;p&gt;For package-cycle detection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nodes: packages
Edges: package dependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For symbol reachability:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nodes: symbols
Edges: calls and references
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For route execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nodes: routes, handlers, services
Edges: handled_by and calls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because using every available edge can produce technically connected but architecturally meaningless paths.&lt;/p&gt;

&lt;p&gt;Suppose a type belongs to a package, that package depends on another package, and the second package contains an HTTP route.&lt;/p&gt;

&lt;p&gt;There is a path through the full graph.&lt;/p&gt;

&lt;p&gt;That does not mean the type participates in the route’s execution.&lt;/p&gt;

&lt;p&gt;Enola therefore constructs constrained projections for specific analyses instead of treating every traversal as equivalent.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example: why a generic dependency graph fails
&lt;/h2&gt;

&lt;p&gt;Consider a frontend and backend stored in the same monorepo.&lt;/p&gt;

&lt;p&gt;The frontend imports an API client package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web
  -&amp;gt; depends_on api-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend imports a routing framework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend
  -&amp;gt; depends_on router
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A conventional dependency graph correctly records both relationships.&lt;/p&gt;

&lt;p&gt;But it cannot answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which frontend method consumes &lt;code&gt;POST /api/orders&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer requires several additional facts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;submitOrder
  -&amp;gt; makes_request POST /api/orders
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/orders
  -&amp;gt; handled_by CreateOrderHandler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CreateOrderHandler
  -&amp;gt; calls OrderService.Create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The useful architectural path is not a package-dependency path.&lt;/p&gt;

&lt;p&gt;It is a projection combining request, route, handler, and call relationships.&lt;/p&gt;

&lt;p&gt;That is the reason Enola needs a typed architectural fact model rather than only a repository dependency graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  The limit of the model
&lt;/h2&gt;

&lt;p&gt;Enola constructs the architecture that can be established from the loaded source and configuration inputs.&lt;/p&gt;

&lt;p&gt;It does not claim that source alone describes every aspect of a production system.&lt;/p&gt;

&lt;p&gt;Deployment manifests, gateways, service meshes, runtime configuration, reflection, feature flags, and infrastructure may alter the architecture visible at runtime.&lt;/p&gt;

&lt;p&gt;The model should therefore distinguish between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;established facts;&lt;/li&gt;
&lt;li&gt;derived relationships;&lt;/li&gt;
&lt;li&gt;unresolved relationships;&lt;/li&gt;
&lt;li&gt;information outside the analyzed scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is important for both developers and agents.&lt;/p&gt;

&lt;p&gt;A missing edge does not always mean that no relationship exists. It may mean that the relevant source, configuration, or resolver was unavailable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;A typed fact model can explain the architecture inside one extraction scope.&lt;/p&gt;

&lt;p&gt;Production systems cross those scopes.&lt;/p&gt;

&lt;p&gt;A mobile client calls a backend maintained elsewhere. A service publishes an event consumed by another repository. A generated client implements a contract defined in a separate codebase.&lt;/p&gt;

&lt;p&gt;The next post will explain how Enola connects these independently extracted models without merging entities based on names or similarity alone.&lt;/p&gt;

&lt;p&gt;Enola is open-source &lt;a href="https://github.com/enola-labs/enola" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devex</category>
      <category>mcp</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>Building Enola, Part 1: Why Deterministic Architecture Analysis Matters</title>
      <dc:creator>Gert </dc:creator>
      <pubDate>Mon, 20 Jul 2026 19:19:58 +0000</pubDate>
      <link>https://dev.to/gert_/building-enola-part-1-why-deterministic-architecture-analysis-matters-27e1</link>
      <guid>https://dev.to/gert_/building-enola-part-1-why-deterministic-architecture-analysis-matters-27e1</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the first post in a series about the engineering decisions behind &lt;strong&gt;Enola&lt;/strong&gt;, an open-source architecture analyzer for developers and AI coding agents.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open source:&lt;/strong&gt; &lt;a href="https://github.com/enola-labs/enola" rel="noopener noreferrer"&gt;https://github.com/enola-labs/enola&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We did not start Enola because we wanted to build infrastructure for AI coding agents.&lt;/p&gt;

&lt;p&gt;We started it while building a golf platform that had grown across club management, memberships, learning, web, mobile, and backend services. Over time, it became increasingly difficult to tell what was still used, what had been abandoned, and what could be removed without breaking something elsewhere.&lt;/p&gt;

&lt;p&gt;Tests helped, but they could not prove that an apparently unused API, type, or code path was not consumed by another repository, an older client, or a less obvious integration.&lt;/p&gt;

&lt;p&gt;AI coding agents made the problem worse in a different way.&lt;/p&gt;

&lt;p&gt;We already used architectural rules and fitness functions to keep generated code within the boundaries we had defined. But an agent could still duplicate an existing module, carry the pattern all the way up to a new API we never needed, and produce code that passed tests and respected every rule.&lt;/p&gt;

&lt;p&gt;The implementation looked valid. The architecture remained compliant. The result was still unnecessary code and future maintenance.&lt;/p&gt;

&lt;p&gt;What became difficult was not reviewing individual lines. It was knowing whether the agent had done exactly what we asked, whether it had quietly done more, and what that change had actually added to the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASTs, language servers, and RAG provide the pieces
&lt;/h2&gt;

&lt;p&gt;None of the foundations behind Enola were new.&lt;/p&gt;

&lt;p&gt;Compilers already had ASTs. Language servers already exposed symbols, definitions, references, and type information. Static analysis tools already built call graphs and dependency graphs.&lt;/p&gt;

&lt;p&gt;The industry did not lack parsers.&lt;/p&gt;

&lt;p&gt;Once AI coding agents became popular, another pattern appeared everywhere: index files or symbols, add embeddings or search, retrieve relevant fragments, and let the agent work out the rest.&lt;/p&gt;

&lt;p&gt;That is useful, but it is not the same as representing the architecture of a system.&lt;/p&gt;

&lt;p&gt;An AST describes the syntax of a language. A language server understands code within the boundaries of the language and workspace it supports. A RAG system retrieves fragments that might be relevant to the question.&lt;/p&gt;

&lt;p&gt;They provide the pieces. The agent still has to connect them.&lt;/p&gt;

&lt;p&gt;Ask what will be affected by changing an API in a backend repository, and retrieval may return the route, handler, request type, client calls, documentation mentioning the endpoint, and similarly named types from other repositories.&lt;/p&gt;

&lt;p&gt;The agent must determine whether a Swift model and a Go model represent the same contract, whether a TypeScript route is a proxy or an unrelated path, which direction the relationships go, and which results are noise.&lt;/p&gt;

&lt;p&gt;That is no longer retrieval. It is architectural reconstruction.&lt;/p&gt;

&lt;p&gt;Because the agent performs that reconstruction, the answer depends heavily on the agent itself. A stronger model may follow the chain correctly. A smaller one may stop early. One agent may inspect another repository; another may decide it already has enough context.&lt;/p&gt;

&lt;p&gt;The same codebase and the same question can therefore produce different architectural conclusions.&lt;/p&gt;

&lt;p&gt;For questions involving engineering judgment, that variation is expected.&lt;/p&gt;

&lt;p&gt;For questions such as &lt;em&gt;"what calls this?"&lt;/em&gt;, &lt;em&gt;"which repositories depend on this contract?"&lt;/em&gt;, or &lt;em&gt;"what became reachable after this change?"&lt;/em&gt;, it should not be the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  A codebase is a network of relationships
&lt;/h2&gt;

&lt;p&gt;Source code is stored as files and directories, but architecture does not follow that shape.&lt;/p&gt;

&lt;p&gt;A file can contain several unrelated symbols. A package can depend on another package without that relationship being obvious from the directory tree. Two repositories can be connected through an API, event, schema, generated client, route, or data model even though there is no direct import between them.&lt;/p&gt;

&lt;p&gt;The useful representation is therefore not another collection of indexed files. It is a graph of explicit relationships.&lt;/p&gt;

&lt;p&gt;Functions call other functions. Types reference other types. Packages depend on other packages. Interfaces are implemented by concrete types. Routes connect handlers to application logic. Clients in one repository consume contracts exposed by another.&lt;/p&gt;

&lt;p&gt;The important part is not merely extracting those relationships. Individual tools already do that.&lt;/p&gt;

&lt;p&gt;The difficult part is combining them into a consistent architectural model across languages, frameworks, and repositories.&lt;/p&gt;

&lt;p&gt;A route in Go does not look like a route in TypeScript. A controller in Java is not represented like an endpoint in Swift. The same name may refer to different concepts in different repositories, while two differently named types may represent the same contract.&lt;/p&gt;

&lt;p&gt;Placing every AST node into a graph does not solve that problem. Neither does embedding every symbol and asking an agent to infer what the graph probably means.&lt;/p&gt;

&lt;p&gt;The relationships need to be discovered, typed, directed, normalised, and connected in a way that remains useful beyond the parser that produced them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deterministic where possible
&lt;/h2&gt;

&lt;p&gt;Not every fact in software can be established with complete certainty.&lt;/p&gt;

&lt;p&gt;Dynamic dispatch, reflection, configuration, generated code, runtime dependency injection, and external systems all introduce ambiguity. Any tool claiming to understand every possible relationship perfectly would be overstating what static analysis can do.&lt;/p&gt;

&lt;p&gt;But a large part of a system’s structure can still be established deterministically, or represented together with the evidence used to infer it.&lt;/p&gt;

&lt;p&gt;A symbol is defined in a particular place. An import exists. A function contains a call. A route is registered. A type implements an interface. A client references a path. A package depends on another package.&lt;/p&gt;

&lt;p&gt;Enola starts from those facts, then uses language and framework specific analysis to connect them into a broader model.&lt;/p&gt;

&lt;p&gt;The purpose is not to remove reasoning from software engineering. It is to stop spending probabilistic reasoning on facts that can be extracted more reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Facts first, reasoning second
&lt;/h2&gt;

&lt;p&gt;There are questions where we want the agent’s judgment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should this interface change?&lt;/li&gt;
&lt;li&gt;Is this design maintainable?&lt;/li&gt;
&lt;li&gt;What is the safest migration path?&lt;/li&gt;
&lt;li&gt;Should this dependency be removed?&lt;/li&gt;
&lt;li&gt;Is the architectural impact acceptable?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And there are questions that should begin with evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which symbols exist?&lt;/li&gt;
&lt;li&gt;What references them?&lt;/li&gt;
&lt;li&gt;Which functions call them?&lt;/li&gt;
&lt;li&gt;Which packages depend on each other?&lt;/li&gt;
&lt;li&gt;Which relationships cross repository boundaries?&lt;/li&gt;
&lt;li&gt;What was added or removed by this change?&lt;/li&gt;
&lt;li&gt;What is reachable from this entry point?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An agent can reason much better about the first category when it does not have to reconstruct the second category every time.&lt;/p&gt;

&lt;p&gt;This is also where the value goes beyond saving tokens.&lt;/p&gt;

&lt;p&gt;A smaller model can work with the same architectural facts as a larger one. Different agents can begin from the same representation of the system. A result does not depend entirely on whether one particular agent happened to search the correct files in the correct order.&lt;/p&gt;

&lt;p&gt;The reasoning may still differ.&lt;/p&gt;

&lt;p&gt;The underlying architecture should not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architectural model becomes a DSL
&lt;/h2&gt;

&lt;p&gt;There is another useful way to look at Enola’s output.&lt;/p&gt;

&lt;p&gt;The files Enola generates under &lt;code&gt;.enola&lt;/code&gt; are not ordinary documentation, source-code summaries, or chunks prepared for retrieval. Together, they form a domain-specific language for describing the architecture of a software system.&lt;/p&gt;

&lt;p&gt;Their vocabulary is intentionally narrower than the vocabulary of the source code itself. They describe architectural concepts such as repositories, modules, symbols, relationships, routes, calls, dependencies, and cross-repository connections without requiring every consumer to parse every language and framework again.&lt;/p&gt;

&lt;p&gt;This closely matches the argument Unmesh Joshi makes in &lt;em&gt;DSLs Enable Reliable Use of LLMs&lt;/em&gt;. The article describes how a constrained semantic model and domain-specific vocabulary can reduce the number of possible interpretations available to an LLM, while deterministic tooling around the DSL can provide a harness for validating what the model produces. It also argues that, once a suitable abstraction exists, an LLM can act as a natural-language interface to it rather than inventing the domain model from scratch.&lt;/p&gt;

&lt;p&gt;Enola applies a related idea to software architecture, although the direction is different from a DSL primarily intended for code generation.&lt;/p&gt;

&lt;p&gt;It starts with general-purpose source code written across different languages and frameworks, analyzes it, and produces a constrained architectural representation. Developers and agents can then query that representation without reconstructing the underlying architecture from raw source files each time.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.enola&lt;/code&gt; files become a shared vocabulary between the codebase, Enola, developers, and coding agents.&lt;/p&gt;

&lt;p&gt;Instead of asking the agent to determine what a Go route, a Swift client, a TypeScript type, and a Java handler might collectively mean, Enola maps them into architectural concepts and relationships first. The agent reasons over a smaller and more explicit domain.&lt;/p&gt;

&lt;p&gt;This does not make the model infallible, and it does not turn every architectural question into a deterministic one. But it changes where uncertainty begins.&lt;/p&gt;

&lt;p&gt;The extraction and representation of the architecture happen before the agent starts reasoning. Different agents can therefore consume the same architectural DSL, even when their ability to independently search and understand a large multi-repository codebase differs significantly.&lt;/p&gt;

&lt;p&gt;The enduring artifact is not the prompt that asked an agent to investigate the system. It is the architectural model extracted from the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  From repositories to an architectural graph
&lt;/h2&gt;

&lt;p&gt;This deterministic architectural model, and the DSL used to expose it, became the foundation of Enola.&lt;/p&gt;

&lt;p&gt;Enola analyzes source code, extracts symbols and relationships within repositories, normalises them into a common representation, and connects repositories where the code provides evidence of a relationship.&lt;/p&gt;

&lt;p&gt;The result is not just an index of files, a symbol database, or a collection of disconnected repository graphs.&lt;/p&gt;

&lt;p&gt;It is an architectural graph that can represent relationships both inside a repository and across repository boundaries.&lt;/p&gt;

&lt;p&gt;Depending on the language and framework, Enola can identify information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Symbol definitions and references&lt;/li&gt;
&lt;li&gt;Function and method calls&lt;/li&gt;
&lt;li&gt;Type and interface relationships&lt;/li&gt;
&lt;li&gt;Module and package dependencies&lt;/li&gt;
&lt;li&gt;Routes and handlers&lt;/li&gt;
&lt;li&gt;Architectural boundaries&lt;/li&gt;
&lt;li&gt;Cross-repository relationships&lt;/li&gt;
&lt;li&gt;Change-impact paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This model can be queried directly by developers or exposed to AI coding agents through MCP.&lt;/p&gt;

&lt;p&gt;Enola does not replace ASTs, language servers, search, or coding agents. It uses similar underlying sources of structural information but solves a different problem: turning language- and repository-specific facts into a system-level architectural model.&lt;/p&gt;

&lt;p&gt;It also does not make architectural decisions on behalf of the agent. It gives the agent a more reliable representation of the system on which to base those decisions.&lt;/p&gt;

&lt;p&gt;Enola is not primarily a visualization tool either. Its data can be used by agents or other tools to generate diagrams, reports, and visualizations, but its core responsibility is extracting, normalising, and connecting the underlying architectural facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more as systems grow
&lt;/h2&gt;

&lt;p&gt;For a small repository, repeated searching and local code navigation may be enough.&lt;/p&gt;

&lt;p&gt;As a system grows across languages, frameworks, services, and repositories, the cost of reconstructing its architecture rises quickly.&lt;/p&gt;

&lt;p&gt;The agent must understand that a route in Go, a controller in Java, an endpoint in TypeScript, and a client call in Swift may be parts of the same architectural path, even though they are represented differently in code.&lt;/p&gt;

&lt;p&gt;It also has to distinguish between things that share a name but not a meaning.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;UserDTO&lt;/code&gt; in one repository may not be identical to a &lt;code&gt;UserDTO&lt;/code&gt; in another. Two services may use different representations of the same business concept. A cross-repository connection may exist through an HTTP contract, event, generated client, or schema rather than a direct code reference.&lt;/p&gt;

&lt;p&gt;This is where architecture analysis becomes more than parsing syntax, running a language server, or putting embeddings on top of symbols.&lt;/p&gt;

&lt;p&gt;The difficult part is building a model that preserves the meaning and direction of relationships across the entire system.&lt;/p&gt;

&lt;p&gt;That is the problem Enola is being built to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;This article explains why deterministic architecture analysis is the starting point.&lt;/p&gt;

&lt;p&gt;The next post will look at how Enola represents software as a graph of graphs: why repository-level graphs are not enough, how cross-repository relationships are discovered, and why seemingly identical concepts can mean different things across languages and frameworks.&lt;/p&gt;

&lt;p&gt;We are building Enola in the open and will continue documenting the engineering decisions, trade-offs, and mistakes behind it as the project develops.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/enola-labs" rel="noopener noreferrer"&gt;
        enola-labs
      &lt;/a&gt; / &lt;a href="https://github.com/enola-labs/enola" rel="noopener noreferrer"&gt;
        enola
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Architecture intelligence for developers, coding agents, and CI - understand codebases, analyze change impact, and catch architectural regressions.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;enola — architectural regression testing for AI-assisted development&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://mcptoplist.com/server/glama%2Fenola-labs%2Fenola" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ea073a5e13236319330f7c4664fb96a6661321b40a0f0c97dc9e3172d9f2e2e0/68747470733a2f2f6d6370746f706c6973742e636f6d2f62616467652f676c616d61253246656e6f6c612d6c616273253246656e6f6c612e737667" alt="MCP Toplist"&gt;&lt;/a&gt;
&lt;a href="https://github.com/enola-labs/enola/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/enola-labs/enola/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://github.com/enola-labs/enola/releases" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3c3edb7bf058c1a7c989d820e35673b84f2d37a2788e9aaa69bb1f10ff7aef42/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f656e6f6c612d6c6162732f656e6f6c61" alt="Release"&gt;&lt;/a&gt;
&lt;a href="https://github.com/enola-labs/enola/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/efe508908ab9518f7ff9d49e22281502e55dc2c529112aac41a8713f8c2ad7e5/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f656e6f6c612d6c6162732f656e6f6c61" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The quality gate every agentic loop is missing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;enola gives your agent the real architecture before it writes a line, then grades what it built and returns that verdict, so it fixes its own regression before calling the job done. Every finding comes from a real parser and a graph algorithm, never a model's guess.&lt;/p&gt;
&lt;p&gt;AI agents can write more code than you can carefully review. Tests check that behaviour still works. Linters check that style rules are followed. Neither checks whether the &lt;em&gt;structure&lt;/em&gt; of the code still makes sense — whether the change coupled two modules that had no business knowing about each other, or closed a dependency loop (a circular dependency). That usually surfaces in review, if someone catches it, or months later when the package is too tangled to refactor.&lt;/p&gt;
&lt;p&gt;enola checks structure while the change is still easy…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/enola-labs/enola" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>architecture</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
