Deterministic, local-first memory and guardrails for AI coding agents β with no LLM in the hot path.
One call tells your agent the code a task touches; one gate tells it what's unsafe to change.
Grounded in static analysis. No API key. Same answer every time.
A real, unedited recording β the published openlore on a fresh clone of ripgrep. install wires your agent and indexes the repo live β 235 files, 2,978 functions, 4,329 call edges in 14 seconds, no API key β orient returns the code a task touches β review catches a signature change that left 39 callers stale β prove projects the payoff. Re-record it yourself: docs/openlore-demo.tape.
Install Β· What you get Β· Benchmarks Β· Governance Β· How it works Β· vs. Alternatives Β· Docs Β· Community
Full table of contents
Start here: Install in one command Β· What you get Β· Is OpenLore for you? Β· See it in action Β· 5-Minute Quickstart Β· What it costs to adopt
Evaluate it: Value Scorecard (wins and losses) Β·
OpenLore vs. AlternativesΒ·
Known LimitationsΒ·
We dogfood our own governance
Understand it: How It Works Β· Governance Β· Core Features Β· Languages & IaC Β· Federation, Interop & PR review
Use it: Agent Cheat Sheet Β· Claude Code Skill Β· Requirements Β· Documentation Β· Development Β· Community
AI coding agents are powerful but amnesiac and ungoverned. Every task starts by re-reading the same files to rediscover structure; every long session quietly drifts toward confident-but-stale assumptions; and nothing tells the agent when a change is about to break a contract, cross an architectural boundary, or open a path into sensitive code.
OpenLore fixes both halves. It runs a one-time static analysis of your codebase and keeps a navigable knowledge graph β call structure, types, tests, decisions, IaC, and spec drift β incrementally fresh as you edit. Agents query it through MCP tools (or the CLI) to start every task already oriented, and to certify a change before it lands. It is deterministic and local-first β no LLM in the hot path β so the same question returns the same grounded answer, and an agent is told when a fact has gone stale instead of served a confident guess.
npm install -g openlore && openlore install
That one command auto-detects your agent (Claude Code, Cursor, Cline, Continue, AGENTS.md), wires it to call orient() automatically,
registers the MCP server, and
builds the indexβ no API key, no config, no questions asked. Then ask your agent:
orient("add a payment method")
β¦and it begins the task already knowing the relevant functions, their callers, the matching specs, the tests, and the risk of changing each one β in a single call. Full setup, variants, and verification: 5-Minute Quickstart.
Zero config, everything discoverable.Core value needs no keys. To see everyopt-incapability β embeddings, covering surfaces, the commit gate, the spec store, and more β whether each is active, and the one command to turn it on, run.openlore features
OpenLore does two things for an agent, both deterministic and local β it remembers your architecture so every task starts oriented, and it governs what the agent changes before the change lands.
π§ Memory β start every task already oriented
Persistent architectural memoryβorient()
once; agents stop re-deriving the system from dozens of file reads, across sessions. Anchored notes and decisionssurvive refactors: a renamed or moved symbol carries its memory forward at the nextanalyze
(withcarriedAcross
provenance) instead of orphaning it.One-call orientationβorient(task)
returns the relevant functions, their callers, matching spec sections, and insertion-point candidates in a single call.~430Β΅s p50 on a 15k-node graph.One unified graphβ application code,** Infrastructure-as-Code**, and** architectural decisionsall project onto the same node/edge primitives, so a single traversal answers questions that span all three. Test-impact selection & dead-code**β "I changedparseConfig()
β which tests should I run?" by backward call-graph reachability; cross-language mark-and-sweep finds what's dead, confidence-tagged, never deletion authority.Context-freshness tracking (Epistemic Lease)β an agent istoldwhen a cached fact has gone stale (context aged, repo moved) instead of acting on a confident guess.
π‘οΈ Governance β guardrails on what the agent changes
Change-impact certificateβchange_impact_certificate
flags when a diffnewly opens a path into a sensitive boundary you declared(reachable after the change but not before) β differential, deterministic, no LLM.** Breaking-change verdict**βcertify_public_surface
classifies every changed exportbreaking / non-breaking / potentially-breaking
over a diff and names the in-repo consumers each break hits; conservative by construction, never silently "safe".Architecture invariants, pre-editβcheck_architecture
answers "may a file under A import B?" against your declared layer/forbidden rulesbeforethe import is written β cross-language.Grounded claims, not guessesβverify_claim
returns a deterministicconfirmed / refuted / unverifiable
verdict with a citation receipt before an agent asserts "X is dead" or "Y is safe to change".One commit gateβopenlore enforce
resolves every governance finding through yourenforcement.policy
and blocks only on what you classblocking
(advisory by default, no API key). Decisions are recorded, gated, and synced into living specs; spec/codedrift detected in milliseconds.
π Honest by construction β β26% agent round-trips on deep traces in large repos, with the losses published next to the wins; every public claim traces to a command you can run. Pure static analysis: no API key, no network, same answer every time.
The fastest way to evaluate a tool is to find out quickly that it isn't for you. So:
β
Strong fit |
A codebase big enough that you can't hold it in your head β and neither can the model. Private or niche code the model never memorized. Long agent sessions where stale assumptions compound. Polyglot repos, or code plus the IaC that deploys it. Anywhere "the agent changed something it shouldn't have" is a real cost, not a hypothetical. |
π€ Try it, but measure |
Mid-size repos and mixed workloads. The orientation win scales with size and depth, so run (seconds, no API key) before you commit to it.openlore prove --estimate |
β Probably not yet |
A small repo the model already knows, answering shallow questions like "who calls parseArgs " β your agent's built-in search is cheaper, and we
perform the refactor, OpenLore is the wrong layer β it locates and certifies, it never edits your code. |
One idea, if you only read one line of this README: an agent's expensive failure mode isn't ignorance β it's confidence. A model that doesn't know a function exists will go look. A model that "knows" a stale fact will confidently build on it, and you pay for that at review time. OpenLore is built so the agent can be told "that fact is stale" and "this change opens a path you said was sensitive" β deterministically, from the graph, with no second model in the loop guessing about the first.
The same task, twice. Ask an agent to add a flag to a command it has never seen:
| Without OpenLore | With OpenLore | |
|---|---|---|
| Opening move | ||
| grep a guessed name β open a file β wrong layer β open three more | orient("add a --since flag to the blast-radius command") |
|
| What it learns | ||
| file contents, one at a time, in whatever order it guessed | the functions, their callers, the matching spec sections, and the ranked insertion points β in one call | |
| What it misses | ||
| the five callers living in files it never opened | every caller the graph can see β statically resolvable ones, at least; dynamic dispatch is still nobody's friend | |
| Before it commits | ||
| "looks right to me" | blast_radius β tests to run; certify_public_surface β the consumers this signature change breaks, by name |
The measured effect of that shape change on deep, multi-hop tasks: 25 β 16 round-trips on excalidraw, β26% aggregate. It is not magic β it is the difference between rediscovering structure per task and querying it. Full numbers, including where this doesn't pay off, in the Value Scorecard.
orient("add a --since flag to the blast-radius command") β one query replaces most exploratory file reads
Real output β openlore orient --json "add a --since flag to the blast-radius command"
, run on this repo (abridged: fields elided, caller lists flattened to names):
{
"relevantFiles": ["src/cli/commands/blast-radius.ts", "src/core/services/mcp-handlers/blast-radius.ts"],
"relevantFunctions": [
{ "name": "computeBlastRadius", "filePath": "src/core/services/mcp-handlers/blast-radius.ts",
"signature": "async function computeBlastRadius(input: BlastRadiusInput): Promise<BlastRadiusBriefing>",
"fanIn": 5, "isHub": true, "language": "TypeScript" }
],
"callPaths": [
{ "function": "computeBlastRadius",
"callers": ["handleBlastRadius", "computeImpactCertificate", "runBlastRadiusCli",
"composeReview", "collectGovernanceFindings"] }
],
"landmarks": [
{ "name": "runBlastRadiusCli", "hops": 1,
"signals": [{ "label": "orchestrator", "evidence": { "fanOut": 11 } },
{ "label": "volatile", "evidence": { "level": "medium", "commits": 6, "coChangedWith": 5 } }] }
],
"insertionPoints": [
{ "rank": 2, "name": "computeBlastRadius", "role": "hub", "strategy": "cross_cutting_hook",
"reason": "computeBlastRadius is called by 5 functions -- adding logic here affects the entire callsite surface." }
],
"suggestedTools": ["record_decision", "analyze_impact", "get_subgraph", "check_spec_drift"]
}
The agent knows exactly where to look, what it touches, and what's risky to touch β before reading a single file. Every field is computed from the graph; nothing is inferred by a model.
Gate a risky change before it lands β deterministic, in CI or a pre-commit hook
openlore impact-certificate --base main # does my diff open a new path into a declared sensitive boundary?
openlore certify-public-surface --base main # did I break a consumer's public API contract?
openlore blast-radius # callers/layers touched, tests to run, specs & decisions that drift
openlore enforce --hook # one gate; blocks only on findings you've classed `blocking`
No LLM, no API key β the same grounded answer every run. Advisory by default; you opt into blocking per finding.
OpenLore only earns its place if an agent with it reaches a correct answer for less total cost than the same agent without it. We measure that inequality and publish it β wins and losses. Numbers are from the Spec 14 agent benchmark (claude -p
, sonnet, N=4 medians, pinned SHAs, --strict-mcp-config
isolating each arm), measured 2026-06-01.
| Scenario (task Γ repo) | Cost Ξ | Round-trips Ξ | Correctness | Verdict |
|---|---|---|---|---|
| Large/unfamiliar repo Β· deep "how does X flow through Y" (its target) | ||||
| β7% to β21% | ||||
| β26% | ||||
| 100% = 100% | β helps β and the win grows with repo size | |||
| Small/familiar repo Β· shallow "who calls X" | task-dependent (Round 1: +43%) | |||
| +38% | ||||
| 100% = 100% | β often adds overhead β measure with openlore prove |
Re-confirmed live 2026-06-03 (N=2):the deep-task winreproducesβ okhttpβ13%. The small/familiar case istask-dependent, not a flat loss: same repo class, opposite outcomes (chalkβ32%win vs express+59%loss). Don't guess from our repos β runon yours.openlore prove
Deep-trace detail β the win scales with codebase size (cost Ξ; round-trips WITHOUT β WITH):
| Repo (size) | Cost Ξ | Round-trips |
|---|---|---|
| excalidraw (~640 files) | β21% | |
| 25 β 16 | ||
| tokio (~790 files) | β21% | |
| 17 β 13 | ||
| okhttp | β13% | |
| 13 β 11 | ||
| django (~3k files) | β7% | |
| 21 β 15 | ||
| gin (110 files, smallest) | +4% (βeven) | |
| 10 β 9 |
Where it helps β and where it doesn't:
Helps: large, unfamiliar, or private codebases the model hasn't memorized; deep multi-hop questions; long sessions where re-reading an ever-growing context compounds. The hardest-to-game signal is**round-trips: β26%, fewer on every deep task.**Doesn't (yet): small, famous repos already in the model's weights answered by a shallow query β there's no orientation tax to remove, so the tool surface is pure overhead.
Honesty contract.We never publish a savings number the benchmark didn't produce; we always show the loss cases next to the wins; the scorecard is date-stamped and re-measured after each optimization phase. Every public token claim traces to a command you can run in this repo β if it doesn't reproduce, treat it as marketing and call it out. Full methodology and per-task numbers:[docs/AGENT-BENCHMARKS.md]. Plumbing latency (orient ~430Β΅s p50) is separate and real:[scripts/BENCHMARKS.md].
Prove it on your repo β no API key needed.openlore prove --estimate
projects the orientation tax from your own call graph in seconds (zero API key, zero network); plainopenlore prove
runs the full measured WITH/WITHOUT pass (needsclaude
- a key). Add
--json
(CI-consumable),--markdown
(a paste-ready scorecard block + a shields.io badge for your README), or--save
(a dated record under.openlore/prove/
). An estimate is labeledestimate
everywhere and never presented as a measurement. Details:[docs/AGENT-BENCHMARKS.md].
npm install -g openlore
cd /path/to/your-project
openlore install # detect your agent, wire it up, AND build the index
That single command:
Auto-detects which agent surfaces are present (Claude Code, Cursor, Cline, Continue, AGENTS.md) and wires each one to callorient()
β no manualCLAUDE.md
editing.Registers the MCP server so it starts automatically when your agent launches (you don't runopenlore mcp
yourself).Builds the index(init
+analyze
β a keyword/BM25 graph, no network needed) soorient()
returns real results in your very first session.Wires task-scoped orientation(Claude Code): aUserPromptSubmit
hook orients each new prompt and injects a bounded, ignorable orientation blockbeforethe first turn β so the common task begins already oriented without a manualorient()
call. A deterministic relevance gate keeps it out of the small/familiar case; disable withcontextInjection.mode: "off"
.
openlore install --no-analyze # wire surfaces only; build the index later
openlore install --dry-run # preview every change without writing
openlore doctor # verify config, index, MCP wiring, and embedding setup
Zero-interaction by design. Nothing prompts you and nothing touches your repo on npm install
. openlore install
never asks a question. And if an agent wires the MCP server without a prior install, the server self-bootstraps β it builds the index once in the background on first run (opt out with OPENLORE_NO_AUTO_ANALYZE=1
).
Stays current automatically. A once-a-day, non-blocking "update available" line; upgrade with ** openlore update** (detects npm vs. Homebrew vs. npx). Silence with
OPENLORE_NO_UPDATE_NOTIFIER=1
. The MCP server keeps the index fresh as you edit (file watcher on by default; build dirs like target/
, node_modules/
, dist/
are pruned automatically). See docs/install.md.
Full pipeline (specs + decisions β optional and additive):
openlore generate # generate living specs (requires API key)
openlore drift # detect spec/code drift (no API key)
openlore decisions # manage architectural decisions
Install from source #
git clone https://github.com/clay-good/openlore
cd openlore
npm install && npm run build && npm link
Nix / NixOS #
nix run github:clay-good/openlore -- analyze
nix shell github:clay-good/openlore
System flake:
environment.systemPackages = [ openlore.packages.x86_64-linux.default ];
Every tool asks for something. Here is exactly what OpenLore asks for, measured on a fresh clone of ripgrep with the published
openlore@2.1.6
β reproduce it in about a minute:
git clone --depth 1 https://github.com/BurntSushi/ripgrep && cd ripgrep
npx openlore init && time npx openlore analyze && du -sh .openlore
| What it costs | On ripgrep (232 source files indexed) |
|---|---|
| One-time index build | |
| 13.6 s, entirely local β no API key, no network call | |
| Disk | |
27 MB under .openlore/ (gitignorable; the graph is a pure function of your source, so it's always rebuildable) |
|
| Per-query latency | |
| ~430 Β΅s p50 in-process via the MCP server; a cold one-shot CLI call is ~2 s, nearly all of it Node startup and opening the index | |
| Keeping it fresh | |
| automatic β the file watcher re-indexes the changed file's dependency closure on save | |
| Your source code | |
| never leaves the machine. No account, no telemetry (opt-in only), no hosted index | |
| Lock-in | |
none β it's one gitignored directory. Delete .openlore/ and nothing about your repo has changed. |
Large monorepos take minutes rather than seconds; that limit is stated plainly in Known Limitations.
Migrating from
spec-gen
? The package is now[and the command is]openlore
openlore
β see[docs/RENAME-TO-OPENLORE.md]for the short checklist.
Memory makes an agent fast. Governance makes it safe. As agents get more autonomous, the bottleneck moves from "can it write the code" to "can I trust what it just changed." OpenLore answers that deterministically β every check below is static analysis, no LLM, advisory by default with opt-in blocking β and all of it rides the one graph, so it spans application code, IaC, and your recorded decisions at once.
| Guardrail | What it certifies | Run it |
|---|---|---|
change_impact_certificate |
||
| Whether a diff newly opens a path into a sensitive boundary you declared β reachable after the change but not before (a differential, not a snapshot) β plus blast radius, drifted specs, and the tests to run. | ||
openlore impact-certificate --base main |
||
certify_public_surface |
||
A breaking-change verdict over a diff: each changed export breaking / non-breaking / potentially-breaking , each break paired with the in-repo consumers it hits. Conservative β what it can't prove safe is never called safe. |
||
openlore certify-public-surface --base main |
||
check_architecture |
||
| "May a file under A import B?" against your declared layer / forbidden / allowed-only rules β a pre-write verdict, cross-language, instead of a post-hoc CI failure. | ||
declare rules in .openlore/architecture.json |
||
verify_claim |
||
A deterministic confirmed / refuted / unverifiable verdict with a citation receipt before an agent asserts "X is dead", "Y is safe to change", or "decision abc12345 still governs this". |
||
MCP tool (verify preset) |
||
openlore enforce |
||
One commit gate over every governance finding. Map each finding code β blocking / advisory / off in enforcement.policy ; the gate blocks only on what you class blocking. |
||
openlore enforce --hook |
||
| Epistemic Lease | ||
| Tells the agent when its context has gone stale (aged, or the repo moved) so a long session can't drift onto confident-but-wrong assumptions. Facts, never commands. | ||
| automatic on every MCP response |
A reviewer doesn't even need an agent: ** openlore review --base main** composes the structural delta and the blast radius into one Markdown briefing, and the bundled GitHub Action posts it as a single sticky PR comment (advisory by default). See
PR review.
The default MCP surface is the ** substrate** preset β 13 tools: the navigation graph-traversal core plus the three highest-value governance reads (
recall
, verify_claim
, blast_radius
), so an out-of-box agent gets the read face on top of navigation (the write face β remember
, record_decision
β stays opt-in via --preset memory
/minimal
/full
). It cleared the DefaultSurfaceRevealsAllFaces benchmark β no task-completion or tool-selection regression vs. the lean core across two models and both repo tiers. The lean navigate-only preset (10 tools) stays a one-flag escape (
navigation
--preset navigation
), and the full surface of 73 tools is opt-in via
--preset full
. Every tool declares one of six capability familiesβ
navigate
Β· change
Β· remember
Β· verify
Β· coordinate
Β· federate
β surfaced in its MCP annotations.family
, so a wide surface stays discoverable by family rather than as a flat list. Reach for the right tool by situation:| Situation | Tool |
|---|---|
| Starting any task | orient(task) β functions, callers, specs, insertion points in one call |
| Shallow "who calls X / where is Y?" | orient(task, lean:true) β navigation core only, ~40% smaller |
| "Which file/function handles X?" | search_code |
| "What's the blast radius if I change this?" | analyze_impact β risk score + up/downstream chain + governing decisions |
| "How does request X reach function Y?" | trace_execution_path |
| "I changed X β which tests should I run?" | select_tests β backward reachability to the reaching tests + paths |
| "What's dead / what dies if I delete X?" | find_dead_code β cross-language reachability, confidence-tagged |
| "What's the blast radius of my whole diff before I commit?" | blast_radius β callers/layers, tests to run, anchored memories & specs that drift |
| "Does my diff open a new path into a sensitive boundary?" | change_impact_certificate β differential reachability into declared surfaces |
| "Did my change break a consumer's public API contract?" | certify_public_surface β breaking-change verdict, consumers named |
| "May I add this import here?" | check_architecture β pre-edit verdict against declared rules |
| About to assert a fact / cite a decision | verify_claim β deterministic verdict + citation receipt |
| Recording an architectural choice | record_decision before writing the code |
| "What changed structurally / whose callers are stale?" | structural_diff β graph diff, stale callers, rename flags |
| "What changes together with this / what's volatile?" | get_change_coupling β co-change + churn from git |
| Reading / checking a spec | get_spec Β· search_specs Β· check_spec_drift |
| "A lot changed since I last looked β what matters?" | briefing_since β changed symbols ranked by structural significance |
| "Does a near-duplicate of this already exist to reuse?" | find_clones β edit-time, scoped clone query |
Everything else (read a file, grep, list files) uses your native tools. Full reference β all 73 tools and parameters: docs/mcp-tools.md.
OpenLore ships a canonical Claude Code Skill at skills/openlore-orient/. Install it once and Claude Code calls
orient()
at the start of every task β no CLAUDE.md
editing.
npm run skill:install-local # β ~/.claude/skills/openlore-orient/
cp -R skills/openlore-orient /path/to/your-project/.claude/skills/ # or per-project
The 8 multi-agent workflow skills (brainstorm, plan-refactor, write-tests, implement-story, debug, β¦) install via openlore setup
into .claude/
, .opencode/
, or .vibe/
. See skills/openlore-orient/README.md.
A good tool should tell you when not to use it. So here is the honest map of the space.
Everyone in this category is answering the same first question: "how does the agent see the codebase without reading it file by file?" LSP toolkits answer it with symbols. Graph MCP servers answer it with a parsed graph. Search platforms answer it with an index. They are all real answers, and several of them are good.
OpenLore answers it too β and then keeps going into the second question almost nobody is answering: what happens when the agent starts writing? A retrieval layer makes an agent informed. It does not make the agent safe. Nothing in a symbol index tells you that this diff just opened a path into your auth boundary, that this signature change breaks four consumers by name, that the decision governing this module was superseded last month, or that the fact your agent has been confidently using for the last 40 tool calls went stale 12 commits ago. That half of the problem β governance, on the same graph, with no LLM in the loop β is the part OpenLore was built for.
Agent built-ins(Cursor, Claude Code) |
LSP toolkits(e.g. Serena) |
Graph MCP servers(e.g. CodeGraph) |
Search platforms(e.g. Sourcegraph) |
OpenLore |
|
|---|---|---|---|---|---|
| Structural context instead of file reads | β grep + file reads | β symbols via LSP | β parsed graph | β index | β call graph + clusters + IaC + decisions on one graph |
| Local, no API key, deterministic | Partial | β | β | β hosted/indexed service | β no LLM in the hot path |
| Cross-session memory anchored to code | β | Partial (notes) | β stored notes | β | β anchored to a symbol β carried across renames/moves, self-invalidating |
Told when a cached fact goes stale |
β | β | β | β | β Epistemic Lease |
| Blast radius + which tests to run | β | β references only | Partial | Partial | β backward reachability, with reaching paths |
Breaking-change verdict over a diff |
β | β | Partial (impact heuristics) | β | β per export, consumers named, conservative by construction |
| "Did this diff open a new path into a sensitive boundary?" | β | β | β | β | β differential reachability, pre-commit |
Architecture invariants before the import is written |
β | β | β | β | β cross-language |
| Spec/code drift + ADRs gated at commit | β | β | β | β | β milliseconds, no API key |
| One policy-driven commit gate | β | β | β | β | β enforcement.policy , advisory by default |
| Claims backed by a citation receipt | β | β | β | β | β confirmed / refuted / unverifiable |
Cost/round-trip effect published with the losses |
β | β | β | β | β β7%ββ21% cost, β26% round-trips on deep tasks β |
Where the others are genuinely the better pick β we would rather you use the right tool than ours:
Doing surgical, symbol-level(rename across files, move a symbol, replace a body) β that is aneditsLSP toolkit's home turf. OpenLore is deliberatelyread-only: it locates and certifies, it does not refactor for you. The two compose well.** Search across hundreds of repos, org-wide, in a browser, with an audit trail**β that is a** code search platform**. OpenLore is local-first and repo-scoped by default (federation is opt-in and read-only).** You just want fast graph retrieval and nothing else**β a** graph MCP server**is a smaller surface to adopt, and several are excellent at it. OpenLore's extra weight is governance; if you do not want a commit gate, drift detection, or change certificates, you are paying for capability you will not use.A small, familiar repo and shallow questions("who calls X") β your agent's built-in search is oftencheaper. We measured this and published it below; we did not hide it.
What you cannot get anywhere else: one graph where your code, your infrastructure, and your architectural decisions are the same node type β so a single traversal answers "what breaks, what does it cost, what governs it, and is that still true?" β and a substrate that would rather tell you "I don't know, this is stale" than hand your agent a confident guess.
β Measured, and it depends on the task β full numbers in the Value Scorecard. Small/familiar repos + shallow queries add overhead; larger codebases + deep questions are a net win (β7%ββ21% cost, β26% tool-calls, scaling with repo size), at 100% answer correctness in both arms. The savings hold where OpenLore is designed to help, not on toy queries.
Comparisons reflect each project's publicly documented capabilities as of July 2026 and describe categories, not verdicts on quality; these are fast-moving projects, and a correction PR is always welcome. Named examples: Serena (MIT), CodeGraph (Apache-2.0), codebase-memory-mcp, Sourcegraph. OpenLore exports SCIP, so it sits alongside them rather than against them.
Three layers, each usable independently:
| Layer | What it does | API key? |
|---|
- Static Analysis |
Call graph, clusters, McCabe CC, IaC, external deps β
CODEBASE.mddigest | No | - Spec & Governance | Living specs, ADRs, drift detection, change certificates, decision & finding gates | For spec generation only |
- Agent Runtime |
73 MCP tools β
orient(), graph traversal, semantic search, verdicts & gates | No |
Use layer 1 alone for structural context. Add layer 2 for semantic intent and governance. Layer 3 keeps it all continuously accessible through graph-native MCP tools once openlore mcp
is running.
flowchart TD
Code[Codebase] --> Analyze[openlore analyze<br/>tree-sitter Β· pure static analysis]
Analyze --> DB[(SQLite graph store)]
Analyze --> Digest[CODEBASE.md<br/>~600-token structural digest]
subgraph shared["Projected onto shared node + edge primitives"]
direction LR
CodeNodes[functions + call edges]
Iac[IaC resources + references]
Dec[decisions + affects edges]
end
Analyze --> CodeNodes
Analyze --> Iac
Analyze -. active decision store .-> Dec
CodeNodes --> DB
Iac --> DB
Dec --> DB
DB --> MCP[73 MCP tools<br/>orient Β· analyze_impact Β· certify Β· verify Β· enforce]
MCP --> Agent((Coding Agent))
Code -. optional, API key .-> Gen[openlore generate]
Gen --> Specs[openspec/specs/*.md<br/>RFC 2119 living specs]
Code --> Drift[openlore drift<br/>spec/code drift, ms, no API]
Agent -. record_decision .-> Gate[decisions + findings gate]
Gate --> Specs
Crucially, application code, Infrastructure-as-Code, and architectural decisions all project onto one shared set of node/edge primitives β so a single traversal answers questions that span all three, and impact analysis returns governance as a graph neighbor. See docs/ARCHITECTURE.md.
Skimmable by design: each bold lead-in is one capability, with the command that runs it and the doc that explains it. Everything is deterministic and local; only the two entries marked "API key" ever talk to a model.
Analyze (no API key) β Continuously maintains a structural representation using pure static analysis: a full call graph in SQLite, label-propagation community detection, McCabe complexity per function, and extracted DB schemas, HTTP routes, UI components, middleware, and env vars. Outputs .openlore/analysis/CODEBASE.md
β a ~600-token digest that compresses tens of thousands of exploratory tokens. The MCP server's file watcher (--watch-auto
, on by default) updates the graph incrementally on every save and converges to what analyze --force would produce; when a change's reverse-dependency closure exceeds the per-save budget, the un-recomputed files are marked
explicitly stale rather than left silently divergent.
Generate (API key) β Sends the analysis to an LLM in 6 structured stages (survey β entities β services β APIs β architecture β ADRs), producing openspec/specs/
living specifications in RFC 2119 with Given/When/Then scenarios.
Drift (no API key) β Compares git changes against spec mappings in milliseconds: Gap, Uncovered, Stale, and ADR-gap. Installs as a pre-commit hook. β docs/drift-detection.md
Test impact selection (no API key) β select_tests
walks the call graph backward from a change to every test that transitively reaches it, returning each test with its reaching path. Static, call-graph-based regression test selection at edit time, not after CI. An honest over-approximate prioritizer ("run these first"), not a sound replacement for the full suite. β docs/test-impact-selection.md
Reachability & dead-code (no API key) β find_dead_code
runs cross-language mark-and-sweep from roots (tests, imports, route handlers, main
), and answers "what becomes dead if I delete X?" Results are confidence-tagged candidates, never deletion authority. The import resolver follows re-export/barrel chains and Python relative imports so a call resolves precisely. β docs/reachability-dead-code.md
Change-impact certificate (no API key) β change_impact_certificate
certifies whether a diff newly opens a path into a declared covering surface (differential reachability β reachable after but not before), plus blast radius, drifted specs, and tests to run. Decays via the freshness lease. Advisory; opt-in blocking on a configured surface severity. CLI: openlore impact-certificate
.
Public API surface contract (no API key, opt-in) β certify_public_surface
gives a deterministic breaking-change verdict over a diff: each changed export breaking / non-breaking / potentially-breaking
, each break paired with the in-repo consumers it hits. Conservative β a change it can't prove compatible is potentially-breaking
, never silently safe. Renamed exports detected via symbol-identity continuity. CLI: openlore certify-public-surface
.
Architecture invariant guardrails (no API key) β check_architecture
turns an architectural rule from a post-hoc CI failure into a pre-write verdict. Declare layers
/ forbidden
/ allowedOnly
in .openlore/architecture.json
(or via an Invariant:
marker on a synced ADR), and the tool answers "may a file under A import B?" with a deterministic verdict + the governing rule. Cross-language over the unified graph. β docs/architecture-invariants.md
Claim verification (no API key, opt-in) β verify_claim
returns confirmed / refuted / unverifiable
with a citation receipt (index commit, content hashes, the reaching path) β never an LLM guess. Structural kinds check the call graph; decision-current
checks a recorded decision is still authoritative and names the live superseder if it isn't.
Finding enforcement (no API key, advisory by default) β openlore enforce
is the unified gate over all governance findings. A single enforcement.policy
block maps each finding code
β blocking / advisory / off
, decoupling a finding's intrinsic severity from your risk posture. The --hook
gate fails a commit only on a blocking
finding; a repo that declares no policy never blocks. It always runs the deterministic stale-decision-reference
check and folds in blast-radius and impact-certificate findings where configured. β docs/configuration.md
Decisions on the graph (API key for consolidation) β Agents call record_decision
before writing code; a pre-commit hook gates the commit until verified decisions are reviewed and written back as requirements. Decisions are also first-class graph nodes: projected into decision::<id>
nodes joined to the files they govern by affects
edges, so analyze_impact
and get_subgraph
return the governing decisions of a symbol and its blast radius as typed neighbors. β docs/specs/openlore-spec-16-decisions-as-graph-nodes.md
Epistemic Lease (no API key) β Models architectural drift as a navigation phenomenon, not a knowledge one: decay is driven by where the agent goes (cross-module trajectory), time since orient()
, and weighted cognitive load. Once context ages or the repo moves, every MCP response carries a brief, factual freshness note β "informational signal; you decide whether to act on it" β never a command. Calling orient()
resets it; when fresh, injection is zero-overhead.
Structural change analysis (no API key) β structural_diff
is the structural complement to git diff
: functions and edges added/removed, signature changes, and the callers in other files now stale because a callee's signature moved. β docs/structural-diff.md
Change-coupling & volatility (no API key) β get_change_coupling
mines two facts the call graph can't see: co-change coupling ("these files always change together" β invisible coupling with no import edge) and churn ("changed 23 times" β a risk flag). Advisory, correlation not causation. β docs/change-coupling.md
Test-coverage gaps (no API key, opt-in) β report_coverage_gaps
answers the inverse of select_tests
: which load-bearing code has no test reaching it at all? Walks forward from every test, reports the code outside that set, ranked by hub/chokepoint significance. No test run, no instrumentation. Gaps-only and honest β it never claims a symbol is "tested". CLI: openlore coverage-gaps
. β docs/coverage-gaps.md
More tools β style fingerprint, clone query, error & env-var impact, change briefing, parallel-work planningβ¦ #
β a descriptive, deterministic idiom profile (arrow vs. declared function,get_style_fingerprint
const
/let
, naming caseβ¦) so an agent matches the house style. Honest by construction: a counter below the evidence floor, or a choice the compiler enforces, reports a null signal, never a guess.β the edit-time, scoped "does a near-duplicate offind_clones
thisalready exist?" companion to the whole-repoget_duplicate_report
. One query (a symbol or a raw snippet), clones ranked exact > structural > near. CLI:openlore find-clones
.β the exceptions that escape a function vs. those caught within it (TS/JS/Python). A sound lower bound β un-analyzable callees disclosed, never assumed exception-free. CLI:analyze_error_propagation
openlore error-propagation
.β "what breaks if I remove this env var?": line-precise read sites, the upstream callers that reach them, tests to run, and per-siteanalyze_env_impact
required
. CLI:openlore env-impact
.β the catch-up lens: changed symbols since a base ref, ranked into a fixed tier order (surprising-change > hub-change > chokepoint-change > ordinary) from existing classifiers, no weighted score. CLI:briefing_since
openlore briefing-since
.β a hazard-typed conflict graph over a task list, or over every in-flight branch/PR/agent-task, with a suggested landing order (opt-inplan_parallel_work
/map_in_flight_conflicts
coordination
preset).β the deterministic per-language capability matrix, derived from the live extractors so it can't over-claim. βget_language_support
Preflight (no API key) β A CI staleness gate: any PR that edits indexed files fails until the graph is refreshed, weighted so hubs surface first. Drop-in templates in examples/ci/. β
Share the index (no API key) β The graph is a deterministic function of the committed source, so a team analyzes once and everyone else imports. openlore export bundle
serializes the index into a portable, integrity-stamped .olbundle
; openlore import
bootstraps a verified index in seconds β validate-or-rebuild, never serving a stale or tampered bundle. β docs/shareable-bundle.md
Telemetry (opt-in, no API key) β Gated by OPENLORE_TELEMETRY=1
(off by default). Append-only JSONL under .openlore/telemetry/
for empirically measuring Epistemic Lease behavior (obstinacy index, recovery efficiency, trajectory dynamics). Analyze with openlore telemetry
.
Languages: TypeScript Β· JavaScript Β· Python Β· Go Β· Rust Β· Ruby Β· Java Β· C++ Β· Swift Β· C# Β· Kotlin Β· PHP Β· C Β· Scala Β· Dart Β· Lua Β· Elixir Β· Bash β call graphs ride the same node/edge primitives for every language. Per-language extraction limits: docs/languages.md.
Infrastructure-as-Code: Terraform/HCL Β· Kubernetes Β· Helm Β· CloudFormation Β· Ansible Β· Pulumi Β· AWS CDK Β· CDKTF Β· Dockerfile Β· Docker Compose Β· GitHub Actions Β· Azure Bicep β IaC resources and their references project onto the same graph as application code, so orient
, search_code
, and analyze_impact
answer "what's the blast radius of changing this security group / ConfigMap / IAM role / base image / CI job?" with zero new tooling. A compose service's build:
resolves to its Dockerfile stage to its FROM
base image β so one analyze_impact
on a base image surfaces every stage and service that would rebuild. β docs/iac.md
Cross-domain impact: for embedded IaC (Pulumi/CDK/CDKTF), the code that provisions a resource is linked to it by a references
edge, so analyze_impact
traverses the codeβinfra boundary end-to-end β "what infrastructure does this handler reach?" and the reverse. A code-only navigator structurally cannot answer this. β docs/cross-domain-impact.md
The hardest orientation questions cross repo boundaries: who calls BillingService.refund
, where is event X
consumed, how does data flow from service A to B. Each repo keeps its own independently-built .openlore
index; a project-local registry references peers, and federated queries load only what they need β no merged graph is ever materialized.
openlore federation add ../billing-service --name billing # register a peer repo's index
openlore federation list # β indexed / β stale / β
unindexed
analyze_impact
, find_dead_code
, select_tests
, and find_path
take an opt-in federation
flag and answer across the fleet β always naming the repos consulted vs skipped, never guessing for an unindexed one. analyze_impact
on a route handler additionally surfaces its cross-service consumers β client call sites in other services that target the endpoint, matched by normalized route key. Enable with openlore mcp --preset federation
. β docs/federation.md Β· docs/cross-service-topology.md
OpenLore's deterministic value usually flows through an agent calling an MCP tool β but everyone opens pull requests. openlore review
drops the same distinctive output into that workflow, no agent required:
openlore review --base main # one Markdown briefing for a base..head range
openlore review --format json # machine-readable, for any CI / forge
It composes the structural delta (structural_diff
) and the blast radius (hubs touched, layers crossed, tests to run, spec/decision/memory drift) into one comment. The bundled GitHub Action posts it as one sticky comment β created once, updated in place, never spammy β advisory by default. Adoption is one workflow file: .github/workflows/openlore-review.yml.example. β
OpenLore exports SCIP. Plug it into Sourcegraph code nav, GitHub stack graphs, Glean importers, or any SCIP-aware tool:
openlore analyze && openlore export scip # writes ./index.scip
The SQLite graph stays canonical; SCIP is a one-way export of the subset it can model. β docs/scip-export.md
OpenLore is the inaugural engine and reference plugin for the OpenSpec marketplace: generate specs from existing code, then hand evolution back to core OpenSpec. OpenSpec discovers OpenLore by a declarative plugin manifest and invokes it as a subprocess β it never imports OpenLore's code. β docs/OPENSPEC-INTEGRATION.md
OpenLore's architecture is governed by the same decision system it ships. These ADRs were recorded with record_decision
, gated at commit, synced into openspec/specs/
, and projected onto the graph β they are the live, load-bearing constraints behind the design, not aspirational docs.
| Decision | Rationale | Where |
|---|---|---|
| North star is a deterministic structural context substrate | ||
| Local-first plumbing (like tree-sitter/SCIP/LSP) agents build on; every feature must make the coding-agent case more useful and stay grounded in static analysis, not LLM guessing | ||
IaC resources project onto the existing graph primitivesFunctionNode
/CallEdge
so every MCP tool works on IaC with zero new toolingsrc/core/analyzer/iac/project.ts
Decisions project onto the graph the same waydecision::
nodes + affects
edges β governance becomes a deterministic graph joinsrc/core/decisions/project.ts
EdgeStore uses SCHEMA_VERSION rebuild-on-bump, not migrationssrc/core/services/edge-store.ts
BM25 keyword retrieval is the zero-network floororient
/search_code
work with no API key or embedding server; dense embeddings are an optional upgradeThe default MCP surface is thesubstrate
preset (navigation core + governance reads)recall
, verify_claim
, blast_radius
); the write face (remember
, record_decision
) stays opt-in. Cleared the DefaultSurfaceRevealsAllFaces benchmark (no regression across two models / both tiers), so openlore install
wires it by default. Lean navigation
and the full set are one opt-in awayThis is the live decision log the pre-commit gate enforces. See docs/governance-dogfooding.md.
We'd rather you know these up front than discover them mid-task. Last validated against the code on 2026-07-25; two entries were wrong and are corrected below. Each open item names the change proposal that closes it β see the change set.
Static analysis only β but "dynamic dispatch isn't captured" is no longer true, and the real boundary is narrower. Polymorphic dispatchis recovered by class-hierarchy analysis, and event channels, routeβhandler bindings, and callback registrations by a deterministic synthesis pass β every one provenance-labeledsynthesized
, never mixed with directly-resolved edges. What genuinely isnot captured: reflective invocation with a non-literal target (getattr(o, name)()
,send(name)
), computed dispatch (obj[expr]()
),eval
-built code, DI/plugin registries with no statically-visible binding, and cross-language bridges. Today those constructs are silently absent from the graph. Two proposals close the single-language part from both sides βdisclose them as boundariesso a conclusion says where it stopped seeing, andresolve the structurally-resolvable halfinto real edges.**Cross-language bridges, and CHA's own name+arity over-approximation, stay uncovered and undisclosed.LLM spec quality varies β and the only automated check on whether the prose is Generated specs reflect the model's understanding; review complex business logic before treating it as authoritative. Structure, requirement format, import/export coverage, and drift are already checked deterministically; what nothing deterministic covers is whether a requirement's claims about the code are true. Spec verification's accuracy and coverage scores come from an LLM judge (~85% of the reported score) β the main place the product puts a model in a guardrail path, alongside one opt-in LLM gap filter inaccurateis another LLM.drift
.Proposed fix: every generated requirement cites the symbols it describes, and a deterministic checker over the call graph β no API key β reportsgrounded
/partially-grounded
/ungrounded
/uncited
per requirement. Grounding proves a requirement isabout code that exists; it will never prove the prose is correct.Keyword (BM25) is the first-class default; semantic is an opt-in upgrade.orient
/search_code
work immediately with no API key. Upgrade to hybrid dense+BM25 withopenlore embed --local
(a bundled, CPU-only, no-API-key on-device embedder, ~23 MB pinned model) or a remoteEMBED_BASE_URL
. Each surface states its active mode (keyword
/local-semantic
/remote-semantic
). The default's real weakness is vocabulary: the tokenizer splits identifiers into sub-tokens but does no stemming or expansion, so a natural-language task query reacheschargeCard
(βcharge
,card
) and misses genuinely abbreviated code (PmtSvc
βpmt
,svc
).Proposed fix: mine an abbreviation lexicon from your repo and expand queries deterministically β still no model, no download, no key.Large monorepos may take several minutes toanalyze
β AST/symbol extraction is the bottleneck, not graph storage. Parallel extraction and content-hash memoization of Pass 1 have shipped; what's still missing is any concept of apackage, so one changed package re-walks the whole repo, and the fact cache is machine-local, so every CI job and every teammate starts cold.Proposed fix: manifest-detected workspace shards with cross-shard frontier re-resolution, plus a portable content-addressed cache.Incremental updates converge or flag, never silently diverge. The watcher re-indexes the changed file's reverse-dependency closure on save; when that exceeds the per-save budget (default 40 files) the un-recomputed files are markedexplicitly stale(freshness verdicts report non-authoritative) instead of left wrong. A stale region** self-heals**: later edits reconcile the parts they touch, and when OpenLore runs as an MCP server or anopenlore serve
daemon a debounced background re-analyze clears the rest. Anyanalyze
clears it immediately, but no manual--force
is required. (Stale marks are only created by a running watcher, and persist across a watcher restart until the next analyze.) Open residue: the budget is spent in arrival order, sowhichfiles stay stale is arbitrary (proposed fix: spend it on hubs and chokepoints first, and report what the stale region actually contains).The index is integrity-checked, never served half-built β and it repairs itself. Everyanalyze
writes an attestation (schema version, counts, content digest); on load the store is reconciled intohealthy
/degraded
/mismatched
, and a non-healthy index is disclosed in the conclusion tools whose soundness depends on completeness β never silently answered over. When a read notices the index has drifted from the code, it kicks off an at-most-once background repairand says so, rather than answering from stale structure (openlore doctor --fix
covers the rest). A read never destroys the store: a schema mismatch reports "not ready" and a corrupt store is quarantined, never dropped.(Re-validated 2026-07-25: no open gap.)
Node.js 22.13+(the first line where the built-innode:sqlite
is available without runtime flags; launching under an older or incapable Node fails fast with a one-line message and exit code 78, never a stack trace).No API key foranalyze
,drift
,mcp
,init
, and every governance/navigation tool.API key only forgenerate
,verify
, anddrift --use-llm
:β¦or use a CLI-based provider (
export ANTHROPIC_API_KEY=sk-ant-... # default provider
export OPENAI_API_KEY=sk-... # OpenAI
export GEMINI_API_KEY=... # Google Gemini
claude-code
,gemini-cli
,mistral-vibe
,cursor-agent
) β no key, just the CLI on your PATH.
Start here: the documentation index maps what you want to do to the one canonical page that answers it.
| Topic | Doc |
|---|---|
| Full documentation index (task β canonical page) | |
docs/mcp-tools.mdopenlore install
β auto-configure agent surfacesdocs/install.mddocs/agent-setup.mddocs/AGENT-BENCHMARKS.mddocs/providers.mddocs/language-support.mdenforcement.policy
)docs/configuration.mddocs/cli-reference.mddocs/architecture-invariants.mddocs/test-impact-selection.mddocs/reachability-dead-code.mddocs/coverage-gaps.mddocs/structural-diff.mddocs/change-coupling.mddocs/drift-detection.mddocs/cross-domain-impact.mddocs/cross-service-topology.mddocs/federation.mddocs/shareable-bundle.mddocs/scip-export.mddocs/preflight.mddocs/ci-cd.mddocs/provenance.mddocs/cli-reference.mddocs/viewer.mddocs/configuration.mdΒ·docs/api.mdΒ·docs/pipeline.mddocs/ARCHITECTURE.mdΒ·docs/ALGORITHMS.mddocs/agentic-workflows.mddocs/TROUBLESHOOTING.mdΒ·docs/PHILOSOPHY.md
npm install
npm run build
npm run test:run # 5500+ unit tests, one-shot (npm test is watch mode)
npm run typecheck
New contributor? See ** CONTRIBUTING.md** for setup, the agent-context / MCP wiring, and the commit gate. Please also read our
Code of Conduct; to report a vulnerability, see
If OpenLore saves your agents from re-reading the same files β or catches one risky change before it lands β star the repo. It's the signal that tells us to keep building, and it helps other engineers find it.
Star history chart Β· stargazers
- β
Star to follow along:github.com/clay-good/OpenLore - π
Found a bug or have an idea? Open anissue. - π€
Want to contribute? Start withCONTRIBUTING.md. - π¦ Install in one line:
npm install -g openlore && openlore install