<?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: Daniel Westgaard</title>
    <description>The latest articles on DEV Community by Daniel Westgaard (@danielwe).</description>
    <link>https://dev.to/danielwe</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%2F3876610%2F1d0996da-2e1c-4cac-979f-f2a9d33d8b15.jpg</url>
      <title>DEV Community: Daniel Westgaard</title>
      <link>https://dev.to/danielwe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielwe"/>
    <language>en</language>
    <item>
      <title>Symbol graphs and artifact graphs: why Sourcegraph stops where infrastructure starts</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Fri, 22 May 2026 22:00:00 +0000</pubDate>
      <link>https://dev.to/danielwe/symbol-graphs-and-artifact-graphs-why-sourcegraph-stops-where-infrastructure-starts-m00</link>
      <guid>https://dev.to/danielwe/symbol-graphs-and-artifact-graphs-why-sourcegraph-stops-where-infrastructure-starts-m00</guid>
      <description>&lt;p&gt;&lt;em&gt;A platform engineer asks two questions on the same morning. First: "I'm renaming this Go method — who calls it?" Second: "I'm bumping our shared Terraform networking module to v3.3.0 — which application repos will run &lt;code&gt;terraform plan&lt;/code&gt; against the new version?" The first is a symbol-graph question and Sourcegraph answers it at the top of the category. The second looks similar from a distance, has the same surface verb — "who depends on this" — and is, mechanically, a completely different graph. This post is about why.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A note on what this post is, and isn't
&lt;/h2&gt;

&lt;p&gt;This is not a competitive teardown. Sourcegraph indexed 54 billion lines of code, &lt;a href="https://github.com/sourcegraph/scip" rel="noopener noreferrer"&gt;created SCIP&lt;/a&gt; as an open language-agnostic protocol for code intelligence, and shipped &lt;a href="https://sourcegraph.com/blog/cross-repository-code-navigation" rel="noopener noreferrer"&gt;cross-repository navigation at enterprise scale&lt;/a&gt; — at the time of writing they host SCIP indexes for over 45,000 public repos and serve customers including Reddit, Stripe, Canva, MongoDB, and Dropbox through their MCP server. Those are real engineering achievements and the product solves real problems well.&lt;/p&gt;

&lt;p&gt;The argument here is narrower: code-symbol graphs and infrastructure-artifact graphs are orthogonal categories, and the second one is structurally outside what Sourcegraph's index produces. This isn't a flaw in Sourcegraph. It follows from the design choices that make symbol graphs work in the first place.&lt;/p&gt;

&lt;p&gt;If you're a platform engineer evaluating "context infrastructure" — code search, IDPs, cross-repo tooling for humans and agents — the practical takeaway is that most teams need both kinds of graph, and the tools should look as different as the questions do.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two questions that look similar from a distance
&lt;/h2&gt;

&lt;p&gt;Sit down with a platform team and they will keep coming back to two questions. They sound similar enough that vendor categories blur them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question one:&lt;/strong&gt; &lt;em&gt;I'm changing this function. Who calls it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is a code-symbol question. The nodes are functions, methods, classes, types, modules. The edges are references — call sites, imports, type usages, inheritance. The engine that builds this graph is a language indexer: it parses TypeScript or Go or Java with a compiler-aware tool and emits a structured record of where each symbol is defined and where it's referenced. Sourcegraph is built directly on this category and is the standard against which everything in it is measured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question two:&lt;/strong&gt; &lt;em&gt;I'm bumping this shared Terraform module to v3.3.0. Which application repos will re-plan? Which of them are pinned to v3.2.0, which to &lt;code&gt;~&amp;gt; 3.2&lt;/code&gt;, and which to &lt;code&gt;&amp;gt;= 3.0&lt;/code&gt;? Of those that float, which umbrella modules pull mine transitively?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is an infrastructure-artifact question. The nodes are Terraform modules, Docker base images, Helm charts, Kustomize bases, reusable GitHub Actions workflows, Kubernetes manifests. The edges are &lt;em&gt;artifact references inside infrastructure-as-code source files&lt;/em&gt;: &lt;code&gt;source = "git::https://...?ref=v3.2.0"&lt;/code&gt;, &lt;code&gt;FROM company/base:${BASE_TAG}&lt;/code&gt;, &lt;code&gt;dependencies[].repository: oci://...&lt;/code&gt;, &lt;code&gt;uses: company/actions/deploy@v2&lt;/code&gt;, &lt;code&gt;bases: - ../base-app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The two graphs have the same surface verb — "who depends on this" — and almost nothing else in common.&lt;/p&gt;

&lt;h2&gt;
  
  
  What SCIP indexes — and what it doesn't
&lt;/h2&gt;

&lt;p&gt;The clearest way to see why is to look at what SCIP, the protocol underneath Sourcegraph's cross-repo navigation, is actually designed to capture.&lt;/p&gt;

&lt;p&gt;Sourcegraph's &lt;a href="https://sourcegraph.com/blog/cross-repository-code-navigation" rel="noopener noreferrer"&gt;own description&lt;/a&gt; of what SCIP indexes covers two categories of structured data: &lt;em&gt;symbols&lt;/em&gt; — carrying definition location, symbol metadata (function vs. class vs. variable vs. etc.), and package ownership including which repository and version defines the symbol — and &lt;em&gt;external symbols&lt;/em&gt; — tracking cross-repository dependencies, the symbols defined in other packages, and version data for each dependency.&lt;/p&gt;

&lt;p&gt;The keyword is &lt;em&gt;symbols&lt;/em&gt;. SCIP indexes the things a compiler or language server understands as named declarations: functions, types, methods, namespaces. To produce this data you need a parser that understands the &lt;em&gt;programming language&lt;/em&gt;'s scoping and name-resolution rules. The same Sourcegraph post lists the official language coverage for auto-indexing as TypeScript, JavaScript, Python, Go, Java, Scala, Kotlin, and C/C++. The broader &lt;a href="https://github.com/sourcegraph/scip" rel="noopener noreferrer"&gt;SCIP protocol&lt;/a&gt; has additional community indexers — for Ruby, .NET, Dart, PHP, Rust — but the marketed coverage of Sourcegraph's product is the eight-language list above.&lt;/p&gt;

&lt;p&gt;All eight are general-purpose programming languages. The indexer for each is built on the relevant language's compiler or language server. Notice what isn't there, and isn't on the roadmap: HCL, Dockerfile, Helm &lt;code&gt;Chart.yaml&lt;/code&gt;, Kubernetes manifest schemas, GitHub Actions workflow YAML, GitLab CI YAML, Kustomize, Ansible playbooks. Not because Sourcegraph forgot about infrastructure-as-code, but because IaC dependency relationships are not symbols. They're values inside strings that the IaC tool evaluates at deploy or build time. The grammar SCIP describes — definition location, symbol metadata, references — doesn't fit the shape of those relationships.&lt;/p&gt;

&lt;p&gt;This is by design, not by oversight. A language indexer that tried to also produce IaC artifact edges would be solving a different problem with the wrong abstraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Sourcegraph itself recommends answering IaC dependency questions
&lt;/h2&gt;

&lt;p&gt;The strongest evidence for the category split is in Sourcegraph's own canonical content. Three of their flagship posts on impact analysis and blast radius — the questions that overlap most with the artifact-graph use case — fall back to regex search over manifest files at exactly the point where the answer turns infrastructural.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sourcegraph.com/blog/multi-repo-search-how-to-search-across-multiple-repositories" rel="noopener noreferrer"&gt;Multi-repo search: How to search across multiple repositories&lt;/a&gt; (March 2025) introduces blast radius and impact analysis as a multi-repo search use case, with the symbol-graph navigation as the precise tool: "It uses SCIP indexes to resolve actual symbol references, so you see real function calls and imports, not just string matches that happen to contain the function name." For the symbol case, that's the right answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sourcegraph.com/blog/why-code-search-at-scale-is-essential-when-you-grow-beyond-one-repository" rel="noopener noreferrer"&gt;Why code search at scale is essential when you grow beyond one repository&lt;/a&gt; (December 2025) walks through "impact analysis before changes" with a worked example. The example reaches for the artifact case — a Go service depending on a shared library — and the recommended query is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repo:myorg/.* file:go.mod content:"auth-lib"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's &lt;code&gt;content:&lt;/code&gt; regex over the contents of &lt;code&gt;go.mod&lt;/code&gt; files, scoped to repos matching &lt;code&gt;myorg/.*&lt;/code&gt;. It works. It will find every &lt;code&gt;go.mod&lt;/code&gt; in the org that mentions &lt;code&gt;auth-lib&lt;/code&gt;. It will not tell you which version each consumer is pinned to in a way the next tool in your pipeline can act on without further parsing, it won't follow &lt;code&gt;replace&lt;/code&gt; directives, and it won't resolve module path aliases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sourcegraph.com/blog/cross-repository-code-navigation" rel="noopener noreferrer"&gt;Cross-Repository Code Navigation&lt;/a&gt; (January 2026) reaches the same pattern for npm. The post's worked example for "Finding All Usages of a Dependency" is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;context:global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file:package.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"your-internal-lib"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="err"&gt;\s*&lt;/span&gt;&lt;span class="s2"&gt;"[~^]?1&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;3"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;patterntype:regexp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again — regex over &lt;code&gt;package.json&lt;/code&gt; strings. Again it works for finding the &lt;em&gt;files&lt;/em&gt;. It doesn't return a structured consumer list with resolved version constraints across &lt;code&gt;~&lt;/code&gt;, &lt;code&gt;^&lt;/code&gt;, &lt;code&gt;&amp;gt;=&lt;/code&gt;, and exact-pin forms; the regex enumerates each one explicitly.&lt;/p&gt;

&lt;p&gt;This is not a critique of those posts. They're honest and they reach for the right tool. &lt;em&gt;Symbol search isn't the tool for this question — for this question, text matching over manifest files is.&lt;/em&gt; But that itself is the observation worth sitting with: when Sourcegraph's own writers reach for dependency-impact use cases, they pivot from SCIP navigation to regex over manifest files. The reason is structural. SCIP doesn't index manifest files because manifest files are not source code in the sense SCIP was designed for. They're declarations a build tool will later resolve against an external system — a module registry, a container registry, a git remote, a chart repository, a Kubernetes API server.&lt;/p&gt;

&lt;p&gt;The regex falls back because the symbol graph has no opinion about manifest declarations. It can't. That's not what it indexes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The homepage demo, read carefully
&lt;/h2&gt;

&lt;p&gt;Sourcegraph's current homepage runs a side-by-side comparison that illustrates the category split better than any external critique could.&lt;/p&gt;

&lt;p&gt;The scenario: a developer asks an AI coding agent to add a &lt;code&gt;Role&lt;/code&gt; field to a &lt;code&gt;User&lt;/code&gt; struct in &lt;code&gt;models/user.go&lt;/code&gt;. The baseline agent edits &lt;code&gt;models/user.go&lt;/code&gt; and &lt;code&gt;database/user_store.go&lt;/code&gt;, declares itself done, and offers a parting suggestion to add a migration. The post-it-note list of "what the agent missed" reads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auth middleware — no role check, any user can access admin routes&lt;/li&gt;
&lt;li&gt;API response DTO — role never returned to clients&lt;/li&gt;
&lt;li&gt;Audit logging — role changes not tracked, no compliance trail&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/admin&lt;/code&gt; frontend routes — no guard, UI still accessible to all&lt;/li&gt;
&lt;li&gt;Invite flow — new users created without a default role&lt;/li&gt;
&lt;li&gt;4 integration tests — assert on user shape, will break&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the same agent runs with Sourcegraph MCP. The agent calls &lt;code&gt;sg_keyword_search "User struct" across 2,847 repositories&lt;/code&gt;, gets 31 files across 7 layers, and produces a complete change.&lt;/p&gt;

&lt;p&gt;This is a strong demo and the win is real. But notice the exact shape of the problem and the exact shape of the offered tool. Every item on the "missed" list is a &lt;em&gt;cross-cutting code-level concern&lt;/em&gt;: a middleware function that checks the user, a DTO type that serializes the user, an audit hook that writes when a user changes, a route guard that consults the user, an invite handler that constructs a user, integration tests that assert on the user shape. Every one of those is a symbol relationship. They are exactly what SCIP indexes and exactly what &lt;code&gt;find_references&lt;/code&gt; and &lt;code&gt;keyword_search&lt;/code&gt; are designed to surface.&lt;/p&gt;

&lt;p&gt;Now imagine an adjacent scenario: a platform engineer is bumping the shared &lt;code&gt;terraform-modules/networking&lt;/code&gt; module from &lt;code&gt;v3.2.0&lt;/code&gt; to &lt;code&gt;v3.3.0&lt;/code&gt; because the new version renames a variable. What does an equivalent demo look like?&lt;/p&gt;

&lt;p&gt;The missed items aren't &lt;code&gt;middleware/auth.go&lt;/code&gt;, &lt;code&gt;api/dto/user_response.go&lt;/code&gt;, &lt;code&gt;routes/admin/guard.ts&lt;/code&gt;. They're: the eight application repos that pin &lt;code&gt;?ref=v3.2.0&lt;/code&gt; directly in a Terragrunt root, the four repos that pin &lt;code&gt;~&amp;gt; 3.2&lt;/code&gt; and will float to the new version on next plan, the two umbrella modules in &lt;code&gt;infra-platform-modules&lt;/code&gt; that re-export &lt;code&gt;networking&lt;/code&gt; and are themselves consumed by another twelve repos, the one repo where the module is pulled through an intermediate &lt;code&gt;terraform-aws-modules/internal-wrapper&lt;/code&gt; chain, and the three GitOps repos that reference the module path in an Atlantis &lt;code&gt;repos.yaml&lt;/code&gt;. None of those relationships is a symbol. None of them appears in a SCIP index. A search for &lt;code&gt;terraform-modules/networking&lt;/code&gt; returns a list of files containing that string and leaves the resolution work — git URL canonicalization, ref-pin parsing, semver constraint evaluation, transitive umbrella resolution, GitOps wiring — to whoever reads the results.&lt;/p&gt;

&lt;p&gt;The homepage demo is correct that an AI agent without cross-repository context misses cross-cutting changes. It's also correct that a symbol graph closes that gap for code-level concerns. It just doesn't close the same gap for IaC-level concerns, because those concerns aren't built out of symbols.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a symbol graph cannot resolve — four worked examples
&lt;/h2&gt;

&lt;p&gt;Concretely, here are four shapes of dependency that show up in every infrastructure-heavy org, and that no code-symbol indexer can resolve without becoming a different product.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Terraform module sources with git URLs and ref pins
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://gitlab.company.com/infra/terraform-modules.git//networking?ref=v3.2.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing here is a symbol. &lt;code&gt;source&lt;/code&gt; is an HCL attribute. The value is a single string with overloaded semantics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;git::&lt;/code&gt; is a Terraform protocol prefix&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https://gitlab.company.com/infra/terraform-modules.git&lt;/code&gt; is a canonical git URL — which Riftmap normalizes against the same git remote whether it appears as &lt;code&gt;https://&lt;/code&gt;, &lt;code&gt;git@gitlab...&lt;/code&gt;, or with &lt;code&gt;.git&lt;/code&gt; stripped&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;//networking&lt;/code&gt; is a subdirectory inside the module repo&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;?ref=v3.2.0&lt;/code&gt; is a git ref pin (could equally be a branch, a tag, or a commit SHA)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To answer "which repos consume the &lt;code&gt;networking&lt;/code&gt; module of &lt;code&gt;terraform-modules&lt;/code&gt; at v3.2.0" you need a parser that understands this string format and a resolver that walks the repo URL back to a canonical identity. A code indexer sees an HCL attribute assignment with a string literal.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Docker &lt;code&gt;FROM&lt;/code&gt; with build-arg substitution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; BASE_TAG=latest&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; company/base-runtime:${BASE_TAG}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual base image consumed in production depends on the &lt;code&gt;--build-arg&lt;/code&gt; passed at build time, which usually lives in a separate file — &lt;code&gt;.github/workflows/build.yml&lt;/code&gt;, a &lt;code&gt;docker-bake.hcl&lt;/code&gt;, a &lt;code&gt;docker-compose.yml&lt;/code&gt;, a Makefile target. Resolving the real consumer relationship requires reading the Dockerfile, finding the default, then reading the build invocation to see if it's overridden. A symbol graph doesn't model build-time evaluation of CI variables across YAML files.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Helm chart consumers across three reference formats
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Chart.yaml dependency&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~3.2.0"&lt;/span&gt;
    &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oci://registry.company.com/charts"&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;  &lt;span class="c1"&gt;# the chart is installed under this name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ArgoCD Application&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://charts.company.com&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.2.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Flux HelmRelease&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=3.0.0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;4.0.0"&lt;/span&gt;
      &lt;span class="na"&gt;sourceRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRepository&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;company-charts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three reference the same chart. The first uses OCI; the second uses HTTPS; the third uses a Flux &lt;code&gt;HelmRepository&lt;/code&gt; pointer that needs to be resolved separately to a registry. The first uses a semver constraint &lt;code&gt;~3.2.0&lt;/code&gt; and an alias that breaks name-matching in values overrides. The second is an exact pin. The third is an explicit semver range. A complete answer to "who consumes &lt;code&gt;platform-services&lt;/code&gt;" requires (a) normalizing all three forms to a canonical chart identity, (b) evaluating each version constraint against the chart's published versions, (c) following Flux source pointers, and (d) following umbrella charts that re-export &lt;code&gt;platform-services&lt;/code&gt; as a subchart. None of this is a symbol relationship. We covered the full Helm case in detail in &lt;a href="https://riftmap.dev/blog/how-to-find-every-consumer-of-your-helm-chart/" rel="noopener noreferrer"&gt;How to Find Every Consumer of Your Helm Chart&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Reusable GitHub Actions workflows
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;company/actions/.github/workflows/deploy.yml@v2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;uses:&lt;/code&gt; value is a single string encoding &lt;code&gt;&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;/&amp;lt;path&amp;gt;@&amp;lt;ref&amp;gt;&lt;/code&gt;. Resolving "who uses my deploy workflow" means parsing this format, normalizing the repo identifier, evaluating the ref pin, and following transitive includes when a reusable workflow itself calls other reusable workflows. None of it lives in a programming language's symbol table.&lt;/p&gt;

&lt;p&gt;The pattern across all four: the consumer relationship is encoded in a string inside an IaC declaration, and resolving it requires understanding the IaC tool's grammar and its evaluation semantics. SCIP indexes none of this, on purpose. A different protocol indexes it, and a different parser estate emits it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each tool actually returns
&lt;/h2&gt;

&lt;p&gt;Concretely: imagine you're the owner of &lt;code&gt;platform-services&lt;/code&gt; Helm chart at v3.2.0 and you're about to publish v3.3.0, which renames a top-level value key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Sourcegraph, the recommended pattern is regex over &lt;code&gt;Chart.yaml&lt;/code&gt;&lt;/strong&gt; (and ArgoCD &lt;code&gt;Application&lt;/code&gt; manifests, and Flux &lt;code&gt;HelmRelease&lt;/code&gt; CRDs, and any shell scripts that call &lt;code&gt;helm install&lt;/code&gt;, each a separate query):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;context:global file:Chart.yaml "platform-services" patterntype:regexp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you get back is a list of file matches: each result is a file path, a line number, a snippet showing &lt;code&gt;platform-services&lt;/code&gt; in context, and PageRank-style ranking. You then open each file to read the version constraint, work out which chart identity is being referenced (HTTPS vs OCI vs alias), and manually evaluate which consumers will float to v3.3.0 versus stay on v3.2.x. The work scales with the number of results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Riftmap, the call against the same artifact looks like this:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1/artifacts/{helm_chart_id}/consumers
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"artifact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"b3790d64-c693-47d5-83b0-3a2c3872faf9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"artifact_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"helm_chart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"helm-library-chart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"full_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"polaris-works/platform/helm-library-chart"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"consumer_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"is_orphan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consumers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data-helm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="nl"&gt;"full_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"polaris-works/data/data-helm"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version_constraint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chart.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source_line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"is_latest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"portal-helm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nl"&gt;"full_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"polaris-works/portal/portal-helm"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version_constraint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=1.0.0 &amp;lt;2.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chart.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source_line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"is_latest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"logistics-helm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nl"&gt;"full_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"polaris-works/logistics/logistics-helm"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version_constraint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chart.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source_line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"is_latest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payments-multi-artifact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"full_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"polaris-works/payments/payments-multi-artifact"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version_constraint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chart.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source_line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"is_latest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"repository"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payments-helm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="nl"&gt;"full_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"polaris-works/payments/payments-helm"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"version_constraint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~1.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Chart.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source_line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"is_latest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"import_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_consumers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consumers_on_latest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"consumers_lagging"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.2.0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five consumers, four pin shapes the resolver normalises (exact-current &lt;code&gt;1.2.0&lt;/code&gt;, exact-behind &lt;code&gt;1.1.0&lt;/code&gt;, tilde range &lt;code&gt;~1.2.0&lt;/code&gt;, explicit range &lt;code&gt;&amp;gt;=1.0.0 &amp;lt;2.0.0&lt;/code&gt;), and &lt;code&gt;consumers_lagging: 1&lt;/code&gt; already evaluated against the chart's published version — the consumer table is the structured answer to "who consumes my chart at v1.2.0" without leaving anything for the agent to parse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcaorwq982h6dv1rk8wur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcaorwq982h6dv1rk8wur.png" alt="Riftmap artifact-consumers view for  raw `platform/helm-library-chart` endraw : five umbrella consumers across the polaris-works testbed, one ( raw `data-helm` endraw ) flagged Behind for pinning  raw `1.1.0` endraw  while the chart sits at  raw `1.2.0` endraw . The two range-form constraints ( raw `~1.2.0` endraw ,  raw `&amp;gt;=1.0.0 &amp;lt;2.0.0` endraw ) render alongside two exact-current pins."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you get back is a structured consumer list. The same chart identity has been resolved across HTTPS, OCI, and Flux pointer forms before you see it. Each consumer carries its version constraint (&lt;code&gt;~3.2.0&lt;/code&gt;, &lt;code&gt;&amp;gt;=3.0.0 &amp;lt;4.0.0&lt;/code&gt;, exact &lt;code&gt;3.2.1&lt;/code&gt;) already evaluated against the published versions, so consumers within range of v3.3.0 are flagged. Umbrella charts that re-export &lt;code&gt;platform-services&lt;/code&gt; are followed transitively; their downstream consumers appear with their own constraints. The work doesn't scale with the result count — the structure has been resolved once, server-side, by the parser estate.&lt;/p&gt;

&lt;p&gt;Neither answer makes the other one wrong. They're answers to different questions, with different shapes, returned by different machinery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steelmanning the obvious objections
&lt;/h2&gt;

&lt;p&gt;A few obvious objections, taken at their strongest.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Couldn't Sourcegraph add IaC parsers?"
&lt;/h3&gt;

&lt;p&gt;Yes, in principle. SCIP is an open protocol. Someone could write &lt;code&gt;scip-hcl&lt;/code&gt; or &lt;code&gt;scip-helm&lt;/code&gt; and emit Terraform-shaped or Helm-shaped records into the same index format Sourcegraph already serves.&lt;/p&gt;

&lt;p&gt;The hard part isn't the parser. The hard part is the &lt;em&gt;evaluator&lt;/em&gt;. The output of &lt;code&gt;scip-hcl&lt;/code&gt; would have to be a different kind of fact — not "this symbol is defined here and referenced there" but "this &lt;code&gt;source = "git::..."&lt;/code&gt; string, when evaluated by Terraform's module installer against the current state of remote git repositories, resolves to repo X at commit Y, in the context of which an attribute called Z is defined, which is consumed by a &lt;code&gt;module&lt;/code&gt; block in repo W that depends on commit V of the same module." That's not a symbol-graph fact. It's an artifact-graph fact. You can serialize it in SCIP if you want, but the data model and the consumer code paths in Sourcegraph (go-to-definition, find-references, version-aware symbol lookup) are built for the symbol case. You'd have to build a parallel pipeline that resolves git URLs, parses semver constraints across &lt;code&gt;~&lt;/code&gt;/&lt;code&gt;^&lt;/code&gt;/&lt;code&gt;&amp;gt;=&lt;/code&gt;, follows umbrella charts, evaluates &lt;code&gt;ARG&lt;/code&gt; defaults, walks reusable workflow chains, and normalizes container image references across registries. At that point you've built a different product that happens to share an index format with the first one.&lt;/p&gt;

&lt;p&gt;The same logic applies to any code-symbol vendor — Cody, Greptile, OpenGrep, Augment, the rest of the category. The category isn't structurally suited to the artifact question. That's the whole point.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Can't an LLM with Cody's @-mention just figure this out?"
&lt;/h3&gt;

&lt;p&gt;This is the version of the objection that requires the most care, because Anthropic's lead on Claude Code has staked out a clear public position on the opposite side. In Boris Cherny's &lt;a href="https://newsletter.pragmaticengineer.com/p/building-claude-code-with-boris-cherny" rel="noopener noreferrer"&gt;Pragmatic Engineer interview&lt;/a&gt;, the team described trying local vector databases, recursive model-based indexing, and other RAG-shaped approaches for agentic search. The conclusion was that "plain glob and grep, driven by the model, beat everything," and Claude Code shipped with that as the architecture.&lt;/p&gt;

&lt;p&gt;Don't read this as a counter-argument to refute. Read it as structural validation of the category split. The Cherny bet is specifically that for &lt;em&gt;symbol-level&lt;/em&gt; questions inside a working session, an LLM driving primitive tools (glob, grep, read) outperforms a pre-built index whose maintenance cost includes staleness, permissions, and integration complexity. That's a coherent and defensible position for the symbol case, and a careful reader of the Pragmatic Engineer interview will notice why it works: when the model greps for a function name, the relevant truth — the call site, the import statement, the type signature — is &lt;em&gt;sitting in the source code the model can read&lt;/em&gt;. The model can iterate. It runs another grep, opens another file, and converges on a verified answer within the session. The primitive tool plus the model plus the source code is a complete loop.&lt;/p&gt;

&lt;p&gt;The artifact case breaks the loop because the truth isn't sitting in the source files in a form grep can converge on.&lt;/p&gt;

&lt;p&gt;Grep &lt;code&gt;terraform-modules/networking&lt;/code&gt; across an org and you get a list of files that mention the string. What grep cannot return — and what reading those files cannot recover without rebuilding the parser estate inside the conversation — is the &lt;em&gt;resolved&lt;/em&gt; answer: which of those references canonicalize to the same module across &lt;code&gt;git::https://&lt;/code&gt;, &lt;code&gt;git@gitlab:&lt;/code&gt;, and stripped-&lt;code&gt;.git&lt;/code&gt; URL forms; which version constraints (&lt;code&gt;~3.2.0&lt;/code&gt;, &lt;code&gt;^3.0.0&lt;/code&gt;, &lt;code&gt;&amp;gt;=3.0.0 &amp;lt;4&lt;/code&gt;, exact &lt;code&gt;3.2.1&lt;/code&gt;) include v3.3.0 after evaluation against the published version list; which Flux &lt;code&gt;HelmRepository&lt;/code&gt; source pointer in &lt;code&gt;gitops/sources/&lt;/code&gt; resolves to which registry; which umbrella module re-exports &lt;code&gt;networking&lt;/code&gt; and pulls in its own downstream consumers; which &lt;code&gt;ARG&lt;/code&gt;-substituted &lt;code&gt;FROM&lt;/code&gt; line in a Dockerfile actually resolves to which base image after CI evaluation. The resolver complexity doesn't surface in grep output. It has to be built once, somewhere, against the IaC tools' grammars and against external registries — or the model has to rebuild it on every query, against text matches, with no audit trail.&lt;/p&gt;

&lt;p&gt;That's why dependency questions want deterministic answers and why "92% confidence that these are your consumers" is unshippable. You can't merge a change to a shared Terraform module against an LLM probability distribution over its consumer set; you need the actual set, derived from actual source, with an audit trail you can hand to the consumer teams before you ship. The deterministic-graph approach has to survive even if Cherny's bet wins everywhere it's making its bet — because in the artifact case the model can't iterate to verification from primitive tools. The verification primitive is the parser estate itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Doesn't &lt;code&gt;terraform graph&lt;/code&gt; already solve this?"
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;terraform graph&lt;/code&gt; is excellent and frequently misunderstood. It produces the DAG of a &lt;em&gt;single&lt;/em&gt; configuration — the resource dependencies inside one root module, used by Terraform's planner to schedule create/update/destroy operations in the correct order. It is not cross-repo, it is not cross-ecosystem, and it has no opinion about Docker base images, Helm charts, or reusable workflows. The same applies to Atlantis stack graphs, Spacelift stack dependencies, and HashiCorp Cloud Platform's Module Explorer — each solves a slice of the Terraform-orchestration problem from within a specific runner. The artifact graph in this post sits one level up: source-derived, cross-repo, cross-ecosystem, runner-agnostic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for the agent layer
&lt;/h2&gt;

&lt;p&gt;If you've followed agent-context infrastructure in 2026 at all, you've watched every vendor in adjacent categories reposition around "the intelligence layer for AI coding agents and developers." Sourcegraph 7.0 (February 25, 2026) used that exact phrase to &lt;a href="https://sourcegraph.com/blog/a-new-era-for-sourcegraph-the-intelligence-layer-for-ai-coding-agents-and-developers" rel="noopener noreferrer"&gt;formalize the shift&lt;/a&gt;. Their MCP server lists Reddit, Stripe, MongoDB, Canva, Dropbox as customers using &lt;code&gt;keyword_search&lt;/code&gt;, &lt;code&gt;nls_search&lt;/code&gt;, &lt;code&gt;go_to_definition&lt;/code&gt;, &lt;code&gt;find_references&lt;/code&gt;, &lt;code&gt;commit_search&lt;/code&gt;, &lt;code&gt;diff_search&lt;/code&gt;, &lt;code&gt;deepsearch&lt;/code&gt;, and a small set of related primitives.&lt;/p&gt;

&lt;p&gt;That's the right shape of MCP server for symbol-level agent context. It's the shape Sourcegraph has been building toward for over a decade and the customer list reflects that fit.&lt;/p&gt;

&lt;p&gt;It's also not the &lt;em&gt;only&lt;/em&gt; MCP server a serious agent setup will compose by 2027.&lt;/p&gt;

&lt;p&gt;The architecture taking shape across the agent ecosystem is multi-context composition. An agent working on a non-trivial cross-cutting change pulls from several specialized context layers: symbol context (the code-graph layer Sourcegraph dominates), artifact context (the IaC dependency layer Riftmap exists to provide), ticket and project context (Linear, Jira, GitHub Issues), documentation and decision context (Notion, Confluence, ADR repos), observability and runtime context (Datadog, Honeycomb, Sentry, log aggregators). Each layer has its own grammar, its own retrieval primitives, its own freshness model, its own MCP server. The agent's orchestration layer composes them.&lt;/p&gt;

&lt;p&gt;In that picture, Riftmap and Sourcegraph aren't alternatives and they aren't complements in the usual "we play well together" sense either. They're &lt;em&gt;peers in a converging architecture&lt;/em&gt;. The fact that the IaC artifact layer now has dedicated infrastructure is not evidence that Sourcegraph is wrong about code-symbol context. It's evidence that the architecture is maturing — that the field has gotten serious enough about agent context to specialize the layers instead of asking one tool to serve every question.&lt;/p&gt;

&lt;p&gt;Both MCP servers can sit in the same agent's tool list. They answer questions Sourcegraph was built to answer well, and questions Sourcegraph was reasonably not built to answer at all. The composition is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Symbol graphs index how code calls itself. The nodes are functions and types; the edges are references; the indexer is a language-aware parser; the protocol is SCIP. Sourcegraph is the standard-bearer for this category and has been for over a decade. For "where is this function called, who depends on this API, what's the blast radius of a refactor" — symbol graph, Sourcegraph, end of discussion.&lt;/p&gt;

&lt;p&gt;Artifact graphs index how infrastructure consumes itself. The nodes are Terraform modules and Docker images and Helm charts and reusable workflows; the edges are artifact references inside IaC source; the parser estate is one-per-ecosystem and the resolution heuristics are IaC-tool-specific. For "who consumes my Helm chart at v3.2.0, which repos will re-plan on a Terraform module bump, what's the blast radius of changing a shared GitHub Actions workflow" — artifact graph, &lt;a href="https://riftmap.dev/" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt;, built for exactly this.&lt;/p&gt;

&lt;p&gt;The questions sound similar. The graphs are different shapes. Most platform teams have both questions, and the right answer is to use both kinds of tool — the same way a serious agent setup in 2026 composes multiple specialized context layers instead of asking any one tool to serve every question.&lt;/p&gt;

&lt;p&gt;That's the test for when this category has matured: when nobody is surprised that the IaC dependency graph lives behind its own MCP server.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the kind of question Riftmap is built to answer. It scans your GitHub or GitLab organisation with a read-only token, parses Terraform, Docker, Helm, Kustomize, Kubernetes, GitHub Actions, GitLab CI, Ansible, Go modules, and npm, and builds the cross-repo artifact graph as a queryable surface — for engineers in the UI, for agents over MCP. Five minutes to first graph. If you've read this far, &lt;a href="https://app.riftmap.dev" rel="noopener noreferrer"&gt;the free tier is here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you're interested in the underlying parsing work for any single ecosystem, the &lt;a href="https://riftmap.dev/blog/series/find-every-consumer/" rel="noopener noreferrer"&gt;Find Every Consumer series&lt;/a&gt; goes one ecosystem at a time: &lt;a href="https://riftmap.dev/blog/how-to-find-every-consumer-of-your-docker-base-image/" rel="noopener noreferrer"&gt;Docker base images&lt;/a&gt;, &lt;a href="https://riftmap.dev/blog/how-to-find-every-consumer-of-your-terraform-module/" rel="noopener noreferrer"&gt;Terraform modules&lt;/a&gt;, &lt;a href="https://riftmap.dev/blog/how-to-find-every-consumer-of-your-github-actions-workflow/" rel="noopener noreferrer"&gt;GitHub Actions workflows&lt;/a&gt;, &lt;a href="https://riftmap.dev/blog/how-to-find-every-consumer-of-your-helm-chart/" rel="noopener noreferrer"&gt;Helm charts&lt;/a&gt;, &lt;a href="https://riftmap.dev/blog/how-to-find-every-consumer-of-your-go-module/" rel="noopener noreferrer"&gt;Go modules&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sourcegraph</category>
      <category>scip</category>
      <category>codesymbolgraph</category>
      <category>artifactgraph</category>
    </item>
    <item>
      <title>Cross-repo context is in product docs. The graph is not</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Wed, 20 May 2026 13:18:28 +0000</pubDate>
      <link>https://dev.to/danielwe/cross-repo-context-is-in-product-docs-the-graph-is-not-326f</link>
      <guid>https://dev.to/danielwe/cross-repo-context-is-in-product-docs-the-graph-is-not-326f</guid>
      <description>&lt;p&gt;&lt;em&gt;Where the vocabulary moved, what the products shipped against it, and the layer that's still uncovered.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Six months ago, "cross-repo context" lived in dev.to posts and HN comments. Today it lives in vendor product documentation, in CPO interviews, in feature pages. It is in &lt;a href="https://docs.warp.dev/agent-platform/capabilities/codebase-context/" rel="noopener noreferrer"&gt;Warp's Codebase Context docs&lt;/a&gt;. It is on &lt;a href="https://www.augmentcode.com/product/context-engine-mcp" rel="noopener noreferrer"&gt;Augment Code's product pages&lt;/a&gt; as a feature called the Context Engine MCP. The phrase JetBrains coined in March to describe what AI agents produce without structural understanding, "shadow tech debt," was the framing of their Junie CLI launch. And in Microsoft's &lt;a href="https://news.microsoft.com/source/features/ai/whats-next-in-ai-7-trends-to-watch-in-2026/" rel="noopener noreferrer"&gt;December 2025 look-ahead to AI in 2026&lt;/a&gt;, GitHub's chief product officer Mario Rodriguez named the category by a different name, "repository intelligence," and framed it as the new competitive advantage for AI-assisted software.&lt;/p&gt;

&lt;p&gt;The vocabulary has crossed a threshold. In roughly sixty days it has moved from practitioner discourse into vendor product surfaces, which is faster than most category terms travel. The phrase used to belong to people who were losing sleep over the problem. Now it also belongs to people selling against it.&lt;/p&gt;

&lt;p&gt;That distinction matters because vocabulary leads product. The language a vendor uses in docs encodes a promise about what the product delivers. When several vendors describe their offerings using the same phrase, the readers of those docs start to expect the phrase to mean something specific. The category forms in language before it forms in products. Once the phrase is in CPO interviews and product documentation, the window for defining what it means starts to close.&lt;/p&gt;

&lt;p&gt;This post is about the gap between the two. The vocabulary moved. The products moved, but mostly not in the direction the vocabulary points. The thing the language most directly describes, a queryable cross-repo dependency graph derived from source manifests, exists. It lives in &lt;a href="https://engineering.fb.com/2026/04/06/developer-tools/how-meta-used-ai-to-map-tribal-knowledge-in-large-scale-data-pipelines/" rel="noopener noreferrer"&gt;Meta's tribal knowledge engine&lt;/a&gt;, in &lt;a href="https://www.mabl.com/blog/how-we-built-a-system-for-ai-agents-to-ship-real-code-across-75-repos" rel="noopener noreferrer"&gt;Mabl's 850-line hand-maintained coordination graph&lt;/a&gt;, and in &lt;a href="https://riftmap.dev/" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt;. It does not yet live in the product that most readers reach for when they read "cross-repo context" in their AI tool's docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the products actually ship
&lt;/h2&gt;

&lt;p&gt;The landscape past the vocabulary line is finer-grained than I thought before doing the research. Four distinct things sit under the same phrase. Each is real. Each answers a different question. Only one of them is the thing the language most directly promises, and that one is the one no consumer vendor has shipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The workspace pattern.&lt;/strong&gt; This is the largest cohort by vendor count. Warp, Cursor, Cline, Continue, Aider, Zed, Windsurf, GitHub Copilot, and Claude Code all sit here. They give the agent access to more files. Warp's docs are explicit: &lt;em&gt;"During cross-repo tasks, Warp's Agents have access to the file paths of all indexed repos."&lt;/em&gt; Cursor's recommended pattern is the multi-root workspace; their community forum has open feature requests for cross-repo agent communication from &lt;a href="https://forum.cursor.com/t/working-on-multiple-repositories/146972" rel="noopener noreferrer"&gt;December 2025&lt;/a&gt; and &lt;a href="https://forum.cursor.com/t/feature-request-cross-window-cross-repo-agent-communication/151603" rel="noopener noreferrer"&gt;February 2026&lt;/a&gt;, both still open. GitHub Copilot's most relevant recent shipping is &lt;a href="https://github.com/orgs/community/discussions/173575" rel="noopener noreferrer"&gt;picking the repository when assigning issues to Copilot&lt;/a&gt;, which lets an agent operate in a different repo per assignment. That is the workspace pattern at organisation scale. None of these is a dependency graph. None resolves an artifact to its consumers. The agent sees more files. It does not see how they relate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The semantic retrieval index.&lt;/strong&gt; This is &lt;a href="https://www.augmentcode.com/product/context-engine-mcp" rel="noopener noreferrer"&gt;Augment Code's Context Engine MCP&lt;/a&gt;. Augment indexes code across multiple repositories, runs the index locally, and exposes a retrieval interface (&lt;code&gt;codebase-retrieval&lt;/code&gt;, &lt;code&gt;codebase-search&lt;/code&gt;) that other agents can call via MCP. Augment publishes benchmark numbers showing real quality gains for downstream tools that wire it in. This is genuine substrate work. It answers questions of the form &lt;em&gt;"find me code relevant to authentication"&lt;/em&gt; very well. The retrieval is semantic, with embeddings over code chunks, which means the index is excellent for navigation and comprehension. It is not, by design, a parser-derived index of how a Terraform module gets consumed by which Helm chart and deployed by which CI pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code-symbol graph.&lt;/strong&gt; This is &lt;a href="https://sourcegraph.com/docs/cody" rel="noopener noreferrer"&gt;Sourcegraph Cody&lt;/a&gt;. Cody sits on a code intelligence graph that understands symbols, including function definitions, references, and call paths, and supports queries across multiple repositories. &lt;em&gt;"Where is this function used?"&lt;/em&gt; gets a precise answer that includes callers in repositories the user has not opened. This is closest to what the word "graph" implies, but it is a code-symbol graph, not an infrastructure-dependency graph. It is also enterprise-only since Sourcegraph discontinued the Free and Pro tiers in July 2025. Pricing starts at $59 per user per month, and the chat UI caps &lt;code&gt;@&lt;/code&gt;-mention selection at ten repositories per query. Real product, with the scope its product design implies: code symbols, not infrastructure manifests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The JS/TS project graph.&lt;/strong&gt; &lt;a href="https://nx.dev/docs/concepts/synthetic-monorepos" rel="noopener noreferrer"&gt;Nx Synthetic Monorepos&lt;/a&gt;, announced in their &lt;a href="https://nx.dev/blog/nx-2026-roadmap" rel="noopener noreferrer"&gt;2026 roadmap&lt;/a&gt;, is the closest publicly-shipped equivalent to the architectural bet under Riftmap. Nx parses cross-repo dependencies into a workspace graph, runs a coordinator agent that walks the graph and spawns per-repo workers, and ships impact analysis and conformance rules across boundaries. This is the same shape of architecture. The ecosystem coverage is JavaScript and TypeScript first, with plugin-based extension to other languages, and the orientation is build orchestration and CI rather than infrastructure-as-code blast radius. Nx independently arriving at the same answer is, if anything, the strongest single piece of evidence that the substrate is the right architecture. Their product extends the JS/TS monorepo ecosystem across repository boundaries. It does not parse Dockerfile FROM lines, Terraform module sources, or Helm chart dependencies across twelve manifest ecosystems.&lt;/p&gt;

&lt;p&gt;Each of these does something real. None of them is the artifact the vocabulary most directly describes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The layer that hasn't moved
&lt;/h2&gt;

&lt;p&gt;The thing the vocabulary points to most directly is a parser-derived, queryable dependency graph across the manifests that govern deployment. That means Terraform module sources and registries, Dockerfile FROM statements across registries, Helm chart dependencies, Kubernetes manifest references, Kustomize overlays, ArgoCD application sources, Go modules, npm packages, Python requirements, Ansible role dependencies, GitLab CI includes, and GitHub Actions workflow calls. Twelve ecosystems, give or take, that together encode how an organisation's software actually runs in production.&lt;/p&gt;

&lt;p&gt;The question this layer answers is structurally distinct from the questions the four above answer. "Where is this function called?" lives in code symbols. "Find me code relevant to authentication" lives in semantic embeddings. "Which packages need to rebuild?" lives in the JS/TS workspace graph. &lt;em&gt;"If I bump &lt;code&gt;python:3.11-slim&lt;/code&gt; to &lt;code&gt;python:3.12-slim&lt;/code&gt;, which services pull that base image, which Helm charts deploy those services, which CI pipelines need to retrigger, and which downstream Terraform modules reference the resulting images?"&lt;/em&gt; lives in the infrastructure-manifest graph. None of the four above will give you that answer, because none of them parses those files in that way.&lt;/p&gt;

&lt;p&gt;Meta's &lt;a href="https://engineering.fb.com/2026/04/06/developer-tools/how-meta-used-ai-to-map-tribal-knowledge-in-large-scale-data-pipelines/" rel="noopener noreferrer"&gt;tribal knowledge engine post&lt;/a&gt; named the cost of not having this layer at runtime. The "what depends on X" question costs roughly 6,000 tokens as a multi-file exploration and roughly 200 tokens as a single graph lookup. That is a thirty-times architecture-level efficiency gap on one of the most common planning questions an agent asks. The number is durable. It is not a model-quality problem and it does not close as context windows grow. It is the cost of reconstructing structure from grep every session instead of querying an index that already knows.&lt;/p&gt;

&lt;p&gt;Meta built that index in-house. &lt;a href="https://www.mabl.com/blog/how-we-built-a-system-for-ai-agents-to-ship-real-code-across-75-repos" rel="noopener noreferrer"&gt;Mabl built theirs by hand&lt;/a&gt;, with 850 lines of registry maintained by a platform team across 79 repositories. &lt;a href="https://www.harness.io/blog/your-repo-is-a-knowledge-graph-you-just-dont-query-it-yet" rel="noopener noreferrer"&gt;Harness named it&lt;/a&gt; in their April essay on Source Context Management, framing it as &lt;em&gt;"the blast radius of every change before it merges."&lt;/em&gt; Harness sells CD, so the post is content marketing rather than competing product. The architectural conclusion these three teams reached independently, that the dependency graph should be parser-derived, queryable as a primitive, and treated as runtime infrastructure rather than per-session context, is the conclusion Riftmap shipped a productised version of. The substrate the vocabulary now describes in product docs is the same one Mabl, Meta, and Harness named in engineering blogs. It is the one that has not yet made its way into any consumer AI coding product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CPO and the feature request
&lt;/h2&gt;

&lt;p&gt;The clearest signal of where this category is going might be the asymmetry between what GitHub says and what GitHub ships.&lt;/p&gt;

&lt;p&gt;In early December 2025, GitHub's chief product officer Mario Rodriguez named "repository intelligence" as the inflection point for 2026 in &lt;a href="https://news.microsoft.com/source/features/ai/whats-next-in-ai-7-trends-to-watch-in-2026/" rel="noopener noreferrer"&gt;Microsoft's annual AI trends piece&lt;/a&gt;. The framing is direct: AI that understands code at the level of relationships and history rather than just lines of code. In Rodriguez's words, repository intelligence "will become a competitive advantage by providing the structure and context for smarter, more reliable AI." The phrasing is good. The category is real.&lt;/p&gt;

&lt;p&gt;In March 2026, a GitHub user filed &lt;a href="https://github.com/orgs/community/discussions/189213" rel="noopener noreferrer"&gt;Discussion #189213&lt;/a&gt;, titled &lt;em&gt;Feature Request: Cross-Repository Context for Copilot (Web ↔ Microservice)&lt;/em&gt;, describing the exact problem the CPO is naming. Web app consumes microservice; service API changes; both repos need coordinated updates; Copilot should be able to see across the boundary. Three upvotes. Two months later, the discussion is still marked Unanswered. It is one specific feature request; I do not want to read too much into a single thread. But the asymmetry is concrete. The CPO of the dominant code platform names the category. The same platform's flagship coding product has not yet answered the feature request that defines it. That is the clearest signal I have found that the language is moving faster than the products.&lt;/p&gt;

&lt;p&gt;Cursor's situation is similar in shape. The product is excellent within a single repo. The community forum has cross-repo feature requests from December 2025 and February 2026, both still open, both with the same pattern of users describing dependency relationships across repositories that the agent cannot see.&lt;/p&gt;

&lt;p&gt;This is not an indictment of either vendor. It is a description of where the leading edge of a category sits when the language has crossed into mainstream product surfaces but the substrate has not. Vendors who use the phrase get credit for the diagnosis. Vendors who ship the layer underneath get credit for the category. The two are different things and right now they are owned by different people.&lt;/p&gt;

&lt;p&gt;The window where that authorship gets decided is finite. Once a major vendor ships a parser-derived cross-repo dependency graph as a first-class feature, the category gets named after their product and everyone else becomes a comparison. That has not happened yet. It is the most interesting bet to be making over the next two quarters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Riftmap sits
&lt;/h2&gt;

&lt;p&gt;Riftmap is the productised version of the layer Meta, Mabl, and Harness have separately described. Twelve parser ecosystems today (Terraform, Dockerfile, Helm, Kubernetes, Kustomize, ArgoCD, Go, npm, Python, Ansible, GitLab CI, GitHub Actions), auto-discovered from a single read-only token, with more on the way. The roadmap extends the parser surface into contract layers (OpenAPI, protobuf, GraphQL) and schema registries, because the manifest plane and the contract plane are both part of the same dependency graph at runtime. No registry to maintain. No catalog YAML. The graph re-runs on every push; freshness is by construction rather than by process. Three endpoints (&lt;code&gt;/repositories/lookup&lt;/code&gt;, &lt;code&gt;/repositories/{id}/context&lt;/code&gt;, &lt;code&gt;/repositories/{id}/impact&lt;/code&gt;) are designed to be called by an agent during planning rather than read by a human in a dashboard.&lt;/p&gt;

&lt;p&gt;The position against the four above is not that they are wrong. Augment's semantic retrieval is excellent at what it does. Cody's code-symbol graph is excellent at what it does. Nx's synthetic monorepo is the right answer for organisations whose primary stack is JS/TS. The position is that none of them is the parser-derived infrastructure-manifest graph, because that is not what they were designed to be. The reader who needs that graph today either builds it like Mabl, runs it like Meta, or queries Riftmap.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bear case
&lt;/h3&gt;

&lt;p&gt;If a major model provider (Anthropic, OpenAI, GitHub) ships a platform-level MCP for cross-repo context as a standard feature, the API layer of this category gets commoditised fast. The defence against that scenario is the part that is hardest to copy: the breadth of the parser surface (twelve ecosystems is more work than it looks, particularly around private registries and transitive resolution), and the freshness contract (the index is parser-derived and re-runs on every push, which is structurally different from any context layer that depends on LLM summarisation). A platform MCP for cross-repo context would still need someone to actually parse the manifests. That work does not become easier because the agent has a new channel to call into.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bull case
&lt;/h3&gt;

&lt;p&gt;The bull case is the architectural choice underneath all of this, and it is the one I keep coming back to. Declarations are deterministic. A Terraform module source, a Dockerfile FROM statement, a Helm chart dependency, a GitHub Actions &lt;code&gt;uses:&lt;/code&gt; reference: these are file-based facts. A parser produces the same graph for the same input on every run. There is no temperature, no top-p, no retry-and-hope-it-converges. The output is binary. A dependency either exists in the graph or it does not.&lt;/p&gt;

&lt;p&gt;That has three consequences that compound over time, and they are why I think the deterministic-first architecture wins this category rather than loses it.&lt;/p&gt;

&lt;p&gt;The first is testability. Every parser case is a fixture. &lt;em&gt;"Given this Dockerfile with this ARG default, the graph should contain edge X→Y."&lt;/em&gt; You can write that test, run it in a millisecond, and pin the behaviour for the lifetime of the codebase. You cannot write that test against an LLM-extracted dependency graph; the same input produces different outputs across runs, model versions, and prompt revisions. The deterministic foundation makes the system improvable in a way that probabilistic extraction is not. Every false positive becomes a permanent test case. Every edge case becomes a permanent regression check. Improvement compounds.&lt;/p&gt;

&lt;p&gt;The second is cost. A parser pass over a repository is microseconds and free. An LLM pass over the same repository is hundreds of milliseconds and bills per token. At organisation scale, with continuous re-scanning on every push, the cost gap is several orders of magnitude. The product that built its dependency layer on a deterministic foundation can offer it cheaply forever. The product that wired LLM extraction into the same loop pays a continuous tax that grows linearly with the size of the codebase and the frequency of change.&lt;/p&gt;

&lt;p&gt;The third is what sits on top. The graph being deterministic does not mean AI has no role. It means AI sits on top of it for the things AI is genuinely good at: ranking, summarising, explaining, planning, prioritising. The agent calls &lt;code&gt;/repositories/{id}/impact&lt;/code&gt; to get the deterministic blast radius, then uses the model to reason about which of the affected repos matter most, which changes are highest-risk, and how to sequence the work. The base layer is the part that needs to be correct. The reasoning layer is the part that needs to be flexible. The bet is that the right architecture for AI coding agents at scale is deterministic at the base and probabilistic at the top, not the other way around. I &lt;a href="https://riftmap.dev/blog/ai-doesnt-understand-blast-radius/" rel="noopener noreferrer"&gt;argued this case in more detail in the blast radius post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The thing I most want to be wrong about is the timing. If the window is wider than two quarters, the bet is easier. If it is narrower, the next post should already be live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The vocabulary moved. The CPO of the largest code platform on the planet has named the category. Several vendors use the phrase in their product surfaces. JetBrains coined a phrase for what AI ships without it. The language now describes a thing more precisely than most of the products that use the language actually deliver.&lt;/p&gt;

&lt;p&gt;The layer the language describes is the parser-derived cross-repo dependency graph. It exists in Meta's in-house system, in Mabl's hand-maintained registry, and in Riftmap. It does not yet exist in the AI coding product that most readers of this post will reach for tomorrow. The window for that to change is open. The next two quarters decide who ends up owning what the language now promises.&lt;/p&gt;

&lt;p&gt;Riftmap is the bet that whoever ships the deterministic substrate during this window, while the language is still being formed and before a major vendor consolidates the category, is the one whose product the language ends up describing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're running AI coding agents across more than a handful of repositories and the language in their docs is starting to feel ahead of what they deliver, the layer underneath is the gap. You can build it yourself, the way Mabl did. Or you can &lt;a href="https://app.riftmap.dev" rel="noopener noreferrer"&gt;start a free scan&lt;/a&gt; and let the parsers find it.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources referenced
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Warp, &lt;em&gt;Codebase Context&lt;/em&gt; — &lt;a href="https://docs.warp.dev/agent-platform/capabilities/codebase-context/" rel="noopener noreferrer"&gt;docs.warp.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Susanna Ray, Microsoft, &lt;em&gt;What's next in AI: 7 trends to watch in 2026&lt;/em&gt; — &lt;a href="https://news.microsoft.com/source/features/ai/whats-next-in-ai-7-trends-to-watch-in-2026/" rel="noopener noreferrer"&gt;news.microsoft.com&lt;/a&gt;, December 8, 2025 (Mario Rodriguez on repository intelligence, section 6)&lt;/li&gt;
&lt;li&gt;Augment Code, &lt;em&gt;Context Engine MCP&lt;/em&gt; — &lt;a href="https://www.augmentcode.com/product/context-engine-mcp" rel="noopener noreferrer"&gt;augmentcode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sourcegraph, &lt;em&gt;Cody documentation&lt;/em&gt; — &lt;a href="https://sourcegraph.com/docs/cody" rel="noopener noreferrer"&gt;sourcegraph.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nx, &lt;em&gt;Synthetic Monorepos&lt;/em&gt; — &lt;a href="https://nx.dev/docs/concepts/synthetic-monorepos" rel="noopener noreferrer"&gt;nx.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nx, &lt;em&gt;2026 Roadmap: Expanding Agent Autonomy&lt;/em&gt; — &lt;a href="https://nx.dev/blog/nx-2026-roadmap" rel="noopener noreferrer"&gt;nx.dev/blog&lt;/a&gt;, February 4, 2026&lt;/li&gt;
&lt;li&gt;prateek-odev, &lt;em&gt;Feature Request: Cross-Repository Context for Copilot (Web ↔ Microservice)&lt;/em&gt; — &lt;a href="https://github.com/orgs/community/discussions/189213" rel="noopener noreferrer"&gt;GitHub Discussion #189213&lt;/a&gt;, March 2026&lt;/li&gt;
&lt;li&gt;Cursor Community Forum, &lt;em&gt;Working on multiple repositories&lt;/em&gt; — &lt;a href="https://forum.cursor.com/t/working-on-multiple-repositories/146972" rel="noopener noreferrer"&gt;forum.cursor.com&lt;/a&gt;, December 2025&lt;/li&gt;
&lt;li&gt;Cursor Community Forum, &lt;em&gt;Feature Request: Cross-Window / Cross-Repo Agent Communication&lt;/em&gt; — &lt;a href="https://forum.cursor.com/t/feature-request-cross-window-cross-repo-agent-communication/151603" rel="noopener noreferrer"&gt;forum.cursor.com&lt;/a&gt;, February 2026&lt;/li&gt;
&lt;li&gt;Engineering at Meta, &lt;em&gt;How Meta used AI to map tribal knowledge in large-scale data pipelines&lt;/em&gt; — &lt;a href="https://engineering.fb.com/2026/04/06/developer-tools/how-meta-used-ai-to-map-tribal-knowledge-in-large-scale-data-pipelines/" rel="noopener noreferrer"&gt;engineering.fb.com&lt;/a&gt;, April 6, 2026&lt;/li&gt;
&lt;li&gt;Geoff Cooney, mabl, &lt;em&gt;How We Built a System for AI Agents to Ship Real Code Across 75+ Repos&lt;/em&gt; — &lt;a href="https://www.mabl.com/blog/how-we-built-a-system-for-ai-agents-to-ship-real-code-across-75-repos" rel="noopener noreferrer"&gt;mabl.com&lt;/a&gt;, April 8, 2026&lt;/li&gt;
&lt;li&gt;Ompragash Viswanathan, Harness, &lt;em&gt;Your Repo Is a Knowledge Graph. You Just Don't Query It Yet.&lt;/em&gt; — &lt;a href="https://www.harness.io/blog/your-repo-is-a-knowledge-graph-you-just-dont-query-it-yet" rel="noopener noreferrer"&gt;harness.io/blog&lt;/a&gt;, April 1, 2026&lt;/li&gt;
&lt;li&gt;Riftmap, &lt;em&gt;AI Doesn't Understand Blast Radius: Why Change Failure Rates Are Up 30%&lt;/em&gt; — &lt;a href="https://riftmap.dev/blog/ai-doesnt-understand-blast-radius/" rel="noopener noreferrer"&gt;riftmap.dev/blog&lt;/a&gt;, April 19, 2026&lt;/li&gt;
&lt;li&gt;Riftmap, &lt;em&gt;You don't need a virtual monorepo. You need a graph.&lt;/em&gt; — &lt;a href="https://riftmap.dev/blog/you-dont-need-a-virtual-monorepo/" rel="noopener noreferrer"&gt;riftmap.dev/blog&lt;/a&gt;, May 12, 2026&lt;/li&gt;
&lt;li&gt;Riftmap, &lt;em&gt;AI coding agents need cross-repo context&lt;/em&gt; — &lt;a href="https://riftmap.dev/blog/ai-coding-agents-need-cross-repo-context/" rel="noopener noreferrer"&gt;riftmap.dev/blog&lt;/a&gt;, May 12, 2026&lt;/li&gt;
&lt;li&gt;Riftmap, &lt;em&gt;Meta needed 50+ AI agents to map their tribal knowledge&lt;/em&gt; — &lt;a href="https://riftmap.dev/blog/meta-tribal-knowledge-engine-build-the-graph-first/" rel="noopener noreferrer"&gt;riftmap.dev/blog&lt;/a&gt;, May 8, 2026&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Appendix: structured summary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; Vocabulary describing cross-repo dependency intelligence has crossed into vendor product documentation, but the layer the vocabulary most directly describes, a parser-derived cross-repo dependency graph across infrastructure manifests, has not yet shipped in any consumer-facing AI coding product. Four distinct things now sit under the same phrase: the workspace pattern (Warp, Cursor, Cline, Continue, Aider, Zed, Windsurf, GitHub Copilot, Claude Code), semantic retrieval (Augment Code's Context Engine MCP), code-symbol graph (Sourcegraph Cody), and JS/TS project graph (Nx Synthetic Monorepos). None of the four covers the infrastructure-manifest layer where deployment-time blast radius lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Warp Codebase Context docs, Augment Context Engine MCP product pages, Sourcegraph Cody @-mention semantics, and Nx Synthetic Monorepos in their 2026 roadmap all use the cross-repo vocabulary explicitly.&lt;/li&gt;
&lt;li&gt;Mario Rodriguez, GitHub's chief product officer, named "repository intelligence" as the inflection point for 2026 AI in Microsoft's annual trends piece (December 2025).&lt;/li&gt;
&lt;li&gt;GitHub Copilot Discussion #189213 (Cross-Repository Context for Copilot, March 2026) remained Unanswered two months after filing.&lt;/li&gt;
&lt;li&gt;Cursor community forum has cross-repo feature requests open from December 2025 and February 2026.&lt;/li&gt;
&lt;li&gt;Meta's published number: graph lookup for "what depends on X" costs ~200 tokens; multi-file exploration costs ~6,000. A ~30x architectural efficiency gap.&lt;/li&gt;
&lt;li&gt;Mabl's 850-line Repo Coordination Graph spans 79+ repositories and is maintained by hand.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architectural takeaway:&lt;/strong&gt; The vocabulary leads the products by approximately two quarters. The kind of substrate the language most directly describes, a parser-derived infrastructure-manifest dependency graph across Terraform, Docker, Helm, Kubernetes, CI manifests, and other ecosystems, exists only in in-house systems (Meta), hand-maintained registries (Mabl), and Riftmap. The window for owning the productised definition of "cross-repo context" closes when a major vendor ships that layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why deterministic-first wins:&lt;/strong&gt; Declarations are deterministic. A parser produces the same graph for the same input on every run, which makes the system testable as fixtures, cheap to operate at organisation scale, and reliable to wire into agent planning loops. AI sits on top of the graph for ranking, summarising, and prioritising. The base layer is the part that needs to be correct; the reasoning layer is the part that needs to be flexible. The right architecture for AI coding agents at scale is deterministic at the base and probabilistic at the top.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; Platform engineers, DevOps leads, and engineering managers running AI coding agents across multi-repo organisations, particularly those whose dependency graph crosses infrastructure-as-code boundaries.&lt;/p&gt;

</description>
      <category>crossrepocontext</category>
      <category>aicodingagents</category>
      <category>repositoryintelligence</category>
      <category>platformengineering</category>
    </item>
    <item>
      <title>You don't need a virtual monorepo. You need a graph.</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Wed, 13 May 2026 09:50:00 +0000</pubDate>
      <link>https://dev.to/danielwe/you-dont-need-a-virtual-monorepo-you-need-a-graph-3fo2</link>
      <guid>https://dev.to/danielwe/you-dont-need-a-virtual-monorepo-you-need-a-graph-3fo2</guid>
      <description>&lt;p&gt;In the past weeks, two engineers have published some of the most concrete writeups I've seen of how to give AI coding agents context across more than one repository. Owen Zanzal's &lt;a href="https://medium.com/devops-ai/the-virtual-monorepo-pattern-how-i-gave-claude-code-full-system-context-across-35-repos-43b310c97db8" rel="noopener noreferrer"&gt;The Virtual Monorepo Pattern&lt;/a&gt; (March 23, 2026) and Rafferty Uy's &lt;a href="https://raffertyuy.com/raztype/repo-of-repos-pattern/" rel="noopener noreferrer"&gt;Repo-of-Repos&lt;/a&gt; (May 2, 2026). Both describe a similar shape of solution. Bundle your repos into a workspace folder. Hand the agent a system map. Ship.&lt;/p&gt;

&lt;p&gt;Both posts are good engineering. They're worth reading carefully. They also describe the strongest version of an approach that stops working at exactly the scale where the problem starts to matter, and the failure mode is the kind that's painful to retrofit.&lt;/p&gt;

&lt;p&gt;What follows is an argument with the pattern, not with the authors. Both posts are the kind of thing I want more of: specific, shipped, written from real use. The disagreement is about where the pattern's ceiling is, and what's worth building above it.&lt;/p&gt;

&lt;p&gt;This is the third post in a loose series. The &lt;a href="https://riftmap.dev/blog/meta-tribal-knowledge-engine-build-the-graph-first/" rel="noopener noreferrer"&gt;first&lt;/a&gt; looked at Meta's tribal knowledge engine and the structural-vs-semantic decomposition hidden inside it. The &lt;a href="https://riftmap.dev/blog/ai-coding-agents-need-cross-repo-context/" rel="noopener noreferrer"&gt;second&lt;/a&gt; walked through three teams converging on cross-repo context as runtime infrastructure. This one is about the alternative response, why I think it's a stopgap rather than a destination, and the asymmetric upgrade path that makes the difference matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the workspace pattern gets right
&lt;/h2&gt;

&lt;p&gt;Owen Zanzal's piece is the cleanest articulation. Three files in a workspace directory:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;.repos&lt;/code&gt;, a bash script that clones every relevant repo into a structured local folder.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLAUDE.md&lt;/code&gt;, a hand-written system map describing how services relate to each other.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;README.md&lt;/code&gt;, a deeper narrative for &lt;em&gt;why&lt;/em&gt; the system is structured the way it is.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;He calls this the virtual monorepo pattern. The thesis line is the part worth taking seriously: &lt;em&gt;"the problem isn't repo structure. The problem is context visibility. Your AI assistant doesn't need to live in a monorepo. It just needs to see one. You don't need a monorepo. You need a monorepo view."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I think that's a correct assessment, and it's the right diagnosis for most teams in this position. They do not actually want to migrate to a monorepo. The migration cost is real, the political friction across teams is real, and the CI/CD changes are real. The workspace pattern delivers a wider context window without paying any of those costs. It's purely additive. It exists alongside the existing repo structure. Nothing breaks.&lt;/p&gt;

&lt;p&gt;Rafferty Uy's &lt;code&gt;repo-of-repos&lt;/code&gt; pattern, published a week ago, is a more polished version of the same idea, named after the agent (Tony) it powers. An outer "agent" repo pulls in all related repos under a &lt;code&gt;repos/&lt;/code&gt; folder as workspace folders, while commits still flow back to each underlying repo's own origin. He ships it as a &lt;a href="https://github.com/raffertyuy/repo-of-repos" rel="noopener noreferrer"&gt;GitHub template&lt;/a&gt;. He explicitly names three things that have changed in the last year that make the pattern work right now: context engineering is now a discipline, context windows got significantly larger, and "agents grep before they answer."&lt;/p&gt;

&lt;p&gt;I want to single out the second of those because it's the load-bearing assumption of the entire pattern, and the rest of this post is going to push on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The load-bearing assumption
&lt;/h2&gt;

&lt;p&gt;The workspace pattern works as long as two things hold simultaneously:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The bundled workspace fits in the agent's context window.&lt;/li&gt;
&lt;li&gt;Grepping over that workspace is cheap enough that the agent reaches for it routinely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both of those hold for Owen Zanzal at 35 repos. They hold for Rafferty Uy at the size he describes. They will hold for many readers of this post. However, I don't think they hold at 200 repos, and the failure isn't gradual.&lt;/p&gt;

&lt;p&gt;Frontier model context windows are around a million tokens at the upper end. A typical mid-sized service repository runs to several hundred thousand lines of code, much of it dependencies and tests. Bundle 200 of them and you don't fit. You don't fit in the next generation either, because organisations grow repos faster than frontier labs ship context length increases.&lt;/p&gt;

&lt;p&gt;Even when the workspace fits in the window, the workspace doesn't load itself into the prompt. Agents grep before they answer, in Rafferty's framing. Grep is O(N) over workspace size. Asking "what depends on X" by grepping 200 repos costs token, time, and latency that doesn't scale, and the answer is fuzzy at best. Meta's published number on this is the cleanest data point in the public record: a graph lookup answers "what depends on X" in around 200 tokens, the same question by exploration costs around 6,000. That's a 30x reduction. It isn't a frontier-model problem. It's an architecture problem.&lt;/p&gt;

&lt;p&gt;There's also a third issue, smaller in any single session but compounding over time. The &lt;code&gt;CLAUDE.md&lt;/code&gt; system map at the heart of the workspace pattern is a hand-written context file. The Gloaguen et al. paper from ETH Zurich and LogicStar.ai (February 2026) studied this directly across four coding agents on 138 niche-repository tasks plus SWE-bench Lite. Their finding: developer-written context files give a marginal +4% improvement in agent success rate, at +19% inference cost. LLM-generated context files give a -3% effect at +20% cost. Across the board, "context files do not provide effective overviews." Agents take the same number of steps to find relevant files whether a context file is present or not.&lt;/p&gt;

&lt;p&gt;That doesn't make &lt;code&gt;CLAUDE.md&lt;/code&gt; useless. The +4% case is real. But it caps the upside of the pattern, and it makes maintenance non-negotiable. Owen Zanzal himself names this cost explicitly: &lt;em&gt;"Keeping &lt;code&gt;.repos&lt;/code&gt; in sync. New repos need to be added to the script. Repos that change names or move need to be updated. This is low-friction but not zero-friction."&lt;/em&gt; That's true at 35 repos. At 200, with five new repos a quarter, it is a part-time job nobody's job description includes.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the pattern actually breaks
&lt;/h2&gt;

&lt;p&gt;I want to be specific about how this breaks, because abstract failure modes don't land. Five concrete things go wrong as you scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One.&lt;/strong&gt; The bundled workspace exceeds the context window. The agent silently truncates or the editor refuses to load the folder. You can mitigate this with multiple smaller workspaces scoped to different domains, which Owen Zanzal recommends as a workaround, but you've now manually re-implemented domain partitioning that the dependency graph would have given you for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two.&lt;/strong&gt; The agent grep over a 200-repo workspace becomes a 30x token tax on every "what depends on X" query. This isn't theoretical. Even Stripe, which has a real monorepo with hundreds of millions of lines of mostly Ruby code, doesn't dump everything into the agent's context. Their published architecture for the Minions agent system uses directory-scoped rule files that attach automatically as the agent traverses the filesystem, &lt;em&gt;"rather than a single global context dump that would overflow any model's window."&lt;/em&gt; If Stripe at monorepo scale can't dump it all, neither can your virtual monorepo at 200 repos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three.&lt;/strong&gt; &lt;code&gt;CLAUDE.md&lt;/code&gt; decays. New service added, old service deprecated, schema changes, ownership boundaries shift. The system map drifts from reality. By the time someone notices, the agent has been confidently shipping changes against a stale model of the system. This is the failure mode Meta's engineering team named in their April 6 post on tribal knowledge: &lt;em&gt;"context that decays is worse than no context."&lt;/em&gt; It's the reason their system uses a self-refreshing critic swarm. The workspace pattern has no equivalent, because nothing's running against it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Four.&lt;/strong&gt; Workspace gives the agent read access. It does not give the agent dependency awareness. The agent can grep through every repo in the workspace and still ship a change to the API repo that breaks the consumer it never opened that session. The cross-repo dependency graph is exactly the data structure that closes that gap, and grepping the workspace doesn't reconstruct it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Five.&lt;/strong&gt; You can't bolt the substrate on cheaply once the workspace pattern has settled in. Once teams are committing &lt;code&gt;CLAUDE.md&lt;/code&gt; updates, scripts depend on &lt;code&gt;.repos&lt;/code&gt; layout, and onboarding tells new engineers "open the agent workspace," migrating to a queryable graph is a second project, not a refinement of the first. The substrate has to exist before you need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The point both authors gesture toward but don't reach
&lt;/h2&gt;

&lt;p&gt;Here's what surprised me when I read Owen Zanzal's piece carefully. His final section is called "Where This Pattern Can Go." It reads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Auto-generating &lt;code&gt;CLAUDE.md&lt;/code&gt; from service metadata, API contracts, and event schemas, keeping the system map current as the system changes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dependency graphs derived from import analysis, Terraform dependency trees, or event topic mappings, and feeding those into the AI context automatically.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Architecture doc generation as a CI artifact, so the README stays in sync with the actual system rather than drifting toward fiction.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the substrate. He has named it precisely. He has written the bullet points for what comes next. The only thing he hasn't done is build it.&lt;/p&gt;

&lt;p&gt;This is the pattern with most workspace posts I've read. The author solves the immediate problem at their current scale, lists "auto-generated dependency graph" as future work, and ships. Rafferty Uy's &lt;code&gt;repo-of-repos&lt;/code&gt; template assumes the agent grepping the workspace is the operating model and never asks at what scale grepping stops being affordable. Both posts are written for the scale where the workspace is the right level of investment. The substrate question opens up at a different scale, and neither author was writing at that scale.&lt;/p&gt;

&lt;p&gt;I think the better read is: the workspace pattern is the stopgap, and the substrate is what it grows into. Specifically, the auto-generated dependency graph bullet at the end of Owen Zanzal's post is doing the load-bearing work. Without it, the pattern hits the maintenance ceiling somewhere between 50 and 100 repos. With it, the maintenance ceiling moves from "engineer with a weekend" to "platform that auto-discovers."&lt;/p&gt;

&lt;p&gt;That second thing is what &lt;a href="https://riftmap.dev/" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; exposes today. It's what Mabl built by hand into their 850-line Repo Coordination Graph in &lt;a href="https://www.mabl.com/blog/how-we-built-a-system-for-ai-agents-to-ship-real-code-across-75-repos" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; and now extended to 100+ repositories with explicit blast-radius-for-tech-debt framing in &lt;a href="https://www.mabl.com/blog/how-we-built-a-system-for-ai-agents-to-ship-real-code-across-75-repos-part-2" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt;. It's the cross-repo dependency index Meta produced as a byproduct of their tribal knowledge engine. It's what Harness, the CD vendor, called the "knowledge graph" that lets you see &lt;em&gt;"the blast radius of every change before it merges"&lt;/em&gt; in their &lt;a href="https://www.harness.io/blog/your-repo-is-a-knowledge-graph-you-just-dont-query-it-yet" rel="noopener noreferrer"&gt;April 1 essay&lt;/a&gt; on Source Context Management. Four independent teams have built it or named it as a primitive in the past six weeks. The workspace pattern is the thing you ship while you wait for that primitive to exist for your stack. If it already exists, the pattern is the alternative bet, not the better one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stripe is the proof
&lt;/h2&gt;

&lt;p&gt;If I had to pick one piece of evidence that the workspace pattern alone doesn't carry you, it's Stripe.&lt;/p&gt;

&lt;p&gt;Stripe runs an internal coding agent system called Minions. As of February 2026, Minions produced over 1,300 merged pull requests per week, all written entirely by AI, all reviewed by humans. They run against Stripe's actual codebase, which is hundreds of millions of lines of mostly Ruby. Stripe is not 35 repos. They are the maximum case for what "monorepo plus large model" can do.&lt;/p&gt;

&lt;p&gt;And it isn't enough. Stripe built an MCP server called Toolshed that exposes nearly 500 internal tools to Minions. They built directory-scoped rule files instead of a global context dump because, in their own published words, the global dump &lt;em&gt;"would overflow any model's window."&lt;/em&gt; They built pre-warmed devboxes, deterministic verification gates, and a custom fork of Block's Goose agent. Signadot's piece &lt;a href="https://thenewstack.io/coding-agents-feedback-signals/" rel="noopener noreferrer"&gt;Coding Agents Are Only as Good as the Signals You Feed Them&lt;/a&gt; puts it cleanly: &lt;em&gt;"Stripe did not achieve this volume simply by pointing a large language model at its monorepo."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the team with the world's most demanding monorepo can't simply bundle everything and let the agent figure it out, the bet that a virtual monorepo will solve it for the rest of us might be structurally unsound. The substrate is what you build because the workspace alone, even when the workspace is real, isn't where the leverage lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The asymmetric upgrade path
&lt;/h2&gt;

&lt;p&gt;Here's how I think about the buy/build decision for teams in this range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At 30 to 50 repos with one engineer who has a weekend.&lt;/strong&gt; The workspace pattern is genuinely fine. Build it. Owen Zanzal's three files will get you most of the way. Rafferty Uy's &lt;code&gt;repo-of-repos&lt;/code&gt; template will get you the rest. Don't overthink it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At 100+ repos and growing.&lt;/strong&gt; The workspace pattern is a maintenance bet you're going to lose. &lt;code&gt;CLAUDE.md&lt;/code&gt; will decay, &lt;code&gt;.repos&lt;/code&gt; will drift, and the agent will gain false confidence in stale information. The fix is structural, not procedural. The dependency graph derived from source code, refreshed on every push, is what closes the gap, and you want it before you need it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At any scale, if the substrate already exists for your stack.&lt;/strong&gt; The workspace is the alternative bet, not the better one. If &lt;a href="https://app.riftmap.dev/" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; or something like it can already auto-discover your dependency graph from a read-only org token, the workspace pattern is solving by hand a problem you don't have to solve manually.&lt;/p&gt;

&lt;p&gt;The asymmetry that matters is on the upgrade path. Going from no-workspace to workspace is a weekend. Going from workspace-with-decayed-&lt;code&gt;CLAUDE.md&lt;/code&gt;-and-stale-dependency-bullets-in-prose to a queryable graph is a project. The migration tax goes up the longer you wait. "We'll do the graph later" is how teams end up with a context file they don't trust and a dependency model they have to recompute by reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Owen Zanzal's deeper principle from the closing of his post is &lt;em&gt;"context beats structure for AI effectiveness."&lt;/em&gt; That's a good line and I agree with it as far as it goes. The structural codebase organisation isn't what determines whether an agent ships safe code; the context the agent has is.&lt;/p&gt;

&lt;p&gt;The version of that principle I'd push for is one step further: structure beats context that has to be rewritten by hand. The kind of context that wins is the kind that updates when the code does, by construction, on every push. The workspace pattern delivers context. The substrate delivers context that doesn't decay.&lt;/p&gt;

&lt;p&gt;I believe if you're going to invest in either, invest in the one that doesn't need a maintainer.&lt;/p&gt;

&lt;p&gt;The architectural bet hasn't changed. Deterministic parsers first. Graph as the durable layer. AI as the layer on top, anchored to verified structure rather than reconstructing it from grep across a workspace that someone has to remember to update.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're running AI coding agents across more than a handful of repositories and finding yourself maintaining a &lt;code&gt;CLAUDE.md&lt;/code&gt; that's drifting from reality, that's the gap. You can build the substrate yourself, the way Mabl did. Or you can &lt;a href="https://app.riftmap.dev/" rel="noopener noreferrer"&gt;start a free scan&lt;/a&gt; and let the parsers find it.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources referenced
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Owen Zanzal, &lt;em&gt;The "Virtual Monorepo" Pattern: How I Gave Claude Code Full-System Context Across 35 Repos&lt;/em&gt; — &lt;a href="https://medium.com/devops-ai/the-virtual-monorepo-pattern-how-i-gave-claude-code-full-system-context-across-35-repos-43b310c97db8" rel="noopener noreferrer"&gt;medium.com/devops-ai&lt;/a&gt;, March 23, 2026&lt;/li&gt;
&lt;li&gt;Rafferty Uy, &lt;em&gt;Repo-of-Repos: Tony's Multi-Repo Workspace for AI Coding Agents&lt;/em&gt; — &lt;a href="https://raffertyuy.com/raztype/repo-of-repos-pattern/" rel="noopener noreferrer"&gt;raffertyuy.com&lt;/a&gt;, May 2, 2026&lt;/li&gt;
&lt;li&gt;Ompragash Viswanathan, Harness, &lt;em&gt;Your Repo Is a Knowledge Graph. You Just Don't Query It Yet.&lt;/em&gt; — &lt;a href="https://www.harness.io/blog/your-repo-is-a-knowledge-graph-you-just-dont-query-it-yet" rel="noopener noreferrer"&gt;harness.io/blog&lt;/a&gt;, April 1, 2026&lt;/li&gt;
&lt;li&gt;Geoff Cooney, mabl, &lt;em&gt;How We Built a System for AI Agents to Ship Real Code Across 75+ Repos&lt;/em&gt; (Part 1 of 2) — &lt;a href="https://www.mabl.com/blog/how-we-built-a-system-for-ai-agents-to-ship-real-code-across-75-repos" rel="noopener noreferrer"&gt;mabl.com&lt;/a&gt;, April 8, 2026&lt;/li&gt;
&lt;li&gt;mabl, &lt;em&gt;How We Built a System for AI Agents to Ship Real Code Across 75+ Repos&lt;/em&gt; (Part 2 of 2) — &lt;a href="https://www.mabl.com/blog/how-we-built-a-system-for-ai-agents-to-ship-real-code-across-75-repos-part-2" rel="noopener noreferrer"&gt;mabl.com&lt;/a&gt;, April 28, 2026&lt;/li&gt;
&lt;li&gt;Engineering at Meta, &lt;em&gt;How Meta used AI to map tribal knowledge in large-scale data pipelines&lt;/em&gt; — &lt;a href="https://engineering.fb.com/2026/04/06/developer-tools/how-meta-used-ai-to-map-tribal-knowledge-in-large-scale-data-pipelines/" rel="noopener noreferrer"&gt;engineering.fb.com&lt;/a&gt;, April 6, 2026&lt;/li&gt;
&lt;li&gt;Gloaguen et al., ETH Zurich and LogicStar.ai, &lt;em&gt;Do Context Files Help Coding Agents?&lt;/em&gt; — &lt;a href="https://arxiv.org/abs/2602.11988" rel="noopener noreferrer"&gt;arxiv.org/abs/2602.11988&lt;/a&gt;, February 2026&lt;/li&gt;
&lt;li&gt;Signadot, &lt;em&gt;Coding Agents Are Only as Good as the Signals You Feed Them&lt;/em&gt; — &lt;a href="https://thenewstack.io/coding-agents-feedback-signals/" rel="noopener noreferrer"&gt;thenewstack.io&lt;/a&gt;, April 2026&lt;/li&gt;
&lt;li&gt;Riftmap, &lt;em&gt;AI coding agents need cross-repo context&lt;/em&gt; — &lt;a href="https://riftmap.dev/blog/ai-coding-agents-need-cross-repo-context/" rel="noopener noreferrer"&gt;riftmap.dev/blog&lt;/a&gt;, May 12, 2026&lt;/li&gt;
&lt;li&gt;Riftmap, &lt;em&gt;Meta needed 50+ AI agents to map their tribal knowledge&lt;/em&gt; — &lt;a href="https://riftmap.dev/blog/meta-tribal-knowledge-engine-build-the-graph-first/" rel="noopener noreferrer"&gt;riftmap.dev/blog&lt;/a&gt;, May 8, 2026&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Appendix: structured summary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; Two patterns have emerged for giving AI coding agents context across multiple repositories. The workspace pattern (Owen Zanzal, Rafferty Uy) bundles repos into a local workspace and hands the agent a hand-written system map. The substrate pattern (Mabl, Meta, Harness, Riftmap) builds a queryable cross-repo dependency graph derived from source code. Both work at small scale. Only the substrate survives the transition past ~100 repos, because the workspace pattern's load-bearing assumptions (workspace fits the context window, grep is cheap, hand-written context stays current) all break at the same point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontier model context windows top out around 1M tokens; 200 mid-sized service repos do not fit, and the next generation of models will not close the gap because organisations add repos faster than context lengths grow.&lt;/li&gt;
&lt;li&gt;Meta's published data: graph lookup for "what depends on X" costs ~200 tokens; the same answered by exploration costs ~6,000. A 30x architecture-not-model gap.&lt;/li&gt;
&lt;li&gt;Gloaguen et al. (arXiv:2602.11988): hand-written context files give +4% agent success rate at +19% cost. LLM-generated context files give -3% at +20% cost. Context files do not provide effective overviews.&lt;/li&gt;
&lt;li&gt;Stripe Minions (1,300+ merged PRs/week, hundreds of millions of LOC): even with a real monorepo, Stripe uses directory-scoped rule files because a global context dump &lt;em&gt;"would overflow any model's window."&lt;/em&gt; The Toolshed MCP server exposes ~500 tools to agents.&lt;/li&gt;
&lt;li&gt;Harness, mabl, Meta, Riftmap (and adjacent: Augment, Depwire, Modulus) have all named the queryable dependency graph as a substrate primitive in the past six weeks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architectural takeaway:&lt;/strong&gt; The workspace pattern is a stopgap with a known maintenance ceiling between 50 and 100 repos. The substrate is what it grows into. Owen Zanzal's "Where This Pattern Can Go" section names auto-generated dependency graphs from import analysis, Terraform trees, and event topic mappings as the next step; the substrate is exactly that, exposed as a queryable graph rather than as bullets in prose. The migration tax goes up the longer you wait, so substrate-first is the correct call for any team above 100 repos and any team that expects to be there within a year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; Platform engineers, DevOps leads, and engineering managers running AI coding agents across more than a handful of repositories, especially those weighing whether to invest in a virtual-monorepo workspace or in a queryable dependency substrate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://riftmap.dev/blog/meta-tribal-knowledge-engine-build-the-graph-first/" rel="noopener noreferrer"&gt;Meta needed 50+ AI agents to map their tribal knowledge&lt;/a&gt; — the durable piece of Meta's stack is the part you can build today.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://riftmap.dev/blog/ai-coding-agents-need-cross-repo-context/" rel="noopener noreferrer"&gt;AI coding agents need cross-repo context&lt;/a&gt; — three teams shipped the same diagnosis in two weeks. Two built the dependency graph substrate. One built around it.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://riftmap.dev/blog/ai-doesnt-understand-blast-radius/" rel="noopener noreferrer"&gt;AI Doesn't Understand Blast Radius&lt;/a&gt; — why AI coding tools optimise for local correctness while production breaks at the cross-repo graph they can't see.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>crossrepocontext</category>
      <category>platformengineering</category>
      <category>aicodingagents</category>
      <category>virtualmonorepo</category>
    </item>
    <item>
      <title>AI coding agents need cross-repo context. The teams running them at scale are already building it themselves."</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Sun, 10 May 2026 22:00:00 +0000</pubDate>
      <link>https://dev.to/danielwe/ai-coding-agents-need-cross-repo-context-the-teams-running-them-at-scale-are-already-building-it-ek0</link>
      <guid>https://dev.to/danielwe/ai-coding-agents-need-cross-repo-context-the-teams-running-them-at-scale-are-already-building-it-ek0</guid>
      <description>&lt;p&gt;Three weeks ago, the Cortex 2026 Engineering in the Age of AI Benchmark put incidents per pull request up 23.5% and change failure rates up roughly 30% since AI adoption accelerated. I &lt;a href="https://riftmap.dev/blog/ai-doesnt-understand-blast-radius/" rel="noopener noreferrer"&gt;wrote about that data and what it means for blast radius&lt;/a&gt; shortly after it landed.&lt;/p&gt;

&lt;p&gt;What I underestimated at the time was how fast the language was going to shift. The phrase that's settled into the conversation since isn't "blast radius" or "service catalog." It's &lt;a href="https://riftmap.dev/what-is-cross-repo-dependency-mapping/" rel="noopener noreferrer"&gt;&lt;em&gt;cross-repo context&lt;/em&gt;&lt;/a&gt;. And it's almost always being used in the same sentence as "AI coding agents." The reason becomes obvious once you read what teams operating AI coding agents at scale are publishing right now. They're not writing about AI making them faster. They're writing about the infrastructure they had to build so AI could ship safely across more than one repo at a time.&lt;/p&gt;

&lt;p&gt;Three teams have published, in the last six weeks, the same diagnosis with three different solutions. The shape of what they're converging on is more interesting than any single one of them, and it has direct implications for how the rest of us should be thinking about agent runtime infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What three teams just shipped
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Neilos (&lt;a class="mentioned-user" href="https://dev.to/neil_agentic"&gt;@neil_agentic&lt;/a&gt; on dev.to), March 27.&lt;/strong&gt; A solo founder running 15+ repositories across Go, Rust, TypeScript, Python, and C++, coordinating ten specialised Claude Code agents through Telegram. His post is &lt;a href="https://dev.to/neil_agentic/how-i-manage-15-repos-with-claude-code-without-losing-my-mind-2ood"&gt;How I Manage 15+ Repos with Claude Code (Without Losing My Mind)&lt;/a&gt;. The diagnostic line is direct: you end up "copy-pasting context between sessions, manually tracking which PR depends on which, and babysitting agents that can't see the full picture."&lt;/p&gt;

&lt;p&gt;His solution, called &lt;code&gt;ttal&lt;/code&gt;, separates "read and explore" from "write." A lightweight exploration agent can read across any repository to gather context. Worker agents only ever write to one repository at a time, in isolated git worktrees. A manager agent holds the cross-repo plan in its head and routes work to the right repo at the right time. There's no dependency graph as a data structure. The cross-repo coordination lives in the manager agent's reasoning and a project registry that maps names to file paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mabl, April 8.&lt;/strong&gt; A team of 25 engineers managing 100+ repositories, pushing 200+ pull requests and 40+ production releases a month before AI acceleration. By February 2026, 39% of their commits were AI-assisted, 60% in infrastructure repos. Their post, &lt;a href="https://www.mabl.com/blog/how-we-built-a-system-for-ai-agents-to-ship-real-code-across-75-repos" rel="noopener noreferrer"&gt;How We Built a System for AI Agents to Ship Real Code Across 75+ Repos&lt;/a&gt;, describes a four-layer architecture. The layer that matters here is what they call "Cross Repo Base."&lt;/p&gt;

&lt;p&gt;Inside Cross Repo Base sits a "Repo Coordination Graph": 850+ lines of structured registry covering 79 repositories with detailed dependency graphs, Pub/Sub topic maps, database table ownership, and prescribed release ordering. When an agent works on a cross-repo feature, it queries this graph to determine which repos need updates, what the merge order should be (upstream libraries before consumers), and which reviewers to tag based on CODEOWNERS and dependency chains. They report that without this layer, "every agent invocation required a human to provide context." With it, "context drift dropped from ~40% of our failures to &amp;lt;5%."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meta, April 6.&lt;/strong&gt; I wrote about &lt;a href="https://riftmap.dev/blog/meta-tribal-knowledge-engine-build-the-graph-first/" rel="noopener noreferrer"&gt;the Meta tribal knowledge engine post&lt;/a&gt; earlier this week, so I'll keep this short. Meta built a multi-stage AI pipeline with 50+ specialised agents to map tribal knowledge across one of their large data pipelines. Buried near the end is the line that matters: "we generated a cross-repo dependency index and data flow maps showing how changes propagate across repositories. This turns 'What depends on X?' from a multi-file exploration (~6000 tokens) into a single graph lookup (~200 tokens)."&lt;/p&gt;

&lt;p&gt;A 30x token reduction for one of the most common questions an agent asks during planning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the same, what's different
&lt;/h2&gt;

&lt;p&gt;The pain is identical. All three describe agents shipping locally-correct code that breaks consumers the agent didn't know existed. Mabl frames it as context drift. Meta frames it as tribal knowledge. Neilos frames it as agents who "can't see the full picture." The phenomenon is one phenomenon: the dependency graph that determines whether a change is safe lives outside any single repository's boundary, which means it lives outside the agent's context window.&lt;/p&gt;

&lt;p&gt;The solutions diverge in a way that's instructive.&lt;/p&gt;

&lt;p&gt;Mabl and Meta both built dependency graphs as a queryable substrate. Mabl's lives as a structured 850-line registry that agents consult at planning time. Meta's lives as a parser-derived graph index that agents call as a tool. The shapes are slightly different but the operating model is the same. Deterministic structure. Refreshed automatically (Mabl from registry updates and CODEOWNERS, Meta from re-parsing source). Queryable as a single lookup rather than a multi-file exploration. The graph exists separately from the agents and outlasts any single agent session.&lt;/p&gt;

&lt;p&gt;Neil's &lt;code&gt;ttal&lt;/code&gt; takes a different approach. The cross-repo information lives in the manager agent's working memory plus a TOML file mapping project names to file system paths, with the architecture isolating writes to one repo at a time and keeping the coordination model in the manager's prompt. This is well-fitted to a solo operator with ten agents and fifteen repositories. At Mabl's scale, the same problem reshapes the solution. Once you have 25 engineers across 79 repos, no single agent's context window is big enough to hold the coordination model and no manual registry stays current at that throughput, so the dependency model has to live outside any agent.&lt;/p&gt;

&lt;p&gt;What I read into this is that the dependency graph as a queryable substrate isn't a stylistic choice. It's what falls out of the problem when you scale it. Three teams hit the same wall. Two engineered the substrate explicitly. One handled it through coordination, which is the right call at solo scale. Once the team running the agents grows past that, the substrate is what you build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this should be a primitive, not something every team rebuilds
&lt;/h2&gt;

&lt;p&gt;The Mabl post lists this layer almost matter-of-factly. The Meta post mentions it in one paragraph after sections on agent swarms and critic passes. Both teams treated it as table stakes for what they were actually trying to do. And in both cases, the structural layer is the cheapest, most reusable, most durable part of what they built. Mabl's CLAUDE.md per repo decays without a maintainer. Meta's LLM-generated context files decay without a self-refreshing critic swarm. The dependency graph doesn't decay, because parsers run on every push.&lt;/p&gt;

&lt;p&gt;Which raises the question for every team that isn't Meta or Mabl: do you really want to be writing your own 850-line &lt;code&gt;repo-coordination-graph.md&lt;/code&gt; by hand? And keeping it current as repos are added, archived, renamed, and refactored? At a 50-engineer org, this is a fully-loaded engineer-month of upfront work and an ongoing maintenance tax that nobody's job description includes. At a 200-engineer org, it's a dedicated platform team responsibility competing with everything else they could be doing.&lt;/p&gt;

&lt;p&gt;This primitive belongs above any single team. The graph itself is structural, derived from source code, and identical in shape across orgs. The thing you actually want from it (which repositories import from which, who consumes which artifact, what's in the transitive blast radius of a change) is the same question whether you have 50 repos or 500. There is no good reason for every multi-repo org running AI coding agents to be writing their own version of this from scratch. It's a substrate, not a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like as an API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://riftmap.dev/" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; auto-discovers the cross-repo dependency graph from a GitHub or GitLab org via a single read-only token, parsing manifests across ten ecosystems: Terraform, Docker, Helm, Kubernetes, Kustomize, Go, npm, Python, Ansible, and GitHub Actions / GitLab CI (and many more to be added in the future). The graph is queryable through an HTTP API designed to be called by AI agents during planning.&lt;/p&gt;

&lt;p&gt;The natural agent flow is three calls. Given an agent working in a clone of &lt;code&gt;github.com/myorg/payments-api&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Resolve the local clone URL to a Riftmap repo&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RIFTMAP_BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/repositories/lookup?url=https://github.com/myorg/payments-api"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$RIFTMAP_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 2. Hydrate context in one round-trip:&lt;/span&gt;
&lt;span class="c"&gt;#    repo + dependencies + dependents + artifacts + freshness fields&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RIFTMAP_BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/repositories/&lt;/span&gt;&lt;span class="nv"&gt;$REPO_ID&lt;/span&gt;&lt;span class="s2"&gt;/context"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$RIFTMAP_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 3. Only when actually needed: transitive blast radius&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RIFTMAP_BASE_URL&lt;/span&gt;&lt;span class="s2"&gt;/repositories/&lt;/span&gt;&lt;span class="nv"&gt;$REPO_ID&lt;/span&gt;&lt;span class="s2"&gt;/impact?max_depth=3&amp;amp;min_confidence=0.8"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$RIFTMAP_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three calls to answer the questions Mabl's Repo Coordination Graph and Meta's dependency index were built to answer. The full schema is published at &lt;code&gt;https://app.riftmap.dev/openapi.json&lt;/code&gt;. Fetch it once, hand it to your agent, and the rest is typed &lt;code&gt;httpx&lt;/code&gt; or &lt;code&gt;fetch&lt;/code&gt; calls. Python and TypeScript versions of these examples are in the &lt;a href="https://docs.riftmap.dev/agents/examples" rel="noopener noreferrer"&gt;agent integration docs&lt;/a&gt;, along with the freshness contract every response carries (&lt;code&gt;last_scanned_at&lt;/code&gt;, &lt;code&gt;last_commit_sha&lt;/code&gt;, &lt;code&gt;last_activity_at&lt;/code&gt;, &lt;code&gt;archived&lt;/code&gt;) so an agent can tell when the graph is stale and either warn the user or trigger a rescan.&lt;/p&gt;

&lt;p&gt;This is the substrate today, exposed as an HTTP API. An MCP server and CLI are next, in that order, so any Claude Code, Cursor, or Cline agent can drop them in without orchestrating HTTP calls. The order and current state are tracked on the &lt;a href="https://docs.riftmap.dev/agents/mcp-cli-roadmap" rel="noopener noreferrer"&gt;public roadmap page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interface outlasts the model
&lt;/h2&gt;

&lt;p&gt;The reasonable objection at this point is: won't models eventually get good enough at cross-repo reasoning that none of this is necessary? Won't Claude 5 or whatever comes next just figure it out?&lt;/p&gt;

&lt;p&gt;I don't think the question goes the way the objection assumes. Even granting that models keep getting better at cross-repo reasoning, three things stay true.&lt;/p&gt;

&lt;p&gt;First, parsing the dependency manifests of a multi-repo organisation from inside a model's context window is doing a job a parser could do deterministically and cheaply. Even if a frontier model does it perfectly, it's still paying token cost on every invocation for what is structurally a precomputed lookup. Meta's 30x token reduction isn't a model-quality problem. It's an architecture problem. A graph index will always be cheaper to query than to reconstruct.&lt;/p&gt;

&lt;p&gt;Second, models change every six months. The dependency graph doesn't. A queryable graph behind a stable API has the same shape whether the agent calling it is Claude 4.7, Claude 5, or whatever is running in 2027. The interface outlasts the model. Investing in the substrate is on a different time horizon to investing in any specific agent's context strategy.&lt;/p&gt;

&lt;p&gt;Third, the freshness problem is structural. A model trained today does not know about repositories your team created last week. A graph derived from last night's scan does. As long as the rate of change in your repository graph exceeds the rate at which models retrain, that gap exists, and the substrate is how you close it.&lt;/p&gt;

&lt;p&gt;What models getting better at cross-repo reasoning probably does change is what gets layered on top of the graph. Better semantic reasoning over verified structure. Better automated PR review against known consumer impact. Better natural-language interfaces to the graph itself. Those are real things, and they'll matter. They all sit above the substrate, not in place of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The three posts I started this with are the same shape of evidence I built &lt;a href="https://riftmap.dev/" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt;, just with bigger names attached. Engineers willing to spend real engineering time building a thing means the thing is needed. When three independent teams publish the same diagnosis in two weeks, the pattern is the pattern.&lt;/p&gt;

&lt;p&gt;The architectural bet hasn't changed. Deterministic parsers first. Graph as the durable layer. AI as the layer on top, anchored to verified structure rather than reconstructing it from scratch. What's changed is who's confirming it publicly, and how much louder the confirmation has gotten.&lt;/p&gt;

&lt;p&gt;If you're running AI coding agents across more than five or six repositories and finding yourself writing prompts that include cross-repo dependency information by hand, or reviewing PRs where the agent shipped a change that broke a consumer it didn't know existed, that's the gap. You can build the substrate yourself the way Mabl did, on the trajectory the Mabl post describes. Or you can &lt;a href="https://app.riftmap.dev" rel="noopener noreferrer"&gt;start a free scan&lt;/a&gt; and skip the 850-line registry. Either way, the substrate is the substrate. It's the part that doesn't lie.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources referenced
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Geoff Cooney, mabl, &lt;em&gt;How We Built a System for AI Agents to Ship Real Code Across 75+ Repos [Part 1 of 2]&lt;/em&gt; — &lt;a href="https://www.mabl.com/blog/how-we-built-a-system-for-ai-agents-to-ship-real-code-across-75-repos" rel="noopener noreferrer"&gt;mabl.com&lt;/a&gt;, April 8, 2026&lt;/li&gt;
&lt;li&gt;Engineering at Meta, &lt;em&gt;How Meta used AI to map tribal knowledge in large-scale data pipelines&lt;/em&gt; — &lt;a href="https://engineering.fb.com/2026/04/06/developer-tools/how-meta-used-ai-to-map-tribal-knowledge-in-large-scale-data-pipelines/" rel="noopener noreferrer"&gt;engineering.fb.com&lt;/a&gt;, April 6, 2026&lt;/li&gt;
&lt;li&gt;Neilos (&lt;a class="mentioned-user" href="https://dev.to/neil_agentic"&gt;@neil_agentic&lt;/a&gt;), &lt;em&gt;How I Manage 15+ Repos with Claude Code (Without Losing My Mind)&lt;/em&gt; — &lt;a href="https://dev.to/neil_agentic/how-i-manage-15-repos-with-claude-code-without-losing-my-mind-2ood"&gt;dev.to/neil_agentic&lt;/a&gt;, March 27, 2026&lt;/li&gt;
&lt;li&gt;Cortex, &lt;em&gt;Engineering in the Age of AI: 2026 Benchmark Report&lt;/em&gt; — &lt;a href="https://www.cortex.io/report/engineering-in-the-age-of-ai-2026-benchmark-report" rel="noopener noreferrer"&gt;cortex.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Riftmap, &lt;em&gt;AI Doesn't Understand Blast Radius&lt;/em&gt; — &lt;a href="https://riftmap.dev/blog/ai-doesnt-understand-blast-radius/" rel="noopener noreferrer"&gt;riftmap.dev/blog&lt;/a&gt;, April 19, 2026&lt;/li&gt;
&lt;li&gt;Riftmap, &lt;em&gt;Meta needed 50+ AI agents to map their tribal knowledge&lt;/em&gt; — &lt;a href="https://riftmap.dev/blog/meta-tribal-knowledge-engine-build-the-graph-first/" rel="noopener noreferrer"&gt;riftmap.dev/blog&lt;/a&gt;, May 8, 2026&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Appendix: structured summary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; AI coding agents at scale need a queryable cross-repo dependency graph as runtime infrastructure. Three independent teams have just published the same diagnosis. Two built the substrate. The third handled it through coordination, which works at solo scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Neilos (&lt;code&gt;ttal&lt;/code&gt;, Mar 27): solo operator, 15+ repos, 10 specialised agents. Diagnostic: "manually tracking which PR depends on which." Solution: coordination layer with manager/worker planes, fitted to solo scale.&lt;/li&gt;
&lt;li&gt;Mabl (Apr 8): 25 engineers, 100+ repos, 39% of commits AI-assisted by Feb 2026. Solution: 850-line Repo Coordination Graph spanning 79 repositories, queried by agents at planning time. Reduced context drift from ~40% to &amp;lt;5% of failures.&lt;/li&gt;
&lt;li&gt;Meta (Apr 6): cross-repo dependency index reduces "what depends on X" from ~6,000 tokens to ~200 tokens, a 30x efficiency gain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Architectural takeaway:&lt;/strong&gt; The dependency graph as a queryable substrate isn't a stylistic choice. It's what falls out of the problem when scale forces externalisation of cross-repo coordination. The graph itself is structural, parser-derived, and identical in shape across orgs, which makes it a primitive that should live above any single team rather than be re-implemented inside each one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Riftmap exposes today:&lt;/strong&gt; auto-discovered graph across 10 ecosystems via a single read-only org token. HTTP API with &lt;code&gt;/repositories/lookup&lt;/code&gt;, &lt;code&gt;/repositories/{id}/context&lt;/code&gt;, &lt;code&gt;/repositories/{id}/impact&lt;/code&gt;. Static OpenAPI schema at &lt;code&gt;https://app.riftmap.dev/openapi.json&lt;/code&gt;. MCP server and CLI on the &lt;a href="https://docs.riftmap.dev/agents/mcp-cli-roadmap" rel="noopener noreferrer"&gt;public roadmap&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audience:&lt;/strong&gt; Platform engineers, DevOps leads, and engineering managers running AI coding agents across more than a handful of repositories.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>crossrepocontext</category>
      <category>platformengineering</category>
      <category>ployrepo</category>
    </item>
    <item>
      <title>Meta needed 50+ AI agents to map their tribal knowledge. The most durable piece of their stack is the part you can build today.</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Fri, 08 May 2026 18:26:38 +0000</pubDate>
      <link>https://dev.to/danielwe/meta-needed-50-ai-agents-to-map-their-tribal-knowledge-the-most-durable-piece-of-their-stack-is-li7</link>
      <guid>https://dev.to/danielwe/meta-needed-50-ai-agents-to-map-their-tribal-knowledge-the-most-durable-piece-of-their-stack-is-li7</guid>
      <description>&lt;p&gt;In early April, Meta's engineering team published &lt;a href="https://engineering.fb.com/2026/04/06/developer-tools/how-meta-used-ai-to-map-tribal-knowledge-in-large-scale-data-pipelines/" rel="noopener noreferrer"&gt;a piece&lt;/a&gt; describing how they used a swarm of 50+ specialised AI agents to map tribal knowledge across one of their large data pipelines: four repositories, three languages, 4,100+ files. The post has been making the rounds in platform engineering circles for good reason. It names a problem most polyrepo organisations live with quietly, and shows what an industrial-scale solution looks like.&lt;/p&gt;

&lt;p&gt;I read it twice. I think there are actually two posts inside it. One is a complex AI orchestration story about agent swarms, critic passes, and self-refreshing context files. That's the one most takes have focused on. The other is a quieter architectural argument that gets one paragraph and is the more useful piece for everyone who isn't Meta.&lt;/p&gt;

&lt;p&gt;This is about the second one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Meta actually built
&lt;/h2&gt;

&lt;p&gt;Meta's pipeline is config-as-code: Python configurations, C++ services, Hack automation scripts working together across multiple repositories. A single data field onboarding touches six subsystems that must stay in sync. Pointing AI coding agents at this codebase produced predictable results. The agents would compile but be subtly wrong. They didn't know which "deprecated" enum values must never be removed because of serialisation compatibility. They didn't know that two configuration modes use different field names for the same operation, and that swapping them produces silent incorrect output. None of this was documented anywhere outside engineers' heads.&lt;/p&gt;

&lt;p&gt;Meta's response was a multi-stage AI pipeline they call a "pre-compute engine." Two explorer agents map the codebase. Eleven module analysts read every file and answer five questions per module. Two writers produce 25–35 line context files. Ten or more critic passes do three rounds of independent quality review. Four fixer agents apply corrections. The output is 59 concise context files covering 100% of code modules, up from ~5% before. Every few weeks, the system self-refreshes: validates file paths, detects coverage gaps, re-runs critics, auto-fixes stale references.&lt;/p&gt;

&lt;p&gt;The results, on a six-task evaluation, are real: roughly 40% fewer tool calls per task. Workflow guidance that used to take two days of asking engineers now takes 30 minutes.&lt;/p&gt;

&lt;p&gt;I'm not going to argue with any of that. It's clearly working at Meta. But it works at Meta partly because Meta has a platform team large enough to build, run, and continuously maintain a self-refreshing critic swarm. That's not a small footnote. It's the whole reason the system stays trustworthy over time, and it's the part that doesn't transfer to a 100-engineer company.&lt;/p&gt;

&lt;h2&gt;
  
  
  The argument hidden in the article
&lt;/h2&gt;

&lt;p&gt;Buried near the end of Meta's post, under the heading "What We Built," there's one paragraph that did more for me than the rest of the article combined. I'll quote it because it's the most striking number in the entire piece:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Beyond individual contextual files, we generated a cross-repo dependency index and data flow maps showing how changes propagate across repositories. This turns "What depends on X?" from a multi-file exploration (~6000 tokens) into a single graph lookup (~200 tokens).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a 30x reduction in token cost for one of the most common questions agents and engineers ask: if I change this thing, what else breaks?&lt;/p&gt;

&lt;p&gt;Meta describes the dependency index almost as an aside, after the long sections on the agent swarm. But it's structurally different from everything else in their stack, and the difference matters.&lt;/p&gt;

&lt;p&gt;The 59 context files are LLM-generated artifacts. They have to be quality-gated by critic agents, validated for hallucinations, and refreshed periodically because code drift makes them stale. That's why the self-refresh mechanism exists, and why it requires a platform team to run. Context that decays is worse than no context, by Meta's own admission.&lt;/p&gt;

&lt;p&gt;The cross-repo dependency index is different. It's a structured graph derived from parsing actual source files. It doesn't need critic agents to validate quality because parsers are deterministic: the same input produces the same output. It doesn't decay between scans because it's not a snapshot of human-readable knowledge. It's a queryable index of what the code actually says. You re-scan on every push, and it's fresh by construction.&lt;/p&gt;

&lt;p&gt;Two layers, two operating models. One is volatile and needs a maintenance team. The other is durable and maintains itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The paper Meta cited deserves a closer read
&lt;/h2&gt;

&lt;p&gt;Meta acknowledges, briefly, &lt;a href="https://arxiv.org/abs/2602.11988" rel="noopener noreferrer"&gt;a recent paper&lt;/a&gt; from ETH Zurich and LogicStar.ai (Gloaguen et al., February 2026) that found AI-generated context files reduced agent success rates on open-source Python repositories. Meta's response is reasonable: those repositories are already well-represented in model training data, so context files are redundant noise there. Their own codebase, with proprietary tribal knowledge, is the opposite case.&lt;/p&gt;

&lt;p&gt;That's a fair distinction, but the paper's actual findings are richer than Meta's framing suggests, and worth engaging with directly.&lt;/p&gt;

&lt;p&gt;Across four coding agents (Claude Code, Codex, Qwen Code, and a Codex variant) on 138 niche-repository tasks plus SWE-bench Lite, the paper found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LLM-generated context files marginally hurt performance&lt;/strong&gt; (-3% on average) while increasing inference cost by over 20%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer-written context files marginally helped&lt;/strong&gt; (+4% on average), still at +19% cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context files do not provide effective overviews.&lt;/strong&gt; This is a direct subsection title in the paper. Agents took the same number of steps to find relevant files whether a context file was present or not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context files lead to more exploration and testing, not less.&lt;/strong&gt; Agents read more, search more, and test more when given a context file. They follow the instructions, but the instructions make tasks harder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stronger models don't generate better context files.&lt;/strong&gt; The expected scaling story doesn't hold.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The paper's own conclusion: context files have only marginal effect on agent behaviour, and are likely only desirable when manually written.&lt;/p&gt;

&lt;p&gt;The honest read of this is not "context files don't work." It's that even in the best case, they give a +4% improvement at +20% cost, and only when humans write them. For LLM-generated context files, which is what Meta's swarm produces, the average effect is negative.&lt;/p&gt;

&lt;p&gt;A caveat in the paper's favour: the evaluation is Python-only. The authors note that niche languages with less training data representation might benefit more from context files. Meta's codebase includes Hack and C++ alongside Python. So Meta sits exactly at the edge of what the paper studies. Their case for context files is defensible, but it isn't cleanly contradicted by the paper either. The honest position is that the jury is still out on whether Meta's quality-gated, critic-validated context files clear the bar that naive &lt;code&gt;/init&lt;/code&gt; commands don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  A different decomposition of the same problem
&lt;/h2&gt;

&lt;p&gt;Meta's five-question framework for module analysts is the most portable idea in their post. The questions are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What does this module configure?&lt;/li&gt;
&lt;li&gt;What are the common modification patterns?&lt;/li&gt;
&lt;li&gt;What are the non-obvious patterns that cause build failures?&lt;/li&gt;
&lt;li&gt;What are the cross-module dependencies?&lt;/li&gt;
&lt;li&gt;What tribal knowledge is buried in code comments?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Look at these and you'll notice they decompose into two categories. Question 4 is a structural question. The answer exists in the source code itself, in import statements, configuration references, module declarations, and infrastructure-as-code definitions. Parsers can extract it deterministically. Questions 1, 2, 3, and 5 are semantic. They require interpretation: what is the intent behind this code, what patterns recur across changes, what isn't written down. These are LLM-shaped questions, and they're exactly the kind the ETH paper says context files struggle with.&lt;/p&gt;

&lt;p&gt;This decomposition matters because it tells you where to invest first. The structural layer is cheap to build, deterministic, and stays fresh by construction. The semantic layer is expensive to build, requires quality gates, and decays without maintenance. If you have unlimited platform resources, you build both. If you have a small team, you build the structural layer first, because it gives you most of the leverage at a fraction of the cost, and because the semantic layer becomes more useful when it can anchor to verified structure.&lt;/p&gt;

&lt;p&gt;There's a deeper architectural argument here too. When an LLM generates context about a codebase from scratch, it's discovering structure and writing prose about it. Hallucination lives in that gap. When an LLM generates context on top of a verified dependency graph, it's annotating known structure with semantic claims. The structural ground truth constrains what the LLM can plausibly say. That's a more reliable architecture, and it's only available to you if you've built the graph first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for teams without a platform team
&lt;/h2&gt;

&lt;p&gt;If you have 50 to 500 repositories and a small platform team or no platform team at all, here's what I'd take from Meta's post.&lt;/p&gt;

&lt;p&gt;You can't afford a self-refreshing critic swarm with 50+ specialised agents. You probably don't need one. The piece of Meta's stack with the highest leverage per dollar is the cross-repo dependency index, and that's the piece you can have today without building any of Meta's orchestration layer. It's also the piece that gives you the freshness signal Meta argues is non-negotiable for trust ("context that decays is worse than no context"), because parsers run on every push and you can timestamp every edge in the graph.&lt;/p&gt;

&lt;p&gt;The order matters. Build the structural layer first. Make "what depends on X" a 200-token graph lookup, not a 6,000-token exploration. Make blast-radius analysis a query, not an interview. Then, if you still want LLM-generated context on top, do it knowing the graph constrains what the LLM can say and the quality bar gets clearer.&lt;/p&gt;

&lt;p&gt;Meta's post is genuinely useful evidence. It validates that tribal knowledge is the bottleneck for AI agents in proprietary codebases. It validates that cross-repo dependency mapping is the cheapest, most reusable primitive in any solution. It also, accidentally, makes a strong case that the rest of the stack is optional for everyone who can't afford to maintain it.&lt;/p&gt;

&lt;p&gt;The graph is the substrate that doesn't lie. Everything else gets layered on top.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is the architectural bet I made when I started building &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; a year ago: deterministic parsers first, graph as the durable layer, AI as a future enhancement that anchors to verified structure. If you're thinking about cross-repo dependency mapping in your own organisation, the &lt;a href="https://riftmap.dev/what-is-cross-repo-dependency-mapping/" rel="noopener noreferrer"&gt;glossary page&lt;/a&gt; is a good starting point.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix: structured summary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claim:&lt;/strong&gt; Meta's tribal knowledge engine is two systems. A self-refreshing AI swarm that needs platform-team maintenance, and a cross-repo dependency graph mentioned almost as an aside. The graph is the durable substrate. The swarm is the volatile layer on top.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence from Meta:&lt;/strong&gt; Cross-repo dependency index reduces "what depends on X" from ~6,000 tokens to ~200 tokens, a 30x token efficiency gain. Self-refresh required for context files because "context that decays is worse than no context."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence from Gloaguen et al. (arXiv:2602.11988, Feb 2026):&lt;/strong&gt; LLM-generated context files reduce agent success rates by ~3% on average at +20% inference cost. Human-written context files improve success rates by ~4% at +19% cost. Context files do not provide effective overviews. Stronger models do not generate better context files. Evaluation is Python-only; niche languages may differ.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural takeaway:&lt;/strong&gt; Meta's five-question framework decomposes into structural (cross-module dependencies, deterministically extractable) and semantic (intent, patterns, undocumented knowledge, LLM-shaped). The structural layer is cheap to build and self-fresh. The semantic layer is expensive and decays. Build the structural layer first. The semantic layer becomes more useful when it anchors to verified structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audience this is written for:&lt;/strong&gt; Platform engineers, DevOps leads, and SRE teams at organisations with 50–500 repositories, polyrepo architectures, and mixed infrastructure-as-code. Especially those without a Meta-scale platform team.&lt;/p&gt;

</description>
      <category>platformengineering</category>
      <category>tribalknowledge</category>
      <category>crossrepodependencies</category>
      <category>aicodingagents</category>
    </item>
    <item>
      <title>How to Find Every Consumer of Your Go Module</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Mon, 20 Apr 2026 22:00:00 +0000</pubDate>
      <link>https://dev.to/danielwe/how-to-find-every-consumer-of-your-go-module-5cbk</link>
      <guid>https://dev.to/danielwe/how-to-find-every-consumer-of-your-go-module-5cbk</guid>
      <description>&lt;p&gt;&lt;em&gt;You maintain a shared Go module. A breaking API change is coming. Which repos across your org import it — and at which version?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You own &lt;code&gt;github.com/company/platform-lib&lt;/code&gt;. Maybe it's your internal observability library that every service imports for structured logging and trace propagation. Maybe it's a shared HTTP middleware package that standardises authentication, rate limiting, and request IDs across all your APIs. Maybe it's a configuration loader that services use to pull settings from your config management system at startup.&lt;/p&gt;

&lt;p&gt;It started as a few shared utilities to avoid copy-pasting. Twenty services adopted it. Then more. Some import it directly and call into it heavily. Some pull it in transitively because another shared library depends on it. Some are on the latest version. Some are three majors behind.&lt;/p&gt;

&lt;p&gt;Now you need to change it. Maybe you're removing a deprecated function that's been generating noise in the godoc for two years. Maybe you're bumping to &lt;code&gt;v2&lt;/code&gt; because you need to restructure the package API in a backwards-incompatible way. Maybe you're changing the signature of a middleware constructor to accept an options struct instead of positional arguments.&lt;/p&gt;

&lt;p&gt;The question is the same one that runs through &lt;a href="https://riftmap.dev/blog/series/find-every-consumer/" rel="noopener noreferrer"&gt;every post in this series&lt;/a&gt;: &lt;strong&gt;which repos across the org import this module, and at which version?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here is what Go module consumption looks like in practice. Your platform team publishes a module, and other repos declare it in their &lt;code&gt;go.mod&lt;/code&gt;:&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="c"&gt;// service-a/go.mod&lt;/span&gt;
&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="m"&gt;1.22&lt;/span&gt;

&lt;span class="n"&gt;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="m"&gt;.8.3&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="m"&gt;.1.0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some repos import the module directly and use it throughout their code. Others pull it in as an indirect dependency — because a library they depend on imports your module — and Go records it with an &lt;code&gt;// indirect&lt;/code&gt; comment:&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;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="m"&gt;.8.3&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;some&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="m"&gt;.4.1&lt;/span&gt; &lt;span class="c"&gt;// indirect&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some repos are pinned to a specific release tag. Some reference a pseudo-version pointing to a specific commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;github.com/company/platform-lib v0.0.0-20260301153042-a1b2c3d4e5f6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some have overridden the module source entirely with a &lt;code&gt;replace&lt;/code&gt; directive — pointing to a local checkout or a patched fork:&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;replace&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;../&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of these represent consumption of your module. None of them look the same. And right now, they live in different repos, owned by different teams, that you probably can't enumerate off the top of your head.&lt;/p&gt;

&lt;h2&gt;
  
  
  What existing tools give you (and where they stop)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  pkg.go.dev
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pkg.go.dev&lt;/code&gt; is the official Go package documentation and discovery site. For public modules, it indexes which packages import a given package and shows an importer list. This is the closest existing tool to "who uses this?"&lt;/p&gt;

&lt;p&gt;The limitation is sharp: &lt;code&gt;pkg.go.dev&lt;/code&gt; only indexes public modules fetched through the public Go module proxy at &lt;code&gt;proxy.golang.org&lt;/code&gt;. If your module is hosted on an internal GitLab or GitHub instance, protected by authentication, and routed through a private &lt;code&gt;GOPROXY&lt;/code&gt;, it does not appear in &lt;code&gt;pkg.go.dev&lt;/code&gt; at all. Every consumer in your org is invisible to it.&lt;/p&gt;

&lt;p&gt;For most platform teams managing internal shared libraries, this makes &lt;code&gt;pkg.go.dev&lt;/code&gt; entirely useless for the question at hand. Even for public modules, the importer list only covers packages that &lt;code&gt;pkg.go.dev&lt;/code&gt; has itself indexed — not a comprehensive count of every organisation that imports the module.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;go mod graph&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;go mod graph&lt;/code&gt; command prints the full module dependency graph for the current module — a recursive list of every direct and transitive dependency. It's useful for understanding what a single module pulls in.&lt;/p&gt;

&lt;p&gt;But it only works outward from the current module. There is no &lt;code&gt;go mod reverse-graph&lt;/code&gt;. You'd have to clone every repo in the org, run &lt;code&gt;go mod graph&lt;/code&gt; in each one, collect the output, and invert it yourself — which is exactly the manual trawl the question is trying to avoid. And the moment you finish, the results are stale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Renovate and Dependabot
&lt;/h3&gt;

&lt;p&gt;Both Renovate and Dependabot support Go modules as a first-class ecosystem. They detect &lt;code&gt;require&lt;/code&gt; entries in &lt;code&gt;go.mod&lt;/code&gt; and open pull requests when newer versions are published — which means they implicitly know which repos consume which modules.&lt;/p&gt;

&lt;p&gt;But they don't expose that knowledge as a queryable view. You cannot ask Renovate or Dependabot "show me every repo in the org that imports &lt;code&gt;platform-lib&lt;/code&gt;, and what version each is on." They react to new versions being published. The reverse — who's consuming the current version before you publish a breaking one — is not something they surface.&lt;/p&gt;

&lt;p&gt;Both tools also only cover repos where they've been configured. A team that hasn't set up Go module support in their config, or doesn't use either tool at all, is invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub code search
&lt;/h3&gt;

&lt;p&gt;You can search for your module path across all repos in an org:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;org:my-org "github.com/company/platform-lib"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This finds &lt;code&gt;go.mod&lt;/code&gt; files that contain the module path and gives you a list of repos. For a one-off audit, it's a workable starting point.&lt;/p&gt;

&lt;p&gt;The familiar problems apply: results don't include version numbers without opening each file individually, the search index lags behind recent commits, and it doesn't account for &lt;code&gt;replace&lt;/code&gt; directives. If a repo has replaced your module with a fork or local path, the &lt;code&gt;require&lt;/code&gt; entry still appears — but what's actually being compiled is different. Code search can't tell you that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Athens (GOPROXY)
&lt;/h3&gt;

&lt;p&gt;Athens is a widely-used open-source Go module proxy that organisations run internally for private modules and caching. It serves module downloads and maintains a local cache.&lt;/p&gt;

&lt;p&gt;Athens tracks which modules were fetched and at which version. What it doesn't track is which &lt;em&gt;source repo&lt;/em&gt; triggered the download. A build server fetching &lt;code&gt;platform-lib v1.8.3&lt;/code&gt; is recorded as a download event, but not which team's CI pipeline it was, which service repo it was building, or which &lt;code&gt;go.mod&lt;/code&gt; declared the dependency. Athens is a supply-chain cache, not a consumption graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is harder than it looks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Major version suffixes create distinct module identities.&lt;/strong&gt; Go's module system treats &lt;code&gt;github.com/company/platform-lib&lt;/code&gt; and &lt;code&gt;github.com/company/platform-lib/v2&lt;/code&gt; as completely separate modules. A repo that imports &lt;code&gt;platform-lib/v2&lt;/code&gt; has a different dependency than one that imports &lt;code&gt;platform-lib&lt;/code&gt;. Grepping for &lt;code&gt;platform-lib&lt;/code&gt; finds both — but a tool that tracks module identities needs to recognise that these are different versions of the same thing, and that consumers may be on any combination of &lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;, &lt;code&gt;v3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This also means publishing a new major version doesn't automatically affect v1 consumers. They continue importing the old module path until they explicitly update both the &lt;code&gt;go.mod&lt;/code&gt; entry and all import paths in their source code. Understanding who's still on v1 requires knowing who's importing which module path — and treating those as a unified dependency family.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;replace&lt;/code&gt; directives obscure the real source.&lt;/strong&gt; A &lt;code&gt;replace&lt;/code&gt; directive can substitute your module with a different source — a local path, a fork, or a different module entirely:&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;replace&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;patched&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="m"&gt;.8.3&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;hotfix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the outside, the &lt;code&gt;require&lt;/code&gt; entry still references &lt;code&gt;github.com/company/platform-lib&lt;/code&gt;. A scanner that reads only &lt;code&gt;require&lt;/code&gt; entries sees this as a consumer on v1.8.3. But the code being compiled is coming from a patched fork. The apparent consumer is not actually running your module — and a tool that reports it as one gives you a misleading picture of who's affected by a change to the canonical source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Indirect dependencies inflate the consumer list.&lt;/strong&gt; Go's &lt;code&gt;go.mod&lt;/code&gt; includes both direct and indirect dependencies, with &lt;code&gt;// indirect&lt;/code&gt; annotations on the latter. If your module is a transitive dependency — imported by a library that's imported by a service — it appears in that service's &lt;code&gt;go.mod&lt;/code&gt; even though the service's code never directly calls into yours.&lt;/p&gt;

&lt;p&gt;This matters when reasoning about impact. A service that directly imports your module and calls its functions will break if you change the API. A service that has it only as an indirect dependency may not be affected at all, depending on how much of your interface the intermediate library exposes. Treating all &lt;code&gt;go.mod&lt;/code&gt; entries as equivalent overstates the blast radius; ignoring indirect entries understates it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;go.work&lt;/code&gt; workspaces cross module boundaries.&lt;/strong&gt; Go 1.18 introduced workspace mode, where a &lt;code&gt;go.work&lt;/code&gt; file at the root of a checkout declares multiple local modules as part of a unified workspace. When workspace mode is active, dependencies between modules in the &lt;code&gt;go.work&lt;/code&gt; are resolved locally rather than through &lt;code&gt;go.mod&lt;/code&gt; &lt;code&gt;require&lt;/code&gt; declarations.&lt;/p&gt;

&lt;p&gt;Some consumers of your module may be developing against it in a shared workspace, with no explicit &lt;code&gt;require&lt;/code&gt; entry in their &lt;code&gt;go.mod&lt;/code&gt; during development. A scanner that only reads &lt;code&gt;go.mod&lt;/code&gt; files misses these entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vendor directories shadow the dependency graph.&lt;/strong&gt; Repos that vendor their dependencies with &lt;code&gt;go mod vendor&lt;/code&gt; keep a copy of all transitive dependencies under &lt;code&gt;vendor/&lt;/code&gt;, with canonical version information in &lt;code&gt;vendor/modules.txt&lt;/code&gt;. Some repos modify vendored code directly — technically discouraged, but not uncommon in practice. These modifications break the assumption that a version string in &lt;code&gt;go.mod&lt;/code&gt; corresponds exactly to the canonical upstream source. A consumer may appear to be on v1.8.3 while actually running a locally modified version of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full answer requires
&lt;/h2&gt;

&lt;p&gt;To reliably answer "who imports this module," you need a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans every repo in the org&lt;/strong&gt;, parsing &lt;code&gt;go.mod&lt;/code&gt; files — without requiring opt-in or registration from each team&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles all reference forms&lt;/strong&gt; — tagged versions, pseudo-versions, and &lt;code&gt;replace&lt;/code&gt; directives — and records what module source is actually being resolved, not just what appears in &lt;code&gt;require&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tracks all major version suffixes&lt;/strong&gt; as distinct but related module identities, so you see the full picture across &lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;, &lt;code&gt;v3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distinguishes direct from indirect dependencies&lt;/strong&gt;, so you know which consumers call into your module directly and which pull it in transitively through another library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follows transitive edges&lt;/strong&gt; to show second-order consumers: repos that depend on your module through an intermediate shared library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps results current&lt;/strong&gt; through regular rescans rather than a one-time snapshot&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of the specific problems &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; is built to solve. It connects to your GitHub or GitLab organisation, scans every repo, and parses &lt;code&gt;go.mod&lt;/code&gt; files to extract module dependencies — including all &lt;code&gt;require&lt;/code&gt; entries, &lt;code&gt;replace&lt;/code&gt; directives, and version strings. It resolves module paths across major version suffixes and builds a cross-repo dependency graph you can query by module.&lt;/p&gt;

&lt;p&gt;The result is the same kind of view described throughout this series: before you remove a function, bump a major version, or change a constructor signature, you open the graph, click the module, and see every repo that imports it — their version, whether they're a direct or indirect consumer, and which teams own them. You know who'll break. You know who to notify. You know who's still on v1 and will need a migration path before v2 ships.&lt;/p&gt;

&lt;p&gt;No manual &lt;code&gt;go mod graph&lt;/code&gt; inversions across dozens of repos. No org-wide code search with manual version lookups. No waiting to see whose CI goes red after you merge.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How is your team solving this today?&lt;/strong&gt; I'd genuinely like to know — drop a comment or find me at &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;riftmap.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>go</category>
      <category>modules</category>
      <category>dependencymanagement</category>
    </item>
    <item>
      <title>How to Find Every Consumer of Your Helm Chart</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Sat, 18 Apr 2026 22:00:00 +0000</pubDate>
      <link>https://dev.to/danielwe/how-to-find-every-consumer-of-your-helm-chart-347c</link>
      <guid>https://dev.to/danielwe/how-to-find-every-consumer-of-your-helm-chart-347c</guid>
      <description>&lt;p&gt;&lt;em&gt;You maintain a shared Helm chart. A breaking change is coming. Which teams across the org are running it — and at which version?&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You own a shared Helm chart. Maybe it's a &lt;code&gt;platform-services&lt;/code&gt; chart that bundles your observability stack: Prometheus, Alertmanager, and a custom metrics exporter your platform team maintains. Maybe it's an &lt;code&gt;ingress-gateway&lt;/code&gt; chart that every product team deploys at the edge of their namespace. Maybe it's a company-wide chart for spinning up a standard service with your logging sidecar, network policies, and PodDisruptionBudget pre-configured.&lt;/p&gt;

&lt;p&gt;It started as a convenience. Teams stopped reinventing the same Kubernetes manifests. A dozen clusters adopted it. Then more. Some teams deploy it via ArgoCD. Some use Flux. Some have &lt;code&gt;helm install&lt;/code&gt; calls baked into their CI pipelines. Some consume it as a subchart inside their own umbrella chart. Some use a plain &lt;code&gt;Chart.yaml&lt;/code&gt; with a &lt;code&gt;dependencies:&lt;/code&gt; block and run &lt;code&gt;helm dependency update&lt;/code&gt; before deploy.&lt;/p&gt;

&lt;p&gt;Now you need to change it. Maybe a value key is being renamed — &lt;code&gt;replicaCount&lt;/code&gt; is becoming &lt;code&gt;replicas&lt;/code&gt; to align with upstream conventions. Maybe you're dropping support for a Kubernetes API version that was removed in 1.32. Maybe you're restructuring the chart into subcharts to make individual components opt-in rather than all-or-nothing.&lt;/p&gt;

&lt;p&gt;The question is the same one it always is: &lt;strong&gt;which repos across the org consume this chart, and at which version?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here is what Helm chart consumption looks like in practice. Your platform team publishes a chart to an internal chart repository or OCI registry. Consumers reference it in a few different ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;As a standalone &lt;code&gt;helm install&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;platform-services oci://registry.company.com/helm-charts/platform-services &lt;span class="nt"&gt;--version&lt;/span&gt; 3.2.1 &lt;span class="nt"&gt;-n&lt;/span&gt; monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;As a dependency in another chart's &lt;code&gt;Chart.yaml&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# some-service/chart/Chart.yaml&lt;/span&gt;
&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~3.2.0"&lt;/span&gt;
    &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://charts.company.com"&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;As an ArgoCD &lt;code&gt;Application&lt;/code&gt; manifest:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# gitops/apps/some-team/monitoring.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Application&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://charts.company.com&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3.2.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;As a Flux &lt;code&gt;HelmRelease&lt;/code&gt; CRD:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# gitops/releases/monitoring.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helm.toolkit.fluxcd.io/v2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRelease&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=3.0.0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;4.0.0"&lt;/span&gt;
      &lt;span class="na"&gt;sourceRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRepository&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;company-charts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All four patterns express the same dependency. None of them look the same in source code. And right now, they probably live in different repos, written by different teams, using different GitOps tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What existing tools give you (and where they stop)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Helm itself
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;helm&lt;/code&gt; CLI knows about releases deployed to a cluster. &lt;code&gt;helm list -A&lt;/code&gt; shows every release across all namespaces in a given cluster, including which chart and version is installed. &lt;code&gt;helm history &amp;lt;release&amp;gt;&lt;/code&gt; shows the version history for a specific release.&lt;/p&gt;

&lt;p&gt;This is useful once you have access to the cluster. The limitation is that it's cluster-scoped and live-state-only. You can see what's running right now, but it tells you nothing about &lt;em&gt;source repos&lt;/em&gt; — which repo contains the &lt;code&gt;values.yaml&lt;/code&gt; driving the release, which team owns it, or whether the same chart is deployed to a different cluster you don't have access to. Most companies have at least three clusters (dev, staging, prod), and many have dozens. You'd need access to all of them, and even then you'd have a list of running releases, not a map of source-code consumers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Artifact Hub and Chart Museums
&lt;/h3&gt;

&lt;p&gt;Artifact Hub is the public index for Helm charts. It tracks download counts and shows chart metadata, but it's a discovery tool for chart &lt;em&gt;users&lt;/em&gt;, not a graph of chart &lt;em&gt;consumers&lt;/em&gt;. If your chart is published internally — to a private chart repository or an OCI registry — it doesn't appear in Artifact Hub at all. And even for public charts, neither Artifact Hub nor any chart museum stores information about which orgs or repos have installed a particular chart version.&lt;/p&gt;

&lt;h3&gt;
  
  
  ArgoCD and Flux
&lt;/h3&gt;

&lt;p&gt;If your org has committed fully to GitOps and every Helm release is declared as an ArgoCD &lt;code&gt;Application&lt;/code&gt; or a Flux &lt;code&gt;HelmRelease&lt;/code&gt; manifest in a Git repo, then in theory all your consumers are visible in those manifests. The problem is "in theory." In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not every team has migrated to GitOps. Some still run &lt;code&gt;helm upgrade&lt;/code&gt; in CI pipelines or from engineer laptops.&lt;/li&gt;
&lt;li&gt;The GitOps manifests may live in many different repos — a central gitops repo, per-team repos, per-environment repos — rather than a single queryable location.&lt;/li&gt;
&lt;li&gt;ArgoCD's UI shows you the apps it's managing, but querying "show me every app that references chart &lt;code&gt;platform-services&lt;/code&gt;" is not a built-in view. You'd need to iterate through the Application CRDs or query the Argo CD API.&lt;/li&gt;
&lt;li&gt;Flux doesn't have a central query surface for "which &lt;code&gt;HelmRelease&lt;/code&gt; objects across all clusters reference chart X at version Y" — that information is spread across &lt;code&gt;HelmRelease&lt;/code&gt; YAML files in whatever repos your GitOps setup uses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Renovate
&lt;/h3&gt;

&lt;p&gt;Renovate supports Helm chart version bumps as a first-class feature. Configured in a repo, it detects chart references in &lt;code&gt;Chart.yaml&lt;/code&gt; dependencies, ArgoCD Application manifests, and Flux HelmRelease CRDs, and opens pull requests when new chart versions are published.&lt;/p&gt;

&lt;p&gt;As with Terraform and GitHub Actions: Renovate &lt;em&gt;must&lt;/em&gt; know who consumes what in order to open those PRs. But it doesn't expose that knowledge as a queryable view. You can't ask Renovate "which repos reference my chart and at which version?" It reacts after a new version is published. The consumer list before you publish a breaking change is not something it surfaces.&lt;/p&gt;

&lt;p&gt;Renovate also only covers repos where it has been configured. A team that hasn't added Helm to their Renovate config — or doesn't use Renovate at all — is invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grep
&lt;/h3&gt;

&lt;p&gt;Clone every repo. Search for the chart name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"platform-services"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.yaml"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.yml"&lt;/span&gt; ~/repos/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This finds direct name references in YAML files. It doesn't distinguish between chart references and random YAML comments, doesn't extract version constraints, misses OCI-format references where the chart name is embedded in a registry URL, and ignores any &lt;code&gt;helm install&lt;/code&gt; calls in shell scripts or Makefile targets. And as always: the results are stale by the next commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is harder than it looks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reference formats are highly inconsistent.&lt;/strong&gt; A Helm chart can be referenced via a traditional HTTP chart repository, an OCI registry, or a local path. These look nothing alike:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Traditional repository (in Chart.yaml dependencies)&lt;/span&gt;
&lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://charts.company.com"&lt;/span&gt;

&lt;span class="c1"&gt;# OCI registry (in Chart.yaml dependencies, Helm 3.8+)&lt;/span&gt;
&lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oci://registry.company.com/helm-charts"&lt;/span&gt;

&lt;span class="c1"&gt;# OCI in a Flux HelmRepository&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oci&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oci://registry.company.com/helm-charts&lt;/span&gt;

&lt;span class="c1"&gt;# Inline OCI reference in a helm CLI call&lt;/span&gt;
&lt;span class="s"&gt;helm install platform-services oci://registry.company.com/helm-charts/platform-services --version 3.2.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resolving all of these back to the same chart identity requires knowing how your registry namespaces charts and how your chart repositories map to OCI paths — out-of-band information that no grep can recover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version constraints vary widely.&lt;/strong&gt; Unlike Docker image &lt;code&gt;FROM&lt;/code&gt; statements (which usually pin to a specific tag) or Terraform module &lt;code&gt;ref=&lt;/code&gt; pins, Helm consumers regularly use semver constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~3.2.0"&lt;/span&gt;      &lt;span class="c1"&gt;# patch-compatible only: &amp;gt;=3.2.0 &amp;lt;3.3.0&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^3.0.0"&lt;/span&gt;      &lt;span class="c1"&gt;# minor-compatible: &amp;gt;=3.0.0 &amp;lt;4.0.0&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=3.0.0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;4"&lt;/span&gt;  &lt;span class="c1"&gt;# explicit range&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;           &lt;span class="c1"&gt;# any version (dangerous)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A consumer pinned to &lt;code&gt;^3.0.0&lt;/code&gt; will automatically receive your &lt;code&gt;3.3.0&lt;/code&gt; release — they don't have to explicitly upgrade. A consumer on &lt;code&gt;~3.2.0&lt;/code&gt; won't. Understanding who will be affected by a minor version bump versus a major bump requires evaluating constraints, not just comparing version strings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Umbrella charts create transitive consumers.&lt;/strong&gt; An umbrella chart is a Helm chart whose primary purpose is to bundle multiple subcharts together. If &lt;code&gt;team-a&lt;/code&gt; publishes an umbrella chart &lt;code&gt;team-a-stack&lt;/code&gt; that includes your &lt;code&gt;platform-services&lt;/code&gt; chart as a dependency, and &lt;code&gt;team-b&lt;/code&gt; deploys &lt;code&gt;team-a-stack&lt;/code&gt; — then &lt;code&gt;team-b&lt;/code&gt; is a transitive consumer of &lt;code&gt;platform-services&lt;/code&gt; through &lt;code&gt;team-a-stack&lt;/code&gt;. A breaking change to &lt;code&gt;platform-services&lt;/code&gt; affects &lt;code&gt;team-b&lt;/code&gt;, but a search for &lt;code&gt;platform-services&lt;/code&gt; in their repos finds nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitOps and imperative installs coexist.&lt;/strong&gt; In most organisations, some teams manage Helm releases declaratively (ArgoCD, Flux) and others do it imperatively (&lt;code&gt;helm install&lt;/code&gt; or &lt;code&gt;helm upgrade&lt;/code&gt; in CI pipelines, shell scripts, or Makefiles). The declarative installs leave traces in Git repos as YAML manifests. The imperative installs may leave no trace in source code at all — just a live release visible in &lt;code&gt;helm list&lt;/code&gt;. A complete consumer map needs to combine both sources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chart aliases break name matching.&lt;/strong&gt; A &lt;code&gt;Chart.yaml&lt;/code&gt; dependency can declare an &lt;code&gt;alias&lt;/code&gt;, which changes the name used to refer to the subchart in values overrides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-services&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~3.2.0"&lt;/span&gt;
    &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://charts.company.com"&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring&lt;/span&gt;   &lt;span class="c1"&gt;# the chart is installed as "monitoring", not "platform-services"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A naive search for &lt;code&gt;platform-services&lt;/code&gt; in values files will find nothing, because the values are nested under &lt;code&gt;monitoring:&lt;/code&gt;. Tools that rely on name matching alone miss aliased consumers entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full answer requires
&lt;/h2&gt;

&lt;p&gt;To reliably answer "who consumes this chart," you need a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans every repo in the org&lt;/strong&gt;, including GitOps repos, service repos, and platform repos — without requiring registration or manual catalog entries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parses all reference formats&lt;/strong&gt; — &lt;code&gt;Chart.yaml&lt;/code&gt; dependencies, ArgoCD Application manifests, Flux HelmRelease CRDs, and &lt;code&gt;helm install&lt;/code&gt; calls in CI configs and shell scripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolves OCI and HTTP registry references&lt;/strong&gt; back to a canonical chart identity, so consumers using different registry URL formats for the same chart are recognised as the same dependency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluates version constraints&lt;/strong&gt; rather than just matching exact version strings, so you know which consumers are within range of your next release&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follows umbrella chart transitive dependencies&lt;/strong&gt;, so second-order consumers are visible, not just direct chart references&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps results current&lt;/strong&gt; through regular rescans, not a one-time manual trawl&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of the specific problems &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; is built to solve. It connects to your GitHub or GitLab organisation, scans every repo, and extracts chart references from &lt;code&gt;Chart.yaml&lt;/code&gt; dependency blocks, ArgoCD Application and ApplicationSet manifests, Flux HelmRelease CRDs, and &lt;code&gt;helm&lt;/code&gt; calls in CI configs. It resolves OCI and HTTP registry URLs to a unified chart identity and builds a cross-repo dependency graph you can query by chart name and version.&lt;/p&gt;

&lt;p&gt;The result is the same kind of view described in the earlier posts in this series: before you rename a value key or drop a Kubernetes API version, you open the graph, click the chart, and see every consumer — their version constraint, the repo that owns the deployment declaration, and which constraint ranges include your next release. You know who'll automatically receive the change, who's pinned to an older range, and who you need to contact before shipping.&lt;/p&gt;

&lt;p&gt;No cluster access required. No cross-org Slack ping. No grep marathon across forty repos.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How is your team solving this today?&lt;/strong&gt; I'd genuinely like to know — drop a comment or find me at &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;riftmap.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>helm</category>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Find Every Consumer of Your Reusable GitHub Actions Workflow</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Fri, 17 Apr 2026 06:38:52 +0000</pubDate>
      <link>https://dev.to/danielwe/how-to-find-every-consumer-of-your-reusable-github-actions-workflow-2cpn</link>
      <guid>https://dev.to/danielwe/how-to-find-every-consumer-of-your-reusable-github-actions-workflow-2cpn</guid>
      <description>&lt;p&gt;&lt;em&gt;You maintain a shared GitHub Actions workflow. You need to rename an input, drop a step, or change a required secret. Which repos across your org call it? Here's why the answer is harder than it should be.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You maintain a reusable GitHub Actions workflow. Maybe it's &lt;code&gt;deploy-to-eks.yml&lt;/code&gt; in your platform team's &lt;code&gt;.github&lt;/code&gt; repo, or a &lt;code&gt;ci-lint-test.yml&lt;/code&gt; that standardises how every service in the org runs its CI pipeline. It started as a way to stop every team from copy-pasting the same 80-line workflow file. It worked. Twenty repos adopted it. Then forty. Then you lost count.&lt;/p&gt;

&lt;p&gt;Now you need to change it. Maybe you're replacing a deprecated action (&lt;code&gt;actions/setup-node@v4&lt;/code&gt; is being forced to Node 24 and you need to bump to &lt;code&gt;v6&lt;/code&gt;). Maybe you're renaming an input from &lt;code&gt;node-version&lt;/code&gt; to &lt;code&gt;runtime-version&lt;/code&gt; because the workflow now supports Bun and Deno too. Maybe you're removing a step that uploaded artifacts to a bucket that no longer exists.&lt;/p&gt;

&lt;p&gt;The question is the same one that comes up for every shared infrastructure artifact: &lt;strong&gt;which repos across our org call this workflow, and at which ref?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here's what a typical reusable workflow consumption looks like. Your platform team publishes a workflow in a central repository, and other repos call it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In some-service/.github/workflows/ci.yml&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-org/platform-workflows/.github/workflows/ci-lint-test.yml@v2.1.0&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;20"&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inherit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some repos pin to a tag. Some pin to a branch like &lt;code&gt;main&lt;/code&gt;. Some pin to a full commit SHA for supply-chain security. Some don't pin at all and reference &lt;code&gt;@main&lt;/code&gt;, which means they're calling whatever version is on HEAD right now.&lt;/p&gt;

&lt;p&gt;You need to find all of them, understand which version each one uses, and figure out which will break when you rename that input. Right now, most teams do this by searching in GitHub, asking in Slack, or just pushing the change and waiting for workflows to fail. None of these are a real answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What existing tools give you (and where they stop)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub code search
&lt;/h3&gt;

&lt;p&gt;GitHub's code search is the most natural place to look. You can search across all repos in an org:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;org:my-org "uses&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-org/platform-workflows/.github/workflows/ci-lint-test.yml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for finding direct string matches. For a one-off audit, it's often good enough. But it has real limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't extract the ref. You can see that a repo calls the workflow, but the &lt;code&gt;@v2.1.0&lt;/code&gt; or &lt;code&gt;@main&lt;/code&gt; part requires manually opening each result and reading the YAML.&lt;/li&gt;
&lt;li&gt;Results are point-in-time. If you're planning a breaking change, you need to know who's calling the workflow &lt;em&gt;now&lt;/em&gt; — not when the search index was last updated.&lt;/li&gt;
&lt;li&gt;The search index can lag. GitHub's code search is eventually consistent. Repos that were recently updated might not show current results immediately.&lt;/li&gt;
&lt;li&gt;It doesn't handle indirection. If a repo has a "wrapper" workflow that calls your reusable workflow, and other workflows call the wrapper, GitHub code search only finds the direct callers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dependabot
&lt;/h3&gt;

&lt;p&gt;Dependabot supports GitHub Actions as a first-class ecosystem. In a repo's &lt;code&gt;dependabot.yml&lt;/code&gt;, you can configure it to watch for action version updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;updates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;package-ecosystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github-actions"&lt;/span&gt;
    &lt;span class="na"&gt;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt;
    &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weekly"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependabot will then open pull requests when actions referenced in the repo's workflows have new versions available. This includes reusable workflows referenced with &lt;code&gt;uses:&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same pattern as Renovate for Terraform: Dependabot &lt;em&gt;implicitly&lt;/em&gt; knows who consumes what, because it's configured per-repo. But it doesn't expose this as a queryable view. You can't ask Dependabot "which repos in my org call &lt;code&gt;platform-workflows/ci-lint-test.yml&lt;/code&gt;?" It's a version updater, not a dependency mapper.&lt;/p&gt;

&lt;p&gt;There's an additional limitation: Dependabot only monitors repos where it's been explicitly enabled. If a team hasn't added &lt;code&gt;github-actions&lt;/code&gt; to their &lt;code&gt;dependabot.yml&lt;/code&gt; — or doesn't have a &lt;code&gt;dependabot.yml&lt;/code&gt; at all — their workflow dependencies are invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Renovate
&lt;/h3&gt;

&lt;p&gt;Renovate also supports GitHub Actions workflows, including reusable workflow references. It can detect &lt;code&gt;uses:&lt;/code&gt; directives in workflow files and open PRs when new versions are available.&lt;/p&gt;

&lt;p&gt;Renovate has a slight edge over Dependabot here: it can be configured org-wide via a shared preset, so you don't need every repo to opt in individually. But the core limitation is identical — it's a dependency &lt;em&gt;updater&lt;/em&gt;, not a dependency &lt;em&gt;mapper&lt;/em&gt;. It reacts when a new version exists. It doesn't give you the blast radius before you publish the change.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub API
&lt;/h3&gt;

&lt;p&gt;The GitHub API lets you list workflows and workflow runs across repositories. You can enumerate all repos in an org, fetch their workflow files, and parse the YAML yourself.&lt;/p&gt;

&lt;p&gt;This is essentially building a custom scanner. The API gives you the raw data, but assembling it into an answer requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paginating through every repo in the org&lt;/li&gt;
&lt;li&gt;Fetching the contents of every &lt;code&gt;.github/workflows/*.yml&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;Parsing YAML to extract &lt;code&gt;uses:&lt;/code&gt; directives&lt;/li&gt;
&lt;li&gt;Filtering for your specific reusable workflow&lt;/li&gt;
&lt;li&gt;Extracting the ref from each reference&lt;/li&gt;
&lt;li&gt;Handling rate limits (the GitHub API is aggressive about rate limiting for large orgs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Teams that go this route end up building a script, running it periodically, and storing the results somewhere. It works. It's also a project you now have to maintain, and the results are stale between runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grep
&lt;/h3&gt;

&lt;p&gt;Clone everything, grep for the workflow reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"platform-workflows/.github/workflows/ci-lint-test.yml"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.yml"&lt;/span&gt; ~/repos/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same trade-offs as always: works once, stale immediately, doesn't extract versions, doesn't catch transitive calls, and at 100+ repos it's slow enough that nobody does it proactively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is harder than it looks
&lt;/h2&gt;

&lt;p&gt;Reusable workflow references look simpler than Terraform module sources or Docker image references — there's really only one syntax. But the complexity is in what surrounds that syntax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ref types vary widely.&lt;/strong&gt; The &lt;code&gt;@ref&lt;/code&gt; portion of a workflow call can be a tag, a branch, or a full SHA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Tag — the common case&lt;/span&gt;
&lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-org/platform-workflows/.github/workflows/deploy.yml@v2.1.0&lt;/span&gt;

&lt;span class="c1"&gt;# Branch — consuming HEAD, inherently unstable&lt;/span&gt;
&lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-org/platform-workflows/.github/workflows/deploy.yml@main&lt;/span&gt;

&lt;span class="c1"&gt;# SHA — supply-chain hardened, but opaque&lt;/span&gt;
&lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-org/platform-workflows/.github/workflows/deploy.yml@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A tag tells you the version. A branch tells you almost nothing — the consumer is on whatever commit &lt;code&gt;main&lt;/code&gt; happened to point to when they last ran. A SHA is precise but requires resolving the commit to a tag or branch to understand which &lt;em&gt;version&lt;/em&gt; the consumer is actually using. Answering "who's still on v1.x?" requires understanding all three forms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reusable workflows vs. composite actions.&lt;/strong&gt; GitHub Actions has two mechanisms for sharing CI logic: reusable workflows (called with &lt;code&gt;uses:&lt;/code&gt; at the &lt;em&gt;job&lt;/em&gt; level) and composite actions (called with &lt;code&gt;uses:&lt;/code&gt; at the &lt;em&gt;step&lt;/em&gt; level). They look similar in YAML but behave differently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Reusable workflow — job level&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-org/platform-workflows/.github/workflows/deploy.yml@v2&lt;/span&gt;

&lt;span class="c1"&gt;# Composite action — step level&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-org/platform-actions/setup-toolchain@v3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are dependencies on shared code in other repos. Both break when the interface changes. A consumer tracking system needs to find both, because platform teams often maintain a mix of reusable workflows and composite actions — and the same "who's using this?" question applies to both.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;.github&lt;/code&gt; repository.&lt;/strong&gt; GitHub has a special convention: a repo named &lt;code&gt;.github&lt;/code&gt; in an org can contain workflow templates that appear in every repo's "Actions" tab. If your platform team publishes starter workflows through the &lt;code&gt;.github&lt;/code&gt; repo, those templates get copied (not referenced) into consuming repos. Once copied, they're no longer linked to the source — there's no &lt;code&gt;uses:&lt;/code&gt; directive pointing back, just a disconnected copy that may or may not have been modified since.&lt;/p&gt;

&lt;p&gt;This means some of your "consumers" are invisible. They took the template, modified it, and now maintain their own fork. You can't find them by searching for &lt;code&gt;uses:&lt;/code&gt; directives because there aren't any.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inputs and secrets are the breaking surface.&lt;/strong&gt; Unlike Terraform modules, where the interface is defined by variables and outputs with explicit types, reusable workflow inputs are loosely typed. A workflow declares its inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DEPLOY_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you rename &lt;code&gt;node-version&lt;/code&gt; to &lt;code&gt;runtime-version&lt;/code&gt;, every caller that passes &lt;code&gt;node-version&lt;/code&gt; will silently fail — the input just won't be received, and the workflow will use its default or break downstream. There's no compile-time error. The failure shows up at runtime, in the middle of a CI run, and the error message often doesn't clearly point to the renamed input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflow call chains.&lt;/strong&gt; Reusable workflows can call other reusable workflows, up to ten levels deep and fifty total workflow calls per run (GitHub increased these limits in late 2025). If workflow A calls workflow B, which calls workflow C, and you change C's interface, both A and B are affected — but only B directly references C. Finding A requires resolving the full call chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full answer requires
&lt;/h2&gt;

&lt;p&gt;To reliably answer "who consumes this reusable workflow," you need a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans every repo in the org&lt;/strong&gt;, parsing all &lt;code&gt;.github/workflows/*.yml&lt;/code&gt; files — not just repos that have opted into Dependabot or Renovate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extracts &lt;code&gt;uses:&lt;/code&gt; directives&lt;/strong&gt; at both the job level (reusable workflows) and step level (composite actions), because platform teams typically maintain both&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolves ref types&lt;/strong&gt; — tags, branches, and SHAs — to a normalised version, so you can answer "who's still on v1.x?" regardless of how the ref was specified&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follows workflow call chains&lt;/strong&gt; so you see transitive consumers, not just direct callers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps the graph current&lt;/strong&gt; through regular rescans, not one-off audits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Makes the result queryable&lt;/strong&gt;: "show me every consumer of &lt;code&gt;ci-lint-test.yml&lt;/code&gt;, grouped by ref, with the team that owns each calling repo"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of the specific problems &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; is built to solve. It scans a GitHub (or GitLab) org, parses every workflow file in every repo, extracts all &lt;code&gt;uses:&lt;/code&gt; directives — including reusable workflow calls, composite action references, and standard action pins — and builds a cross-repo dependency graph that you can query by artifact.&lt;/p&gt;

&lt;p&gt;The result: before you rename that input or remove that step, you open the graph, click the workflow, and see exactly which repos call it and which ref each one pins to. You know who'll break. You know who to notify. You know who's pinned to &lt;code&gt;main&lt;/code&gt; and will see the change immediately versus who's on a tagged release and has time to migrate.&lt;/p&gt;

&lt;p&gt;No org-wide grep. No stale GitHub code search. No "let's push it and see whose CI goes red."&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How is your team solving this today?&lt;/strong&gt; I'd genuinely like to know — drop a comment or find me at &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;riftmap.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>devops</category>
      <category>platformengineering</category>
      <category>dependencymanagement</category>
    </item>
    <item>
      <title>How to Find Every Consumer of Your Terraform Module</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Wed, 15 Apr 2026 09:30:27 +0000</pubDate>
      <link>https://dev.to/danielwe/how-to-find-every-consumer-of-your-terraform-module-enh</link>
      <guid>https://dev.to/danielwe/how-to-find-every-consumer-of-your-terraform-module-enh</guid>
      <description>&lt;p&gt;&lt;em&gt;You maintain a shared Terraform module. You need to make a breaking change. Which repos across your org actually source it — and at which version? Here's why the answer is harder than it should be.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You maintain an internal Terraform module. Maybe it's &lt;code&gt;terraform-aws-vpc&lt;/code&gt; or &lt;code&gt;modules/rds-cluster&lt;/code&gt; or a shared networking baseline that half the org builds on top of. It started as a convenience — one team wrote a good VPC module, others adopted it, and now it's load-bearing infrastructure for dozens of repos.&lt;/p&gt;

&lt;p&gt;Then you need to change it. Maybe you're upgrading the AWS provider from v4 to v5 and the module interface needs to change. Maybe you're deprecating a variable that seemed like a good idea eighteen months ago. Maybe you're fixing a security misconfiguration and the fix requires consumers to pass a new required variable.&lt;/p&gt;

&lt;p&gt;The question is simple: &lt;strong&gt;which repos across our org source this module, and at which version?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you can't answer this, you're deploying blind. You either make the change and wait for &lt;code&gt;terraform plan&lt;/code&gt; failures to trickle in across teams, or you don't make the change at all and let the tech debt compound. Neither option is good.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here's what this typically looks like. Your platform team publishes a module, and repos across the org consume it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://gitlab.company.com/infra/terraform-aws-vpc.git?ref=v2.3.0"&lt;/span&gt;

  &lt;span class="nx"&gt;cidr_block&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;
  &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some repos pin to a specific Git tag. Some pin to a branch. Some point at &lt;code&gt;main&lt;/code&gt; with no ref at all, which means they're consuming whatever happens to be at HEAD when they next run &lt;code&gt;terraform init&lt;/code&gt;. Some source the module from your internal Terraform registry instead of Git. Some use a relative path because the module lives in a monorepo alongside the consuming root module.&lt;/p&gt;

&lt;p&gt;You need to find all of them, understand which version each one uses, and figure out which teams to notify before you push the breaking change. Right now, most teams do this by grepping, searching the Terraform registry UI, or asking in Slack. None of these give you a complete answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What existing tools give you (and where they stop)
&lt;/h2&gt;

&lt;p&gt;Several tools address pieces of the Terraform module consumer problem. Each one is useful in its own context. None of them answer the full question.&lt;/p&gt;

&lt;h3&gt;
  
  
  HCP Terraform (Terraform Cloud) Module Registry
&lt;/h3&gt;

&lt;p&gt;If you use HCP Terraform or Terraform Enterprise, the private module registry tracks which workspaces consume a published module. The registry UI shows a module's versions, usage statistics, and which workspaces reference it.&lt;/p&gt;

&lt;p&gt;This works within the HCP Terraform ecosystem. The limitation is scope: it only sees modules published to &lt;em&gt;its&lt;/em&gt; registry and consumed by &lt;em&gt;its&lt;/em&gt; workspaces. If a team sources the module directly via a Git URL — which is extremely common, especially in orgs that adopted Terraform before the private registry existed — that consumption is invisible. If some repos use HCP Terraform and others use a different backend (local state, S3, Spacelift, env0, Atlantis), you're seeing a partial picture. It's also worth noting that the HCP Terraform free tier was discontinued in March 2026, so this option now requires a paid plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spacelift / Scalr / env0
&lt;/h3&gt;

&lt;p&gt;Spacelift has the most advanced module consumer tracking of the orchestration platforms. Its module registry tracks which stacks consume each module version, and trigger policies can automatically kick off runs on consumer stacks when a new module version is published. Scalr offers a modules report that shows which modules and versions are used across all workspaces, including which workspaces are running outdated versions. env0 has similar workspace-level dependency awareness.&lt;/p&gt;

&lt;p&gt;These are genuinely useful capabilities — if all your Terraform runs flow through a single platform. The limitation is the same for all of them: they only see what's inside their own ecosystem. If your org uses Spacelift for production infrastructure but engineers run &lt;code&gt;terraform plan&lt;/code&gt; locally for development, or if one team uses Atlantis while another uses GitHub Actions with raw &lt;code&gt;terraform&lt;/code&gt; commands, no single orchestrator has the full picture. And none of them see the module source references in the &lt;em&gt;code&lt;/em&gt; — they see module consumption at the &lt;em&gt;workspace execution&lt;/em&gt; level, which is a different layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;terraform providers&lt;/code&gt; and &lt;code&gt;terraform graph&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Terraform's built-in &lt;code&gt;terraform graph&lt;/code&gt; command outputs a DOT-format dependency graph for a single root module. &lt;code&gt;terraform providers&lt;/code&gt; lists the providers required by a configuration and its modules.&lt;/p&gt;

&lt;p&gt;These commands work per-configuration, not per-org. They answer "what does &lt;em&gt;this&lt;/em&gt; root module depend on?" They don't answer "which root modules across my org depend on &lt;em&gt;that&lt;/em&gt; shared module?" You'd need to clone every repo, run the command in every Terraform root, and aggregate the results — which is essentially building a custom scanner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grep / GitHub code search
&lt;/h3&gt;

&lt;p&gt;The most common approach. Clone everything (or use GitHub's code search), and search for the module source URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"terraform-aws-vpc"&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.tf"&lt;/span&gt; ~/repos/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This finds direct string matches. It works for a one-off audit. But it breaks down in several ways that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It doesn't resolve registry sources. &lt;code&gt;source = "app.terraform.io/company/vpc/aws"&lt;/code&gt; and &lt;code&gt;source = "git::https://gitlab.company.com/infra/terraform-aws-vpc.git"&lt;/code&gt; might reference the same module — grep treats them as unrelated strings.&lt;/li&gt;
&lt;li&gt;It doesn't extract versions. You can see that a repo references the module, but parsing out &lt;code&gt;?ref=v2.3.0&lt;/code&gt; or the &lt;code&gt;version = "~&amp;gt; 2.0"&lt;/code&gt; constraint from a registry source requires additional work per match.&lt;/li&gt;
&lt;li&gt;The results are stale immediately. The search reflects the state of whatever you cloned. By tomorrow, three repos might have changed their module references.&lt;/li&gt;
&lt;li&gt;At 100+ repos, it takes long enough that nobody does it proactively. It becomes an incident response activity, not a planning tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Renovate
&lt;/h3&gt;

&lt;p&gt;Renovate understands Terraform module sources and can open pull requests when a new version is available. It parses &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt; attributes in &lt;code&gt;.tf&lt;/code&gt; files and supports Git refs, registry modules, and GitHub releases.&lt;/p&gt;

&lt;p&gt;Like with Docker images, Renovate &lt;em&gt;implicitly&lt;/em&gt; knows who consumes what — it's configured per-repo and has parsed the module sources. But it doesn't expose this as a queryable view. You can't ask Renovate "which repos in my org source &lt;code&gt;terraform-aws-vpc&lt;/code&gt;?" It's a dependency &lt;em&gt;updater&lt;/em&gt;, not a dependency &lt;em&gt;mapper&lt;/em&gt;. It reacts after a new version exists. It doesn't give you the blast radius &lt;em&gt;before&lt;/em&gt; you publish the new version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is harder than it looks
&lt;/h2&gt;

&lt;p&gt;Terraform module sourcing is more complex than most people realise, because the same module can be referenced through at least five different source types — and each one looks completely different in the HCL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git sources&lt;/strong&gt; are the most common for internal modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://gitlab.company.com/infra/terraform-aws-vpc.git?ref=v2.3.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The URL might use HTTPS or SSH. The ref might be a tag, a branch, or a commit SHA. Some teams use the &lt;code&gt;git::&lt;/code&gt; prefix, others rely on Terraform's URL inference. The same module can appear as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://gitlab.company.com/infra/terraform-aws-vpc.git?ref=v2.3.0"&lt;/span&gt;
&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git@gitlab.company.com:infra/terraform-aws-vpc.git?ref=v2.3.0"&lt;/span&gt;
&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gitlab.company.com/infra/terraform-aws-vpc?ref=v2.3.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These all point at the same module. A grep for one form misses the others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Registry sources&lt;/strong&gt; use a completely different syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app.terraform.io/company/vpc/aws"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This references the same underlying module, but through the registry. The version constraint is in a separate attribute, not in the source URL. Matching this to the Git-sourced references requires knowing that the registry module &lt;code&gt;company/vpc/aws&lt;/code&gt; maps to the Git repo &lt;code&gt;infra/terraform-aws-vpc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local paths&lt;/strong&gt; appear when modules live alongside root configurations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../../modules/vpc"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a dependency on an internal module, but there's no URL to grep for. The relationship is purely structural — you need to resolve the relative path within the repo's directory tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub/GitLab shorthand&lt;/strong&gt; sources add another variant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github.com/company/terraform-aws-vpc?ref=v2.3.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform interprets this as a Git clone from GitHub, but the syntax differs from both the explicit &lt;code&gt;git::&lt;/code&gt; form and the registry form.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Subdirectory references&lt;/strong&gt; complicate things further:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc_endpoints"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::https://gitlab.company.com/infra/terraform-aws-vpc.git//modules/endpoints?ref=v2.3.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;//&lt;/code&gt; separator means "clone this repo, then use the &lt;code&gt;modules/endpoints&lt;/code&gt; subdirectory." This is a dependency on the same repo but a different module path within it. A consumer tracking system needs to handle both the repo-level relationship and the subpath.&lt;/p&gt;

&lt;p&gt;Then there's the &lt;strong&gt;transitive problem&lt;/strong&gt;. Module A sources module B, which sources module C. If you change module C, both B and A are affected — but A never mentions C anywhere in its code. Understanding the full blast radius requires resolving the entire chain, not just direct consumers.&lt;/p&gt;

&lt;p&gt;None of the tools in the previous section handle all of these source types and resolve them to a unified view. This is why teams keep falling back to grep and tribal knowledge — and why both keep failing at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full answer requires
&lt;/h2&gt;

&lt;p&gt;To reliably answer "who consumes this Terraform module," you need a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans every repo in the org&lt;/strong&gt;, not just those registered in a specific orchestration platform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parses all five source types&lt;/strong&gt; — Git URLs (HTTPS and SSH), registry sources, local paths, GitHub/GitLab shorthand, and subdirectory references — and normalises them to a single identity per module&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extracts version constraints&lt;/strong&gt; from both &lt;code&gt;?ref=&lt;/code&gt; parameters in Git sources and &lt;code&gt;version&lt;/code&gt; attributes in registry sources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolves transitive dependencies&lt;/strong&gt; so you can see not just direct consumers but the full downstream blast radius&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps the graph current&lt;/strong&gt; through scheduled or event-triggered rescans, not one-off audits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Makes the result queryable&lt;/strong&gt;: "show me every consumer of &lt;code&gt;terraform-aws-vpc&lt;/code&gt;, grouped by version, with the team that owns each consuming repo"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of the specific problems &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; is built to solve. It scans a GitLab or GitHub org, parses every &lt;code&gt;.tf&lt;/code&gt; file across every repo, resolves all five module source types to a unified dependency graph, and lets you click on any module to see every consumer — direct and transitive — with the version each one pins to.&lt;/p&gt;

&lt;p&gt;The result: before you push the breaking change, you open the graph, click the module, and see exactly which repos and teams are affected. You know who to notify. You know who's still on v1.x and who's already on v2.x. You know which repos pin to &lt;code&gt;main&lt;/code&gt; and will break immediately versus which pin to a tag and have time to migrate.&lt;/p&gt;

&lt;p&gt;No grepping. No Slack polling. No "let's just push it and see who complains."&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How is your team solving this today?&lt;/strong&gt; I'd genuinely like to know — drop a comment or find me at &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;riftmap.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>infrastructure</category>
      <category>security</category>
    </item>
    <item>
      <title>How to Find Every Consumer of Your Docker Base</title>
      <dc:creator>Daniel Westgaard</dc:creator>
      <pubDate>Mon, 13 Apr 2026 19:16:20 +0000</pubDate>
      <link>https://dev.to/danielwe/how-to-find-every-consumer-of-your-docker-base-e32</link>
      <guid>https://dev.to/danielwe/how-to-find-every-consumer-of-your-docker-base-e32</guid>
      <description>&lt;p&gt;&lt;em&gt;You maintain a shared base image. A CVE drops. Which repos are affected? Here's why the answer is harder than it should be.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You maintain an internal Docker base image. Maybe it's &lt;code&gt;platform/node-base&lt;/code&gt; or &lt;code&gt;company/python-runtime&lt;/code&gt;. A dozen repos use it. Or thirty. Or you're not sure how many, because nobody's counted since the last team reorganisation.&lt;/p&gt;

&lt;p&gt;Then a critical CVE hits the base OS layer, and you need to push a patched version. The question that matters is simple: &lt;strong&gt;which repos across our org pull this image, and at which version?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This should be easy to answer. It isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scenario
&lt;/h2&gt;

&lt;p&gt;Here's what this looks like in practice. Your platform team builds and publishes &lt;code&gt;registry.company.com/platform/base-image&lt;/code&gt;. Across the org, Dockerfiles reference it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; registry.company.com/platform/base-image:v2.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some repos pin to a specific tag. Some use &lt;code&gt;latest&lt;/code&gt;. Some have &lt;code&gt;ARG&lt;/code&gt;-parameterized &lt;code&gt;FROM&lt;/code&gt; statements where the tag is injected at build time. Some reference the image not in a Dockerfile but in a &lt;code&gt;docker-compose.yml&lt;/code&gt; or a CI pipeline config.&lt;/p&gt;

&lt;p&gt;You need to find all of them, check which version each one uses, and coordinate the update. Right now, most teams do this by grepping, asking on Slack, or checking CI logs. None of these give you a complete, current answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What existing tools give you (and where they stop)
&lt;/h2&gt;

&lt;p&gt;Several tools address parts of the Docker base image problem. Each one is useful. None of them answer the full question.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Scout
&lt;/h3&gt;

&lt;p&gt;Docker Scout analyzes a &lt;em&gt;built image&lt;/em&gt; and tells you what base image it uses, whether that base is outdated, and what vulnerabilities it contains. &lt;code&gt;docker scout recommendations&lt;/code&gt; will suggest updated base images with fewer CVEs.&lt;/p&gt;

&lt;p&gt;This is valuable, but it works per-image, not per-org. It answers "what base image does &lt;em&gt;this&lt;/em&gt; image use?" It doesn't answer "which of my 200 repos use &lt;em&gt;this&lt;/em&gt; base image?" You'd need to run Scout against every image in your registry and correlate the results back to source repos — which is a project in itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Renovate
&lt;/h3&gt;

&lt;p&gt;Renovate detects &lt;code&gt;FROM&lt;/code&gt; statements in Dockerfiles and can open pull requests when a newer tag is available. It &lt;em&gt;implicitly&lt;/em&gt; knows who consumes what, because it's configured per-repo and parses the Dockerfiles.&lt;/p&gt;

&lt;p&gt;But Renovate doesn't expose this as a queryable view. You can't ask Renovate "show me every repo that references &lt;code&gt;platform/base-image&lt;/code&gt;." It reacts when a new version appears. It doesn't give you the pre-release blast radius: before you push the patched image, which repos and teams will need to update?&lt;/p&gt;

&lt;h3&gt;
  
  
  Backstage / Roadie Tech Insights
&lt;/h3&gt;

&lt;p&gt;Roadie (a managed Backstage provider) has a Tech Insights feature that can parse Dockerfiles via regex, extract base image versions, and create scorecards tracking migration progress. This is the closest existing solution to the consumer-tracking problem.&lt;/p&gt;

&lt;p&gt;The limitation is that it requires Backstage to be set up with &lt;code&gt;catalog-info.yaml&lt;/code&gt; per repo. You're tracking Docker base images through a service catalog that depends on manual registration. If a repo isn't in the catalog, its Dockerfile is invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container registries
&lt;/h3&gt;

&lt;p&gt;Your registry (Docker Hub, Harbor, ACR, ECR) knows which images have been &lt;em&gt;pulled&lt;/em&gt; and how often. Some provide pull statistics by tag. But pull logs don't tell you which &lt;em&gt;source repo&lt;/em&gt; initiated the pull. A CI pipeline pulling &lt;code&gt;base-image:v2.1&lt;/code&gt; shows up as a pull event, not as "repo &lt;code&gt;frontend-api&lt;/code&gt; depends on this image via its Dockerfile on line 3."&lt;/p&gt;

&lt;h3&gt;
  
  
  Grep
&lt;/h3&gt;

&lt;p&gt;The fallback everyone uses. Clone all the repos, run &lt;code&gt;grep -r "platform/base-image"&lt;/code&gt;, assemble the results. It works once. The results are stale immediately. It misses parameterized &lt;code&gt;FROM&lt;/code&gt; statements, compose files, and CI configs. And at a hundred repos, it takes long enough that nobody does it proactively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is harder than it looks
&lt;/h2&gt;

&lt;p&gt;The core difficulty is that Docker base image dependencies live in multiple places and multiple formats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dockerfiles&lt;/strong&gt; are the obvious source, but &lt;code&gt;FROM&lt;/code&gt; statements can use &lt;code&gt;ARG&lt;/code&gt; substitution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; BASE_TAG=v2.1&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; registry.company.com/platform/base-image:${BASE_TAG}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple grep for the image name finds this. A simple grep for the &lt;em&gt;version&lt;/em&gt; doesn't, because the version is in a variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Compose files&lt;/strong&gt; reference images differently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.company.com/platform/base-image:v2.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a dependency on the same image, declared in a completely different file format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI pipeline configs&lt;/strong&gt; often pull images directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.company.com/platform/base-image:v2.1&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;make build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A GitLab CI &lt;code&gt;image:&lt;/code&gt; directive or a GitHub Actions &lt;code&gt;container:&lt;/code&gt; field is another consumption point for the same base image, and it's in yet another file.&lt;/p&gt;

&lt;p&gt;Then there's the &lt;strong&gt;producer-side problem&lt;/strong&gt;. Knowing which repos &lt;em&gt;consume&lt;/em&gt; the image is half the story. You also need to know which repo &lt;em&gt;builds&lt;/em&gt; it. That's usually a CI pipeline with &lt;code&gt;docker build&lt;/code&gt; and &lt;code&gt;docker push&lt;/code&gt; commands, not something declared in a manifest. Connecting the consumer graph to the producer requires cross-referencing multiple file types within and across repos.&lt;/p&gt;

&lt;p&gt;No single tool today connects all of these surfaces into one view.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the full answer requires
&lt;/h2&gt;

&lt;p&gt;To reliably answer "who consumes this base image," you need a system that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scans every repo in the org&lt;/strong&gt;, not just the ones registered in a catalog&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parses Dockerfiles, Compose files, and CI configs&lt;/strong&gt;, because the same image can be referenced in all three&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles variable substitution&lt;/strong&gt; in &lt;code&gt;FROM&lt;/code&gt; statements by resolving &lt;code&gt;ARG&lt;/code&gt; defaults&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detects which repos produce which images&lt;/strong&gt; by scanning CI configs for build and push commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps the results current&lt;/strong&gt; through scheduled or event-triggered rescans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Makes the graph queryable&lt;/strong&gt;: "show me every consumer of &lt;code&gt;platform/base-image&lt;/code&gt;, grouped by version"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is one of the specific problems I'm building &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;Riftmap&lt;/a&gt; to solve. It scans a GitLab or GitHub org, parses Dockerfiles (including multi-stage builds, ARG defaults, and Compose files), detects which repos produce which images via CI config analysis, and builds a cross-repo dependency graph you can query by artifact.&lt;/p&gt;

&lt;p&gt;The result: when that CVE drops, you click on the base image in the graph and immediately see every repo that depends on it, which version each one uses, and who owns them. No grepping. No Slack archaeology. No stale spreadsheet from last quarter.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How is your team solving this today?&lt;/strong&gt; I'd genuinely like to know — drop a comment or find me at &lt;a href="https://riftmap.dev" rel="noopener noreferrer"&gt;riftmap.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>infrastructure</category>
      <category>security</category>
    </item>
  </channel>
</rss>
