KognitaKognita.

Blog

Monorepos Broke AI Coding Tools. Here's the Real Reason Why.

10 min read

"The monorepo was supposed to make this easier." Every engineering team that migrated to a monorepo at some point had this conversation — shared code in one place, single source of truth, no more version skew between packages. And in practice, it does make many things easier: atomic cross-package changes, unified CI, consistent tooling, dependency management in one configuration file. The promise held up. Then AI coding tools arrived, and something broke.

The AI assistant that works brilliantly in a single-service repository starts producing wrong answers in the monorepo. It misses existing implementations and creates duplicates. It recommends patterns that were established packages ago but are not in the files it has indexed. It traces a bug to the wrong service because it cannot see the package that actually changed. The same model, the same tool, visibly degraded. The reason is that the indexing model underlying every major AI coding tool was designed for a world that monorepos exist specifically to replace — and the assumptions built into local indexing break under the monorepo's actual structure.

Why monorepos were supposed to make context easier

The original argument for monorepos was context consolidation. Instead of having the authentication logic duplicated across eight repositories — each drifting slightly from the others as teams patched their local copy independently — the monorepo puts it in libs/auth/ and everything imports from there. Changes propagate atomically. The whole organization can see, in one place, what the authentication library actually does. For human developers reading code, this genuinely works. You can grep across the full codebase, trace imports to their source, and navigate the dependency graph with a file browser and a terminal.

The context consolidation argument extends to onboarding and knowledge transfer. A new engineer in a multi-repo architecture has to clone twelve repositories to get a complete picture of the system. In a monorepo, one clone gives them the whole surface. Senior engineers can review changes across service boundaries in a single pull request. The organizational knowledge about how things relate is encoded structurally — in import paths, in shared package boundaries, in the fact that billing-service and checkout-service both import from shared/pricing-engine rather than having their own copies.

For human context, this works. Monorepos do make cross-package knowledge more accessible to developers willing to navigate them. The problem is that AI coding tools do not navigate monorepos the way developers do. They index them — and the way they index them breaks the context consolidation that monorepos were built to provide.

How AI tool indexing actually works — and why monorepos break it

Cursor, GitHub Copilot, and similar tools all work by building a local index of the workspace — a file-by-file vector embedding that enables semantic retrieval when the AI needs context for a completion or an agentic task. When you open a file and ask Cursor to explain a function, Cursor retrieves related files from the local index, constructs a context window, and sends it to the model. The quality of the answer depends directly on whether the right files were retrieved — and the right files can only be retrieved if they were indexed to begin with.

This model works well for repositories that fit comfortably in the local index. Cursor fully indexes roughly 2,500 files in a workspace before coverage degrades. For a single-service backend with a few hundred files, 2,500 is more than enough. For a monorepo with 100 packages, 2,500 files covers fewer than 30 packages at typical package sizes — and the packages it indexes are determined by recency of access rather than architectural importance. The packages a developer has been working in lately get indexed. The shared libraries that everything depends on — which the developer opened once during setup and has not touched directly since — do not.

This is the foundational problem: local indexing is recency-biased, while monorepo dependencies are centrality-biased. The files that most need to be in context — the shared interfaces, the base classes, the proto definitions, the event schemas — are exactly the files that get evicted from the local index because they are not in the developer's active workspace. The AI ends up reasoning about the leaves of the dependency tree while missing the roots.

Failure mode one: index size limits

The practical consequence of index size limits in a 100-package monorepo is not subtle. Developers working in a focused area of the monorepo — say, the payments domain — have their local index saturated by payments-related packages. When they need to write code that touches a shared library, the AI has partial or no knowledge of what that library actually does. The completions look plausible. The generated code compiles. But it is generated against the AI's training data approximation of what that kind of library typically does, not against what this library actually does in this codebase.

What local AI indexing covers vs. misses in a 100-package monorepo
What local AI indexing covers vs. misses in a 100-package monorepo:

  Packages total:         100
  Packages fully indexed: ~25 (files in active developer workspace)
  Packages partially indexed: ~30 (low-signal embeddings, no call graph)
  Packages not indexed:   ~45 (never opened, outside workspace bounds)

  What gets covered:
    -> The service the developer is currently working in
    -> A handful of recently-opened adjacent packages
    -> Top-level README and config files (but not deeply parsed)
    -> Some shared utilities if they were @-mentioned recently

  What gets missed — the packages that matter most:
    -> shared/      — cross-cutting platform libraries used by everything
    -> core/        — the oldest, most complex, most depended-upon packages
    -> proto/       — schema definitions that define service contracts
    -> infra/       — Terraform, Kubernetes, deployment definitions
    -> events/      — event schema definitions consumed across services
    -> libs/auth/   — authentication primitives referenced everywhere

  The missed packages are not the simple ones.
  They are the ones where wrong assumptions cause the most expensive bugs.

The pattern in the code block above is typical of a monorepo in the 50-150 package range, which covers most engineering organizations that have been running a monorepo for more than two years. The packages that fall outside the local index are disproportionately the ones that matter most for correctness — the shared platform code that forms the foundation of everything built on top of it. A bug introduced at the shared layer propagates to every consumer. An AI recommendation that ignores what the shared layer actually does is a bug waiting to ship.

Failure mode two: service boundary confusion

In a well-structured monorepo, packages have clear ownership and responsibility. The orders service manages order state. The fulfillment service consumes order events and coordinates shipping. The notification service observes fulfillment events and sends messages to customers. These boundaries exist in the code as import restrictions, event contracts, and API definitions. To a human developer, the boundary is obvious because the directory structure and package names reflect organizational intent.

To an AI tool working from a local index, the boundary is a retrieval artifact. If the AI's context for a task in orders-service does not include the event schema that fulfillment-service consumes — because events/ is not in the active workspace — the AI has no basis for knowing that a change to the orders-service event payload will break a downstream consumer. It will recommend the change with no warning. The tests pass locally because the tests in orders-service are the only tests in the index. The breakage surfaces in fulfillment-service's CI pipeline or in production.

This failure mode gets worse as teams follow monorepo best practices more rigorously. The more clearly defined the service boundaries, the more the AI has to cross them to give correct advice — and the more likely those boundaries are to exceed what the local index can span. Good monorepo architecture maximizes the problem that local indexing has with good monorepo architecture.

Failure mode three: cross-package retrieval

The third failure mode is the most insidious because it produces code that looks correct and passes initial review. When an AI generates an implementation without knowing that an identical or near-identical implementation already exists in another package, the result is a duplicate — functionally identical code in two places, with separate test suites, separate bug histories, and separate maintenance burdens. The AI did not fail to write the code. It failed to know the code already existed.

In a monorepo with 100 packages, duplicate detection is a meaningful problem. The whole point of a monorepo is to prevent duplication by consolidating shared code into libraries. An AI tool that cannot retrieve across the full package graph undermines this goal every time it generates something new instead of reusing what is already there. Over months, this produces monorepos with the structural appearance of consolidation and the practical reality of fragmentation — shared logic that is nominally in libs/ but practically duplicated across six service-level reimplementations the AI created without knowing about the canonical version.

Why using the monorepo correctly makes things worse

There is a perverse dynamic in monorepo scale: teams that use the monorepo correctly — splitting packages clearly, centralizing shared code aggressively, enforcing import boundaries — create more packages, with more cross-package dependencies, with more of the important code living in shared packages that are architectural foundations rather than active development targets. Each good architectural decision expands the gap between the packages a developer touches daily and the packages the AI needs to reason about correctly.

AI tool behavior: small repo vs. large monorepo — same tool, same model, different results
AI tool behavior: small repo vs. large monorepo — same tool, same model, different results:

  Dimension                  | Single-service repo     | 100-package monorepo
  ---------------------------+-------------------------+-------------------------------
  Index coverage             | ~100% of relevant files | 20-40% of relevant files
  Cross-package retrieval    | N/A — one package        | Frequently fails or omits
  Shared library awareness   | Automatic               | Depends on developer having
                             |                         | opened it recently
  Proto/schema changes       | Visible in workspace    | Usually not indexed
  Duplicate logic detection  | Works well              | Misses existing impl in other
                             |                         | packages — creates duplicates
  Convention consistency     | Reliable                | Degrades as scope grows
  Session quality over time  | Stable                  | Degrades as codebase grows
  New engineer session       | Equivalent to senior    | Severely degraded — no
                             |                         | accumulated local context
  Cross-service bug tracing  | Not applicable          | Cannot trace across package
                             |                         | boundaries not in workspace

  The tool that works brilliantly for a small project
  delivers progressively worse results as the monorepo grows —
  not because the model got worse, but because the indexing assumption broke.

The table above shows why this compounds over time. A monorepo that starts with 20 packages has a different AI-assistance quality profile than the same monorepo at 80 packages. As the organization adds packages, centralizes more shared code, and follows the monorepo's own organizational logic, the local index covers a progressively smaller fraction of what matters. The AI assistance that engineers raved about in the first year quietly degrades as the codebase matures — not because the tools got worse, but because the architecture got better in ways the tools do not support.

This also explains why AI assistance quality diverges between engineers. A senior engineer who built the billing service and has opened every relevant file in that domain over years of working in it has a richer local index for billing-domain tasks. A new engineer working on the same task starts with a nearly empty local index and gets generic assistance based on training data patterns. The AI amplifies existing familiarity rather than leveling the playing field — the opposite of the organizational benefit monorepos promise for knowledge sharing.

The per-developer index problem at team scale

The local indexing model has a second structural problem that is distinct from size limits: each developer's index is their own. Two developers working on adjacent packages in the same monorepo are working from independent context snapshots. There is no shared index across the team. There is no way for one developer's accumulated knowledge about how the packages relate — encoded in their session history, their recent file opens, their @-mentions — to transfer to another developer's session.

For a monorepo where the explicit goal is shared understanding and consolidated knowledge, per-developer AI indexing is architecturally backwards. The tool that is supposed to help the team work with the consolidated codebase siloes each developer's context back into per-person knowledge. A new engineer in a team of twenty gets AI assistance that is disconnected from the twenty developers' worth of codebase navigation that their colleagues have accumulated. The monorepo is shared. The AI's context about it is not.

This shows up concretely in code review. When a senior engineer reviews a PR that touches a shared library and finds that the AI-generated code duplicates something in a package the author never opened, the review comment is "this already exists in libs/platform/resilience." The author did not know. The AI did not know. The local index did not have it. The review cycle is the quality gate — but review cycles have a cost, and monorepos with 50 packages have a lot of surface area for this kind of miss.

What a managed org-level index does differently

The architectural difference between a local index and a managed org-level index is not just scale. It is the indexing model itself. A local index is a vector embedding of files in the active workspace, built on a developer's laptop, covering what has been recently accessed. A managed org-level index is a semantic analysis of the entire codebase, built on server-side infrastructure, covering everything regardless of what any individual developer has been working on.

Kognita's managed index is not file chunking at scale. It builds a call graph across the full monorepo, traces execution paths across package boundaries, identifies dependency relationships between packages, and constructs a behavioral model of how the system actually works — not how any individual package works in isolation. When a developer queries the MCP endpoint about a change they are considering, the index can trace the impact through the full dependency graph, including through packages that no developer has accessed in six months and that would never appear in a local index.

How Kognita's managed index handles cross-package context vs. local indexing
How Kognita's managed index handles cross-package context vs. local indexing:

  Scenario: Developer changes the currency field type in
  shared/proto/currency.proto from float to string.

  --- LOCAL INDEXING (Cursor / Copilot) ---

  Developer's workspace: packages/billing-service/
  Local index: covers billing-service/ + some recently opened files
  proto/ directory: not indexed (never opened in this session)

  AI suggestion: "looks good, the field is just a type change"
  Reality: 7 services consume currency.proto via generated clients.
           3 of them use parseInt on the rate field, which silently
           coerces "1.08" to 1 — a 100x pricing error.

  None of this is visible because the dependent services
  are outside the local workspace.

  --- KOGNITA MANAGED INDEX ---

  Index covers: all 100 packages, full call graph, dependency map
  Re-indexed: on every push to main, automatically

  Developer queries MCP: "What services consume the currency rate
  field from proto/currency.proto?"

  Kognita returns:
    -> billing-service: uses parseFloat (safe)
    -> pricing-service: uses parseInt on rate field (UNSAFE — line 34)
    -> checkout-service: uses parseFloat (safe)
    -> reporting-service: uses parseInt on rate field (UNSAFE — line 89)
    -> 3 other services: not directly consuming, indirect via billing-service

  Developer fixes the two parseInt callers before merging.
  The pricing error never reaches production.

The scenario in the code block above is a version of a bug that exists in real production systems right now. Proto field type changes that are syntactically valid but semantically breaking are one of the most common categories of cross-service bugs in systems that use protocol buffers or similar schema-defined contracts. The local index cannot catch this because the dependent services are outside the workspace. A managed index that covers the full monorepo catches it automatically, because the dependency graph extends across every package.

The re-indexing model matters as much as the coverage. Kognita re-indexes on every push to main. A developer who merged a shared library change at 9am does not need to notify their teammates that their local indexes are stale — by 9:15, the managed index reflects the change, and every developer's MCP session is working from the updated context. In a monorepo shipping multiple times per day, an index that is a day old is not acceptable context for an AI coding session. An index that updates on push is.

The cross-package call graph and why it changes retrieval quality

The most important difference between a file-level vector index and a semantic call graph is what each model can answer. A vector index answers "which files are semantically similar to this query?" A call graph answers "what happens at runtime when this function is called?" — including through package boundaries, event consumers, async job dispatchers, and indirect dependencies that are invisible in the file-level view.

For monorepo developers, the call graph question is the one that matters for most of the bugs that are expensive to find. "What services will this queue schema change affect?" is a call graph question. "Does a retry handler for this event already exist somewhere in the monorepo?" is a call graph question. "What code path executes when a user's subscription lapses, end to end?" is a call graph question. Vector similarity retrieval cannot answer any of these correctly when the relevant code is distributed across dozens of packages.

The reason AI coding tools degrade specifically on the most complex tasks in large monorepos is that the complex tasks are the ones where call graph context matters most. Simple scaffolding — generate a new service with the standard shape, write a CRUD handler for this endpoint — works fine from a local vector index because it does not require cross-package reasoning. The tasks that go wrong are the ones that require understanding what the system does across its full surface. Those are also the most expensive tasks to get wrong.

The shared context argument for team consistency

Beyond the individual developer session quality argument, there is a team consistency argument for a managed index that is worth making explicitly. In a local-indexing model, AI assistance quality varies by developer seniority and tenure — senior engineers with deep familiarity with the monorepo have more relevant files in their local index and get better AI assistance. Junior engineers and new hires get generic assistance based on training data.

A managed org-level index serves the same context to every developer session, regardless of tenure. The engineer who joined last week and the engineer who built the core platform library are querying the same indexed model of the system. The new engineer's AI session knows about the packages that exist, the patterns the team has established, and the cross-package dependencies that define the architecture — because that knowledge is in the shared managed index, not in any individual's accumulated session history.

This is the monorepo promise extended to AI assistance: shared knowledge, consolidated context, available to everyone on the team from day one. Local indexing cannot deliver this because local indexing is, by definition, per-developer. Managed indexing can.

MCP as the integration model

The managed index serves developers through the Model Context Protocol endpoint. Any MCP-compatible AI coding tool — Claude Code, Cursor, Windsurf, or a custom agent pipeline — connects to the Kognita MCP endpoint and can query the org-level index alongside its own local context. This means teams do not have to replace their existing AI coding tools to get managed index coverage. Cursor sessions that need cross-package context query Kognita through MCP. The local Cursor index handles the files the developer is actively working in. The managed index handles the cross-package context that the local index cannot reach.

For monorepo teams, this combination covers the real failure modes. The local index is fast and low-latency for the files you are currently editing. The managed index has the call graph and cross-package awareness that catches the bugs the local index misses. Neither tool alone is the right answer. The local index is too narrow. The managed index is not optimized for the file-level editing experience. Together, they cover the monorepo's actual context surface.

The MCP connection also means the managed index is available to non-developer AI tools. An agent pipeline that answers product manager questions about the system, a Jira MCP integration that connects sprint tickets to live codebase state, an incident response tool that traces a production anomaly through the system's behavioral graph — all of these benefit from the same managed index that developer sessions query. One index, multiple access surfaces, all reflecting the full monorepo structure automatically.

Final take

Monorepos did not break AI coding tools. AI coding tools were built for a different problem — individual developer productivity on single-service codebases — and that problem does not scale to the full package graph of a mature monorepo. The indexing assumption that works for 300 files breaks at 3,000. The per-developer session model that is fine for a solo developer conflicts with the shared-knowledge goal of the monorepo. The file-level similarity retrieval that answers "which file is relevant?" cannot answer "what happens at runtime when this change propagates through twelve packages?"

The tools that work on small projects degrade on the projects that matter most — the shared packages, the core platform, the oldest and most depended-upon code in the monorepo. Managed org-level indexing with cross-package call graph coverage is not a nice-to-have for large monorepos; it is the only indexing model that actually matches the monorepo's architecture. Kognita indexes the full monorepo structure, maintains semantic cross-package understanding, and serves it through an MCP endpoint that every developer session in the organization shares — automatically updated on every push, with no local indexing process required from any developer.