DEV Community

N3MO
N3MO

Posted on

AI agents are hallucinating on your monorepo. Here is why I used PostgreSQL to fix it.

We need to talk about the elephant in the room: AI coding agents are failing on massive codebases.

Whether you are using Cursor, Claude, or Windsurf, if you point an LLM at a 30,000-file enterprise monorepo and ask, "What breaks if I rename this utility function?", the AI will likely hallucinate. Why? Because LLMs rely on flat-file text search (grep) or semantic vector embeddings, neither of which actually understand the structural call graph of your code.

To fix this, developers have started building local AST (Abstract Syntax Tree) parsers. But I noticed a fatal flaw in the popular ones: they were dumping the entire AST graph into static JSON files.

If you try to load a 500,000-edge JSON graph into memory to traverse a dependency tree, your machine is going to choke.

I knew there had to be a better way to scale this, so I built N3MO.

The N3MO Approach: PostgreSQL + Recursive CTEs

Instead of relying on fragile JSON files, I decided to treat source code like enterprise data. N3MO uses Tree-sitter to parse 27 different programming languages into ASTs, but instead of keeping them in memory, it batch-inserts the symbols and function calls directly into PostgreSQL.

Why Postgres? Because of Recursive CTEs (WITH RECURSIVE).

Instead of writing a slow Breadth-First Search algorithm in Python to figure out the "blast radius" of a function change, N3MO pushes the traversal directly down to the C-optimized database engine.

-- A simplified version of how N3MO calculates blast radius in milliseconds
WITH RECURSIVE impact_chain AS (
    SELECT id, source_symbol_id, 1 as depth, ARRAY[source_symbol_id] as path
    FROM calls WHERE resolved_symbol_id = %s

    UNION ALL

    SELECT c.id, c.source_symbol_id, ic.depth + 1, ic.path || c.source_symbol_id
    FROM calls c
    JOIN impact_chain ic ON c.resolved_symbol_id = ic.source_symbol_id
    WHERE ic.depth < 4 AND NOT (c.source_symbol_id = ANY(ic.path))
)
Enter fullscreen mode Exit fullscreen mode

By leveraging Postgres cycle guards (c.source_symbol_id = ANY(ic.path)) to prevent infinite recursive loops, N3MO can trace the blast radius of a function across tens of thousands of files in under 50 milliseconds.

Connecting it back to AI (MCP)
Once the code graph was living in Postgres, I built an MCP (Model Context Protocol) server. Now, when Claude or Cursor looks at my code, it doesn't just read textβ€”it queries the PostgreSQL database to understand the exact, deterministic call graph before it refactors anything.

Join the Community
N3MO is completely open-source. We are actively looking for contributors to help us add more Tree-sitter languages, optimize our database migrations (Alembic), and build out the AI layer.

⭐ Check out the code: https://github.com/RajX-dev/N3MO
πŸ’¬ Talk Architecture: https://discord.gg/cTgZKHf2G
If you are tired of your LLM agents hallucinating, drop the JSON files and come use a real database.

Top comments (0)