June 10, 2026
How We Turned a 500K-Line Codebase Into an AI Knowledge Graph
Six months ago, our AI coding assistant had a problem.

By Sachin Kasana
3 min read
Ask it:
"What services are affected if I modify the payment workflow?"
And it would confidently return an answer that was only partially correct.
The issue wasn't the LLM.
The issue was context.
Our repository contained more than 500,000 lines of code spread across APIs, microservices, event consumers, scheduled jobs, shared libraries, and infrastructure code. Non members can read here
Traditional vector search could find relevant files.
But it struggled to understand how everything was connected.
So we stopped treating our codebase as a collection of documents.
Instead, we turned it into a knowledge graph.
The result was an AI system that could answer architectural questions, perform impact analysis, identify hidden dependencies, and help engineers navigate a massive codebase with much greater accuracy.
Here's how we built it.
Why AI Struggles With Large Codebases
Most AI coding tools rely heavily on semantic search.
The process looks roughly like this:
User Question
↓
Vector Search
↓
Relevant Files
↓
LLM
↓
AnswerUser Question
↓
Vector Search
↓
Relevant Files
↓
LLM
↓
AnswerThis works surprisingly well for small projects.
However, large enterprise systems are different.
Imagine the following dependency chain:
UserService
↓
BillingService
↓
PaymentGateway
↓
StripeAdapterUserService
↓
BillingService
↓
PaymentGateway
↓
StripeAdapterNow ask:
What breaks if StripeAdapter changes?
Vector search may retrieve:
StripeAdapter.ts
PaymentGateway.tsStripeAdapter.ts
PaymentGateway.tsBut it may completely miss:
BillingService
UserService
OrderProcessing
RefundWorkflowBillingService
UserService
OrderProcessing
RefundWorkflowThe problem is that vector search understands similarity.
It does not naturally understand relationships.
Software systems are built from relationships.
Functions call functions.
Classes inherit classes.
Services publish events.
Consumers subscribe to events.
Modules import modules.
Without those relationships, AI sees code fragments instead of architecture.
Thinking About Code as a Graph
Once we stepped back, the solution became obvious.
A codebase is naturally a graph.
Function
│
calls
▼
Function
Class
│
inherits
▼
Class
Service
│
publishes
▼
Event
Consumer
│
subscribes
▼
EventFunction
│
calls
▼
Function
Class
│
inherits
▼
Class
Service
│
publishes
▼
Event
Consumer
│
subscribes
▼
EventEvery node represents something meaningful.
Examples:
File
Class
Method
Function
Service
Event
Database Table
API Endpoint
QueueFile
Class
Method
Function
Service
Event
Database Table
API Endpoint
QueueEvery edge represents a relationship.
Examples:
CALLS
IMPORTS
INHERITS
PUBLISHES
SUBSCRIBES
READS
WRITES
DEPENDS_ONCALLS
IMPORTS
INHERITS
PUBLISHES
SUBSCRIBES
READS
WRITES
DEPENDS_ONOnce you model the repository this way, answering architectural questions becomes much easier.
Extracting Structure From the Repository
The first step was building a parser.
For TypeScript services, we used ts-morph to walk the Abstract Syntax Tree (AST).
import { Project } from "ts-morph";
const project = new Project();
project.addSourceFilesAtPaths("src/**/*.ts");
for (const file of project.getSourceFiles()) {
const imports = file.getImportDeclarations();
imports.forEach((imp) => {
console.log({
source: file.getBaseName(),
target: imp.getModuleSpecifierValue(),
relation: "IMPORTS"
});
});
}import { Project } from "ts-morph";
const project = new Project();
project.addSourceFilesAtPaths("src/**/*.ts");
for (const file of project.getSourceFiles()) {
const imports = file.getImportDeclarations();
imports.forEach((imp) => {
console.log({
source: file.getBaseName(),
target: imp.getModuleSpecifierValue(),
relation: "IMPORTS"
});
});
}This gave us relationships like:
UserService
IMPORTS
BillingService
IMPORTS
PaymentGatewayUserService
IMPORTS
BillingService
IMPORTS
PaymentGatewayNext, we extracted:
- Function calls
- Class inheritance
- Interface implementations
- Event publishers
- Event subscribers
- API routes
- Database interactions
The goal wasn't to index code.
The goal was to map relationships.
Building the Knowledge Graph
After extracting relationships, we stored them in Neo4j.
Example:
CREATE
(a:Service {name: "UserService"})
-[:DEPENDS_ON]->
(b:Service {name: "BillingService"})CREATE
(a:Service {name: "UserService"})
-[:DEPENDS_ON]->
(b:Service {name: "BillingService"})Another example:
CREATE
(a:Service {name: "BillingService"})
-[:CALLS]->
(b:Service {name: "PaymentGateway"})CREATE
(a:Service {name: "BillingService"})
-[:CALLS]->
(b:Service {name: "PaymentGateway"})Very quickly the graph started growing.
Instead of thousands of isolated files, we now had:
20,000+ Nodes
85,000+ Relationships20,000+ Nodes
85,000+ RelationshipsAnd that's where things became interesting.
Impact Analysis Became Trivial
Previously, answering this question was painful:
What will break if I modify PaymentGateway?
An engineer would manually inspect:
- Imports
- References
- Service calls
- Event consumers
- Documentation
Sometimes for hours.
With a graph:
MATCH p=(n)-[*]->(m)
WHERE n.name = "PaymentGateway"
RETURN pMATCH p=(n)-[*]->(m)
WHERE n.name = "PaymentGateway"
RETURN pThe answer appeared instantly.
The graph showed every downstream dependency.
This became one of the most valuable capabilities we built.
Combining Graph Search With LLMs
The graph alone was useful.
The real breakthrough happened when we connected it to an LLM.
Architecture:
User Question
↓
Graph Query
↓
Relevant Subgraph
↓
Code Retrieval
↓
LLM
↓
AnswerUser Question
↓
Graph Query
↓
Relevant Subgraph
↓
Code Retrieval
↓
LLM
↓
AnswerSuppose a developer asks:
Which services are affected by payment retries?
The workflow becomes:
Step 1
Identify entities.
payment retries
PaymentService
RetryProcessorpayment retries
PaymentService
RetryProcessorStep 2
Query graph relationships.
PaymentService
↓
RetryProcessor
↓
NotificationService
↓
BillingServicePaymentService
↓
RetryProcessor
↓
NotificationService
↓
BillingServiceStep 3
Retrieve actual code.
payment.service.ts
retry.processor.ts
billing.service.tspayment.service.ts
retry.processor.ts
billing.service.tsStep 4
Send only relevant context to the LLM.
Instead of dumping hundreds of files into a context window, we provide a highly connected subset of the repository.
The answer quality improved dramatically.
What Surprised Us Most
We originally built the graph to help AI.
Unexpectedly, humans started using it more than AI.
Engineers began asking questions like:
- Which services depend on this API?
- What events trigger this workflow?
- What consumes this queue?
- Which modules are tightly coupled?
- What breaks if we remove this service?
Previously these answers required tribal knowledge.
Now they could be queried.
The graph became a living architecture map.
Beyond RAG
Most teams trying to improve AI coding assistants focus on better retrieval.
More embeddings.
More vector databases.
More chunking strategies.
Those improvements help.
But they don't solve the core issue.
Software systems are not collections of documents.
They are networks of relationships.
A knowledge graph captures those relationships explicitly.
That allows AI systems to reason about architecture instead of merely searching text.
Real-World Applications
Once the graph existed, we found dozens of use cases.
Impact Analysis
Identify everything affected by a code change.
AI Code Review
Provide dependency-aware context to reviewers.
Migration Planning
Understand upgrade impact across services.
Developer Onboarding
Help new engineers understand system architecture.
Technical Debt Discovery
Identify tightly coupled components.
Architecture Documentation
Generate diagrams directly from the graph.
Final Thoughts
Most AI coding assistants today are excellent at understanding files.
Very few truly understand systems.
That's an important distinction.
A repository isn't just source code.
It's a network of dependencies, workflows, services, events, and business logic.
Once we modeled those relationships as a knowledge graph, both engineers and AI systems became significantly better at navigating the codebase.
The biggest lesson was simple:
AI doesn't just need more context. It needs better context.
And in large codebases, relationships are often more valuable than the code itself.
If you're building AI tools for software engineering, don't just index your repository. Map it. The results can be surprisingly powerful.