Your Code Knows What Changed. But Does It Know Why?
DEV Community

Your Code Knows What Changed. But Does It Know Why?

AI can write a pull request in seconds. But when that pull request touches a piece of code written three years ago, there is a much harder question: Why does this code exist? That answer might be buried across 47 commits, 12 pull requests, an old incident, a Slack conversation nobody remembers, and one engineer who left the company six months ago. This is becoming one of the biggest problems in AI-assisted software engineering. Because writing code is getting cheaper. Understanding code is not. The Codebase Is Not the Whole System Consider this: if (user.isLegacy && !featureEnabled) { return fallback(); } Looks suspicious. Maybe it's dead code. Maybe someone forgot to clean it up. So an AI coding agent suggests: - if (user.isLegacy && !featureEnabled) { - return fallback(); - } The tests pass. The PR looks clean. You merge it. Three hours later, production breaks for a subset of customers. Now you're asking a very different question: Who knew why that code was there? The answer might have been hiding in the engineering history. Git Knows What Changed Git is incredible. It can tell you: What changed? Who changed it? When did they change it? But those aren't always the questions engineers need answered. We need: Why did it change? What problem was it solving? What depends on it? What happens if I change it? Has this failed before? Who understands this part of the system? Was this introduced because of an incident? What happened the last time someone touched it? The problem isn't that this information doesn't exist. It does. It's just fragmented. The Hidden Knowledge Graph Inside Every Codebase Every engineering organization already has a graph. They just don't usually call it one. A pull request is connected to commits. Commits are connected to files. Files are connected to services. Services are connected to deployments. Deployments are connected to incidents. Incidents are connected to fixes. Fixes are connected to engineers. Engineers are connected to decisions. Decisions are connected to outcomes. Like this: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Issue β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ solved by β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PR β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ modified β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Code β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ depends on β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Service β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ affected β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Incident β”‚ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ caused β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Fix β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ The valuable information isn't just the nodes. It's the edges. Why Relationships Matter Suppose you're looking at: checkout.ts A traditional code search might tell you: checkout.ts β”œβ”€β”€ imported by payment.ts β”œβ”€β”€ imported by cart.ts └── imported by order.ts Useful. But an Engineering Graph could tell you: checkout.ts β”‚ β”œβ”€β”€ modified by PR #842 β”‚ β”œβ”€β”€ solved issue #421 β”‚ β”œβ”€β”€ reviewed by Maya β”‚ └── followed incident #91 β”‚ β”œβ”€β”€ depends on retry.ts β”‚ └── introduced after checkout timeout β”‚ β”œβ”€β”€ affects payment-service β”‚ └── changed 7 times during payment incidents Now you aren't just looking at code. You're looking at the story of the code. And that story can change the decision you make. Give AI the Story, Not Just the Source File This is where things get interesting. Imagine asking an AI agent: "Can I remove this retry?" With only the source code, it might respond: "The retry appears redundant and could potentially be removed." That's not useful. Now give it the engineering context: retry.ts ↓ used by checkout.ts ↓ introduced in PR #842 ↓ PR linked to checkout timeout #421 ↓ incident #91 involved the same request path ↓ three subsequent PRs modified retry behavior Now the answer could be: "I would not remove it yet. This retry mechanism was introduced to address a checkout timeout and has been modified several times after production issues. I'd inspect incident #91 and the related PRs before changing it." Same model. Different context. Better context β†’ better reasoning. That's the fundamental idea. Let's Build One You don't need a giant infrastructure project to understand the architecture. Start with a tiny graph. type NodeType = | "file" | "commit" | "pull_request" | "issue" | "service" | "incident" | "person"; type Relationship = | "MODIFIED" | "SOLVED" | "DEPENDS_ON" | "AUTHORED_BY" | "REVIEWED_BY" | "AFFECTED" | "CAUSED"; Our nodes: type Node = { id: string; type: NodeType; name: string; metadata?: Record ; }; And edges: type Edge = { from: string; to: string; relationship: Relationship; metadata?: Record ; }; Now we can represent: const edges: Edge[] = [ { from: "pr:842", to: "file:checkout.ts", relationship: "MODIFIED", }, { from: "pr:842", to: "issue:421", relationship: "SOLVED", }, { from: "file:checkout.ts", to: "file:retry.ts", relationship: "DEPENDS_ON", }, { from: "incident:91", to: "service:checkout", relationship: "AFFECTED", }, ]; That's already enough to start answering questions that plain text search struggles with. The Agent Should Query the Graph Before It Acts Now put an AI agent on top. A traditional agent looks something like: Goal ↓ Observe ↓ Decide ↓ Act ↓ Check ↓ Repeat Useful. But it starts every task with roughly the same level of ignorance. Give it the graph: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Goal β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Query Graph β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Observe β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Decide β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Execute β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Verify β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Update Graph β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ └──────→ Next task Now the agent doesn't just observe the repository. It observes what happened before. The Agent Can Learn Without Retraining This distinction is easy to miss. We normally think an AI system improves like this: Better model ↓ More training ↓ Better weights ↓ Better performance But agents have another path: Better experience ↓ Better graph ↓ Better context ↓ Better decisions ↓ Better experience The underlying model doesn't have to change. The environment around the model improves. That's powerful. But There's a Catch We shouldn't blindly turn agent activity into permanent knowledge. Imagine an agent tries: Increase retry count: 2 β†’ 10 The test passes. The agent records: "10 retries fixes checkout." Next week another agent sees that "knowledge" and does the same thing. Now you've created a feedback loop that makes the system confidently worse. That's why learning systems need evidence. Knowledge Needs Evidence Instead of: Agent says it worked. Store: Code changed ↓ Unit tests passed ↓ Integration tests passed ↓ PR merged ↓ Deployment succeeded ↓ No incident followed Now your graph can represent: type Outcome = { status: "success" | "failure"; confidence: number; evidence: string[]; }; For example: const outcome: Outcome = { status: "success", confidence: 0.92, evidence: [ "unit tests passed", "integration tests passed", "pull request merged", "deployment succeeded", ], }; The system isn't just remembering. It's remembering why it believes something. That difference becomes enormous at scale. Not Everything Is Knowledge An agent might make 50 observations while solving one problem. We shouldn't permanently promote all 50 into "truth." There are levels: Observation ↓ Evidence ↓ Repeated pattern ↓ Validated relationship ↓ Reusable knowledge ↓ Heuristic For example: Observation checkout.ts imports retry.ts Event PR #842 modified checkout.ts Outcome Tests passed after the change. Knowledge checkout.ts frequently changes with retry.ts. Heuristic When checkout timeout tests fail, inspect retry behavior first. That's a much safer learning architecture than dumping every agent thought into a vector database. And Failure Is Data Too This might be the most underrated part. Suppose an agent tries two approaches: Task: Fix checkout timeout Approach A ↓ FAILED Approach B ↓ SUCCEEDED A normal system might only remember B. A learning system should remember both. Task β”‚ β”œβ”€β”€ attempted β†’ Approach A β”‚ └── FAILED β”‚ └── attempted β†’ Approach B └── SUCCEEDED That's negative knowledge. The next agent doesn't have to walk into the same wall. It can know: "This approach was already tried. It failed." The graph remembers the dead ends. This Isn't RAG RAG is incredibly useful. But RAG and an Engineering Graph solve different problems. RAG asks: What information is relevant to this question? A graph can ask: How are these things related? Imagine asking: Why is checkout.ts high risk? A document retriever might find: PR #842 PR #811 PR #743 A graph can reconstruct: checkout.ts β”‚ β”œβ”€β”€ modified by PR #842 β”‚ β”œβ”€β”€ authored by Maya β”‚ └── reviewed by Bobby β”‚ β”œβ”€β”€ related to retry.ts β”‚ β”œβ”€β”€ affected checkout-service β”‚ └── connected to incident #91 └── caused by previous checkout change That's not just retrieval. That's contextual reasoning over relationships. And the two technologies work beautifully together: User Question ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Semantic Searchβ”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ Relevant entities ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Graph Traversalβ”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ Relationships ↓ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ LLM β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ Evidence-backed answer Now Scale It Beyond One Repository This is where the idea gets really interesting. Imagine your engineering graph continuously absorbs: GitHub ↓ Commits ↓ Pull Requests ↓ Code ↓ Services ↓ Deployments ↓ Incidents ↓ Fixes ↓ Agent Experiences Then add: Jira Slack Confluence Datadog Architecture decisions Human feedback Eventually you're not building another code search engine. You're building a living model of how the engineering organization works. You can ask: Why was this architecture chosen? Who understands this service? What usually breaks when we change it? Which files are high risk? What has already been tried? Which engineers solved similar problems? What happened after the last deployment? What should an AI agent inspect before touching this service? Those answers don't exist in any single system. They emerge from the connections between systems. The R

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.