Claude Corner
Claude Keeps Guessing Why Your Code Looks That Way? Give It a Repo Graph That Rebuilds on Every Merge.
By Steven Sorensen · 27 Oct 2026

At a glance
- A short Python builder turns repo files, merged PRs and open issues into a graph Claude Code reads through the official MCP memory server.
- One
claude mcp addline connects it, and Claude queries it withsearch_nodesandopen_nodes. - A small GitHub Actions job rebuilds it on every push to
mainand weekly, then uploads an artifact instead of pushing.
You ask Claude why a config file looks the way it does. It reads the file, guesses and moves on. The real answer sits in a PR from last spring and the issue it closed, and Claude has no map of how code, docs, PRs and issues connect. A knowledge graph is that map.
Why it matters
The official MCP memory server stores a graph as entities, relations and observations in one JSONL file, and its tools let Claude search it and open nodes with their relations. Our read: a graph answers which merged PRs changed this file and which issues they closed in one lookup instead of a chain of searches. The hard part is keeping it true, so rebuild it on a schedule and give it an owner.
How to build a repo graph and keep it fresh
We ran the builder and memory server on a public repo on Oct 27. The claude mcp add line and workflow were checked against the docs, not run. The path is absolute because the server resolves a relative MEMORY_FILE_PATH against its own package folder.
# We have not run this script end to end on every platform. Run it from your repo root.
# Needs git, gh, python3, Claude Code. Optional install, pinned 5:03 PM ET Oct 27, Node 24 or later:
# npm install -g @modelcontextprotocol/server-memory@2.0.0
set -euo pipefail
mkdir -p scripts .github/workflows
cat > scripts/build_graph.py <<'EOF'
import json, os, subprocess as sp
def sh(*a): return sp.run(a, check=True, capture_output=True, text=True).stdout
def gh(*a): return json.loads(sh("gh", *a))
nodes, rels = {}, []
def node(n, kind, *obs): nodes.setdefault(n, {"type": "entity", "name": n, "entityType": kind, "observations": []})["observations"].extend(obs)
for f in sh("git", "ls-files").splitlines():
if f.endswith((".md", ".py", ".ts", ".go", ".yml")): node(f, "doc" if f.endswith(".md") else "code")
for pr in gh("pr", "list", "--state", "merged", "--limit", "50", "--json", "number,title,mergedAt,files,closingIssuesReferences"):
p = f"PR #{pr['number']}"; node(p, "pull_request", pr["title"], "merged " + pr["mergedAt"][:10])
rels += [{"type": "relation", "from": p, "to": f["path"], "relationType": "changes"} for f in pr["files"] if f["path"] in nodes]
for i in pr["closingIssuesReferences"]:
n = f"Issue #{i['number']}"; node(n, "issue", "closed by " + p); rels.append({"type": "relation", "from": p, "to": n, "relationType": "closes"})
for i in gh("issue", "list", "--state", "open", "--limit", "100", "--json", "number,title"): node(f"Issue #{i['number']}", "issue", i["title"], "open")
os.makedirs(".graph", exist_ok=True)
open(".graph/repo-graph.jsonl", "w").write("".join(json.dumps(r) + "\n" for r in [*nodes.values(), *rels]))
print(len(nodes), "nodes,", len(rels), "relations")
EOF
cat > .github/workflows/repo-graph.yml <<'EOF'
name: repo-graph
on:
push: {branches: [main]}
schedule: [{cron: "0 13 * * 1"}] # Monday 13:00 UTC, 9 AM EDT. Use 14 after Nov 1.
permissions: {contents: read, pull-requests: read, issues: read}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: python3 scripts/build_graph.py
env: {GH_TOKEN: "${{ github.token }}"}
- uses: actions/upload-artifact@v7
with: {name: repo-graph, path: .graph/repo-graph.jsonl}
EOF
python3 scripts/build_graph.py
claude mcp add repo-graph --env MEMORY_FILE_PATH="$PWD/.graph/repo-graph.jsonl" -- npx -y @modelcontextprotocol/server-memory@2.0.0
# echo "/scripts/build_graph.py @your-org/graph-owners" >> .github/CODEOWNERS
The workflow runs on every push to main plus a weekly Monday cron. Actions cron runs in UTC, so 9 AM ET is 13:00 UTC during daylight time and 14:00 UTC after Nov 1. The job never pushes. It uploads an artifact you pull with gh run download -n repo-graph -D .graph. To commit the graph, have the job open a small PR instead, which needs contents: write, pull-requests: write and the repo setting Allow GitHub Actions to create and approve pull requests.
The commented last line adds a CODEOWNERS owner for the builder. Swap in your own team, then uncomment it so changes to the builder request their review. Each rebuild starts empty, so deleted files and old closed issues drop out. Our recommendation: keep a 50 PR window, and once a month spot check ten nodes and add an entity type only when the graph missed a real question.
Gotchas
- Claude's own notes do not survive. The next rebuild replaces the file, so put lasting facts in docs or PR descriptions where the builder finds them.
- Local scope by default. Add
--scope projectto share the server through.mcp.json. Teammates approve it on first run.
Sources:
- Connect Claude Code to tools via MCP, Claude Code docs
- Knowledge Graph Memory Server
- Events that trigger workflows, schedule
- Managing GitHub Actions settings for a repository
- About code owners
Recommended AI prompt
Copy this paragraph into ChatGPT, Claude, Gemini, Grok, or whatever AI you use.
I want Claude Code to see how my repo's code, docs, pull requests and issues connect through the official MCP memory server. Ask me which folders matter, how many merged PRs of history I want, and who should own the graph. Then adapt a short builder that writes the memory server's JSONL format, give me the exact claude mcp add command with an absolute file path, and a GitHub Actions workflow that rebuilds weekly and on push to main and uploads an artifact or opens a pull request, never pushing to main. Use only commands from the Claude Code, MCP servers and GitHub docs, and link each one.