Generated banner for OpenLore
Generated logo for OpenLore

OpenLore

Memory server

Created by Clay Good

Published in clay-good/OpenLore. Publisher page

362 GitHub stars

What it is

Persistent architectural memory and structural cognition for AI coding agents.
— the description by Clay Good

AutoGineer overview, from the brain’s own files

OpenLore is a memory system designed to solve the amnesia and lack of governance in AI coding agents. By performing a one-time static analysis of a repository, it builds a persistent knowledge graph that encompasses application code, infrastructure-as-code, and architectural decisions. Unlike standard RAG systems, OpenLore operates deterministically without an LLM in the hot path, ensuring consistent answers and providing agents with an 'Epistemic Lease' to warn them when facts have become stale. It serves as both a memory layer for agent orientation and a governance engine that certifies changes, flags breaking API surface modifications, and validates sensitive boundary crossings before they are committed.

How it works

Memory model: Hybrid

AutoGineer overview, from the brain’s own files

OpenLore functions as an MCP server that indexes your repository into a local knowledge graph. Upon installation, it maps code structures, dependencies, and architectural specs. During agent sessions, it injects a compact architecture digest into the agent's context. The agent then uses MCP tools to query this graph for tasks like orienting to a codebase, tracing execution paths, or verifying claims. Governance tools analyze diffs against the graph to provide deterministic verdicts on breaking changes or boundary violations without requiring an LLM.

Folders
FolderRoleFiles
.claudeagent-config1
.githubagent-config12
docsdocs62
examplesexamples95
scriptsscripts27
skillsagent-instructions25
srcsource-code1284
benchfolder15
benchmarksfolder1
openspecfolder1261
packagingfolder2
schemasfolder2
stubsfolder1

The agent reads

The agent writes

Requirements

Runtimes: Node.js ^22.22.0 || >=23.5.0

Databases: LanceDB

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Google Antigravity, Kiro, OpenCode, Amp, Roo Code, Cline, JetBrains Junie, Devin (ex-Windsurf)

Set it up

  1. This brain runs code automatically: 1 script. Review package.json before opening the folder in Claude Code. No step below turns a hook on for you.

    • package.json: node scripts/postinstall.mjs
  2. Not scanned by AutoGineerAutoGineer could not scan everything it runs. https://www.npmjs.com/package/openlore
  3. Created by Clay Good. Published in clay-good/OpenLore. Licence: MIT License.

Usage

AutoGineer overview, from the brain’s own files

  • Orienting an agent to a new codebase at the start of a task.
  • Validating that a proposed code change does not break public API contracts.
  • Checking if a diff introduces a path into a sensitive architectural boundary.
  • Verifying claims about code safety or dead code before refactoring.
  • Maintaining architectural specifications that stay synchronized with code changes.
  • Selecting relevant tests to run based on the specific impact of a code modification.
“Orient me to the user authentication flow in this repository.”
The agent calls orient() and receives a summary of relevant functions, callers, and specs, allowing it to start the task immediately without manual file exploration.
“Review my current changes for breaking API surface modifications.”
The agent uses certify_public_surface to identify changed exports and lists the specific consumers that would be affected by the proposed changes.
“Verify if the function 'deleteUser' is safe to remove.”
The agent uses find_dead_code or verify_claim to determine if the function has any remaining callers or dependencies, providing a deterministic verdict.
“Check if this diff opens a path into the sensitive 'payment-gateway' module.”
The agent uses change_impact_certificate to confirm whether the new code creates a reachable path into the protected boundary.

Documentation by Clay Good

<h1 align="center">OpenLore</h1>

<p align="center"> <strong>Deterministic, local-first memory and guardrails for AI coding agents — no LLM in the hot path.</strong><br> Install once. From then on it runs in the background, keeping your agent oriented and its changes safe.<br> No API key. Same answer every time. </p>

<p align="center"> <a href="https://www.npmjs.com/package/openlore"><img src="https://img.shields.io/npm/v/openlore?color=2563eb&label=npm&logo=npm&logoColor=white" alt="npm version"></a> <a href="https://github.com/clay-good/OpenLore/actions/workflows/ci.yml"><img src="https://github.com/clay-good/OpenLore/actions/workflows/ci.yml/badge.svg" alt="CI status"></a> <a href="LICENSE"><img src="https://img.shields.io/npm/l/openlore?color=22c55e" alt="MIT License"></a> <img src="https://img.shields.io/node/v/openlore?color=339933&logo=node.js&logoColor=white" alt="Node >=22.22"> <br> <img src="https://img.shields.io/badge/MCP-ready-7c3aed?logo=anthropic&logoColor=white" alt="MCP ready"> <img src="https://img.shields.io/badge/languages-21%20%2B%2013%20IaC-f97316" alt="21 languages + 13 IaC ecosystems"> <img src="https://img.shields.io/badge/tests-5500%2B-success" alt="5500+ tests"> <img src="https://img.shields.io/badge/API_key-not_required-0ea5e9" alt="No API key required"> <a href="https://github.com/clay-good/OpenLore/stargazers"><img src="https://img.shields.io/github/stars/clay-good/OpenLore?style=social" alt="GitHub stars"></a> </p>

<p align="center"> <code>install</code> wires your agent and indexes the repo → <code>orient</code> returns the code a task touches → <code>review</code> catches the change that breaks a contract. All local, no API key. </p>

<p align="center"> <strong><a href="#install-once-then-forget-it">Install</a> · <a href="#what-you-get">What you get</a> · <a href="#value-scorecard--does-it-pay-for-itself">Benchmarks</a> · <a href="#openlore-vs-alternatives">vs. Alternatives</a> · <a href="#documentation">Docs</a></strong> </p>


AI coding agents are amnesiac and ungoverned. Every task restarts by re-reading the same files, long sessions drift onto stale assumptions, and nothing warns the agent when a change is about to break a contract or cross a boundary.

OpenLore fixes both halves, and then gets out of your way. A one-time static analysis builds a live knowledge graph of your repo — call structure, types, tests, decisions, IaC. From then on your agent is already oriented at the start of every task and its changes are certified before they land — automatically, deterministically, with no LLM in the hot path, so the same question returns the same grounded answer and the agent is told when a fact goes stale instead of served a confident guess.

Install once, then forget it

npm install -g openlore && openlore install

That is the only command most people ever run. It auto-detects your agent (Claude Code, Cursor, Cline, Continue, Pi, AGENTS.md), wires it, registers the MCP server, and builds the index — no API key, no config, no prompts. After that, it's on in the background and you don't think about it:

  • 🔄 Every task starts oriented, automatically — a compact architecture digest is injected into your agent's context at the start of each session, and your agent calls orient() and the other tools itself over MCP. You don't type anything.

  • 🌱 Every repo, not just this one — install wires your user account too, so each new git repo you open indexes itself in the background on first touch. "Install once" is literal.

  • 🩹 Always fresh — a file watcher re-indexes as you edit and converges to a full analysis; anything it can't recompute in time is marked explicitly stale, never silently wrong.

  • 🛡️ Changes get checked before they land — the governance tools (and openlore review on a PR) run deterministically with no API key.

The CLI below exists for humans and CI — prove, review, doctor, features. The everyday path is: install, then let your agent use it.

Zero config. Run openlore features to see every opt-in capability (embeddings, the commit gate, the spec store…), whether it's on, and the one command to enable it. openlore doctor verifies the wiring.


What you get

Two things, both deterministic and local:

🧠 Memory — your agent starts every task already oriented

  • Orient in one call — orient(task) returns the relevant functions, callers, specs, tests, and insertion points (~430µs p50 on a 15k-node graph) instead of a dozen exploratory file reads.

  • Survives refactors — anchored notes and decisions carry forward when a symbol is renamed or moved, instead of orphaning.

  • One graph for everything — application code, Infrastructure-as-Code, and architectural decisions share one graph, so a single query spans all three.

  • Told when a fact is stale — every response carries a brief, factual freshness note (the Epistemic Lease) once a cached fact has aged or the repo moved, so a long session can't drift onto a confident guess.

🛡️ Governance — guardrails on what the agent changes

  • Breaking-change verdict — certify_public_surface classifies every changed export breaking / non-breaking / potentially-breaking and names the consumers each break hits. Never silently "safe."

  • Sensitive-boundary check — change_impact_certificate flags when a diff opens a new path into a boundary you declared (reachable after the change, not before).

  • Grounded claims — verify_claim returns confirmed / refuted / unverifiable with a citation, before an agent asserts "X is dead" or "Y is safe to change."

  • One commit gate — openlore enforce blocks on findings you mark blocking. Advisory by default, no API key.

The one line to remember: 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 builds on it, and you pay 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, with no second model guessing about the first.


Value Scorecard — does it pay for itself?

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 and publish it — wins and losses. Numbers from the Spec 14 benchmark (claude -p, sonnet, N=4 medians, pinned SHAs), measured 2026-06-01.

ScenarioCost ΔRound-trips ΔVerdict
Large/unfamiliar repo · deep "how does X flow through Y" (its target)−7% to −21%−26%✅ helps — and the win grows with repo size
Small/familiar repo · shallow "who calls X"task-dependent (Round 1: +43%)+38%❌ often adds overhead — measure first

The win scales with codebase size (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

Re-confirmed live 2026-06-03: the deep-task win reproduces (okhttp −13%); the small/familiar case is task-dependent, not a flat loss (chalk −32% win vs. express +59% loss). Don't guess from our repos — run openlore prove --estimate on yours (seconds, no API key).

Honesty contract. We never publish a savings number the benchmark didn't produce, we always show the losses next to the wins, and every claim traces to a command you can run. Methodology: docs/AGENT-BENCHMARKS.md.


Is OpenLore for you?

✅ Strong fitA codebase too big to hold in your head — and the model's. Private or niche code the model never memorized. Long sessions where stale assumptions compound. Polyglot repos, or code plus the IaC that deploys it.
🤔 Try it, measureMid-size repos and mixed workloads. Run openlore prove --estimate (seconds, no key) before you commit.
❌ Probably notA small repo the model already knows, answering shallow questions — your agent's built-in search is cheaper, and we publish the measurement that says so. Also: OpenLore locates and certifies, it never edits your code.

What your agent reaches for (you rarely type these)

Once wired, your agent calls these over MCP on its own. The default surface is the substrate preset — 15 tools: the navigation core, the highest-value governance reads (recall, verify_claim, blast_radius), and the spec composites. The lean navigation preset (10 tools) and the full 83 tools (--preset full) are one flag away.

When the agent needs to…Tool
Start any taskorient(task) — functions, callers, specs, insertion points in one call
Find which file/function handles Xsearch_code
Know the blast radius of a changeanalyze_impact — risk + up/downstream chain + governing decisions
Trace how request X reaches function Ytrace_execution_path
Pick the tests a change should runselect_tests — backward reachability to the reaching tests
Find dead code / what dies if X is deletedfind_dead_code — cross-language reachability
Check a whole diff before commitblast_radius — callers, tests, specs that drift
Tell if a diff opens a sensitive boundarychange_impact_certificate
Tell if a change breaks a consumer's APIcertify_public_surface — verdict, consumers named
Verify a fact before asserting itverify_claim — deterministic verdict + citation
Record an architectural choicerecord_decision before writing the code

Full reference — all 83 tools: docs/mcp-tools.md. Ships as a Claude Code Skill at skills/openlore-orient/.


OpenLore vs. alternatives

Everyone in this category answers the same first question: how does the agent see the codebase without reading it file by file? LSP toolkits answer with symbols, graph MCP servers with a parsed graph, search platforms with an index. OpenLore answers it too — then keeps going into the second question almost nobody answers: what happens when the agent starts writing? A retrieval layer makes an agent informed; it doesn't make it safe. That second half — governance on the same graph, no LLM in the loop — is what OpenLore is for.

Agent built-insLSP toolkitsGraph MCPSearch platformsOpenLore
Structural context instead of file reads❌ grep✓ symbols✓ graph✓ index✓ call graph + IaC + decisions on one graph
Local, no API key, deterministicPartial✓✓❌ hosted✓ no LLM in the hot path
Cross-session memory anchored to code❌Partial✓ notes❌✓ carried across renames
Told when a cached fact goes stale❌❌❌❌✓ Epistemic Lease
Breaking-change verdict over a diff❌❌Partial❌✓ per export, consumers named
"Did this diff open a sensitive path?"❌❌❌❌✓ differential, pre-commit
Cost effect published with the losses❌❌❌❌✓ −26% round-trips on deep tasks

Use the other tool when it's the right one: symbol-level edits (rename across files) are an LSP toolkit's home turf — OpenLore is deliberately read-only, and the two compose. Org-wide search across hundreds of repos is a search platform. Just-fast-graph-retrieval is a smaller graph MCP server. And a small, familiar repo with shallow questions is cheaper with your agent's built-in search. OpenLore also exports SCIP, so it sits alongside these tools, not against them.

Comparisons reflect each project's publicly documented capabilities as of July 2026; a correction PR is always welcome.


What it costs

Measured on a fresh clone of ripgrep with published [email protected]:

What it costsOn ripgrep (232 files)
One-time index build13.6 s, entirely local — no API key, no network
Disk27 MB under .openlore/ (gitignorable; rebuildable from source)
Per-query latency~430 µs p50 in-process via the MCP server
Your source codenever leaves the machine — no account, no telemetry (opt-in only)
Lock-innone — delete .openlore/ and nothing about your repo has changed

Large monorepos take minutes rather than seconds, stated plainly in Known limitations. Platform: Linux and Windows are exercised in CI; macOS is supported.

Fully local — nothing phones home. No account, no network call; your source, the index, and every query stay on your machine. The only diagnostics that exist are opt-in and local-only, off by default and never transmitted: OPENLORE_TELEMETRY=1 records tool calls, agent identity, latency, error messages, decision titles, and lease events to the gitignored .openlore/telemetry/ directory, and OPENLORE_LLM_LOGS=1 stores prompts and responses (after secret redaction) in the gitignored .openlore/logs/ directory. → docs/configuration.md


Languages, Infrastructure-as-Code & contracts

Languages: TypeScript · JavaScript · Python · Go · Rust · Ruby · Java · C++ · Swift · C# · Kotlin · PHP · C · Scala · Dart · Lua · Elixir · Bash — call graphs ride the same primitives for every language. → docs/language-support.md

Infrastructure-as-Code: Terraform/HCL · Kubernetes · Helm · CloudFormation · Ansible · Pulumi · AWS CDK · CDKTF · Dockerfile · Docker Compose · GitHub Actions · Azure Bicep · Serverless Framework — IaC resources 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 / IAM role / base image / CI job?" with zero new tooling. → docs/iac.md

Interface contracts: OpenAPI 3.x · Swagger 2.0 — each declared operation projects onto the same graph, bound to the route handler that serves it and the function that calls it, so orient and analyze_impact cross the API boundary. Matching is literal, and everything unbound is disclosed. → docs/contracts.md


Known limitations

We'd rather you know these up front.

  • Static analysis only. Polymorphic dispatch, event channels, and route→handler bindings are recovered (provenance-labeled synthesized). What isn't: reflective invocation with a non-literal target (getattr(o, name)()), eval, and DI/plugin registries with no visible binding — silently absent from the graph.

  • LLM spec quality varies. Generated spec prose reflects the model's understanding — review complex business logic. Structure, format, coverage, and drift are checked deterministically.

  • Keyword (BM25) search answers first; local semantic follows in the background. orient/search_code work immediately with no key. Background builds then add on-device vectors (CPU-only, ~23 MB model) when the optional package is installed; opt out with "embedding": { "provider": "none" }.

  • Large monorepos analyze one package at a time after the first full build (openlore analyze --shard <name>); the initial index is still a full analysis.

  • Incremental updates converge or flag, never silently diverge. When a change exceeds the per-save budget, the rest is marked explicitly stale and self-heals on later edits or a background re-analyze.


Documentation

Start here: the documentation index maps every task to the one canonical page that answers it. The essentials:

I want to…Doc
Install & wire my agent (one command)install.md
Understand why wiring an agent helpsagent-setup.md
See all 83 MCP tools + parametersmcp-tools.md
Run the full CLI referencecli-reference.md
Configure .openlore/config.json (incl. enforcement.policy)configuration.md
Choose / configure an LLM provider (optional)providers.md
Fix a setup problemTROUBLESHOOTING.md
Read the benchmark methodology + per-task numbersAGENT-BENCHMARKS.md
Search code by meaning (semantic / GraphRAG)semantic-search.md
Pick the tests a change should runtest-impact-selection.md
Find dead code / coverage gapsreachability-dead-code.md · coverage-gaps.md
Detect spec ↔ code driftdrift-detection.md
Enforce architecture / import rulesarchitecture-invariants.md
Gate commits / wire CI & PR reviewci-cd.md · preflight.md
Understand IaC & cross-domain impactiac.md · cross-domain-impact.md
See declared API contracts in the graphcontracts.md
Query across repos (federation)federation.md
Share the index as a bundleshareable-bundle.md
Export to Sourcegraph / SCIP toolsscip-export.md
Read the internalsARCHITECTURE.md · ALGORITHMS.md · PHILOSOPHY.md

Development

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, MCP wiring, and the commit gate. Please also read our Code of Conduct; to report a vulnerability, see SECURITY.md.


Community

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.


  • OpenSpec — spec-driven development framework

  • AGENTS.md — system prompt for direct LLM prompting

  • Examples — BMAD, Vibe, GSD, drift-demo, spec-kit integrations

docs/contracts.md

Interface contracts in the graph

OpenLore models a codebase as nodes and edges. A declared HTTP interface maps onto the same graph: an operation is a thing code serves and calls, so it becomes a node with edges to the code on both sides.

Because contract elements are projected onto the existing graph primitives (FunctionNode / CallEdge), the graph tools see them with no change of their own: orient, search_code, get_subgraph, analyze_impact, the SCIP export, and the federation manifest. An operation reaches you as a typed cross-domain neighbour (crossDomain.operations), never inside the list of code functions — it is a declaration, not something you can edit or test. find_path and trace_execution_path walk call edges only, so they do not route through an operation.

The mapping

Contract conceptOpenLore graph primitive
A declared operation (GET /v1/orders/{id})a node (FunctionNode), language: OpenAPI
The route handler that serves itan implements_endpoint edge, handler → operation
The function that calls ita consumes_endpoint edge, caller → operation

name and the node address are <METHOD> <path template> with the servers base path included; filePath and startLine point at the declaration in the specification; className is the dialect (OpenAPI or Swagger); signature carries the operationId when the document declares one.

Formats read: OpenAPI 3.0 / 3.1 and Swagger 2.0, YAML or JSON. A file is a candidate when its extension is .yaml / .yml / .json and it carries a top-level openapi or swagger key.

A declaration is not a code unit

A contract node is excluded from every candidate set that ranks source code — hub functions, entry points, find_dead_code, report_coverage_gaps, briefing_since — and a contract edge never changes a function's fan-in or fan-out. Projecting a specification therefore changes no structural metric of the code.

One conclusion does change, and deliberately: a handler that serves a declared operation is invoked from outside the code — by whoever calls that endpoint, in this repository or outside it — so it is a reachability root rather than a dead-code candidate. A handler no specification declares is unaffected.

Matching is literal

No name similarity, no embeddings, no prefix guessing.

  • Handler → operation. The route's method and normalized path must equal the operation's. A router is often mounted under the servers base path, so a route also matches the path as declared under paths — but only when it carries a literal segment and exactly one operation across every specification has that method and declared path.

  • Caller → operation. Same rule, plus one step: a call writes a value where the specification writes a parameter (/v1/orders/7 against /v1/orders/{id}), so a concrete path binds to the one declared template that accepts it, segment by segment. An exactly declared path always wins over a template the same path could fill, and a path that fills two different templates binds nothing.

  • A wildcard-method registration (app.use, app.all) declares no one operation, so it matches nothing.

Everything unbound is disclosed

The projection never implies absence. The build result carries:

DisclosureMeans
unmatchedOperationsDeclared, but no indexed route serves it and no indexed call consumes it
unmatchedRoutesRegistered, but no declared operation matched it
unresolvedBindingsA route or call that did match an operation, whose handler or caller resolved to no single indexed function
unreadSpecificationsThe file declared itself a specification and could not be read, with the reason
unboundClientCallsThe call's method or path is not literal, its host is not one this specification declares, its address is ambiguous, or no operation declares it
ambiguousAddressesA method and path that name more than one declared operation, so nothing binds to them
notAssessedWhy an "unmatched" conclusion is withheld rather than drawn
truncationsEvery cap the projection stopped at, with what it saw

Each list is bounded and carries its exact total, so a disclosure never becomes the thing that blows up the artifact carrying it.

A specification that fails to parse is therefore unread, never read as "declares nothing" — and while anything is unread, degraded, or past a cap, nothing is called unmatched at all: notAssessed says why. find_dead_code repeats the part that bears on liveness, because an unresolved binding means a declared handler can still look dead. certify_service_contract is the conclusion-shaped view of the same parity, with breaking-change verdicts; the contracts line in orient and blast_radius names the operation a handler in scope serves.

When it is computed

The projection runs during openlore analyze, with the rest of the graph. A specification is not source code, so a subset rebuild — the watcher's per-file pass, or a shard-scoped re-analysis — does not re-project it, exactly as it does not re-project an infrastructure manifest. Contract edges are therefore preserved, not deleted, when a bound handler's own file is rebuilt: a save cannot silently drop the binding. Run openlore analyze to refresh the operations after a specification changes.

Named schemas as nodes

Each named schema a specification declares — components/schemas in OpenAPI 3, definitions in Swagger 2 — is a graph node, and references_schema edges connect an operation to the schemas it names and a schema to the schemas its own definition names:

implements_endpoint : createOrder -> POST /v1/orders
references_schema   : POST /v1/orders -> #/components/schemas/Order
references_schema   : POST /v1/orders -> #/components/schemas/OrderInput
references_schema   : #/components/schemas/Order -> #/components/schemas/Customer

That shape is the point: a schema shared by twenty operations is one node with twenty inbound edges, so "what does changing Order touch?" is a graph question, and a handler reaches the schemas of its own contract through the operation it serves.

Per-field nodes are deliberately not projected. A large document has thousands of fields, and the question they would answer — did this property's type or requiredness change — already belongs to certify_service_contract's verdict. The named schema is the reusable unit.

Edges are direct only; the transitive closure is the graph's to walk rather than a list duplicated on every node. A cycle terminates, each edge drawn once. A $ref naming something the document does not declare — an external reference, or a name that does not resolve — draws no edge and is disclosed; nothing is invented for it. The number of projected schemas is bounded, and a document past the cap says so rather than projecting part of its schemas silently.

A references_schema edge is a declaration binding, so it contributes to no function's degree, no entry-point or hub classification, no file-level dependency ranking and no layer violation — verified as a difference between two builds of the same code, one with the specification and one without.

Protobuf

Every .proto file is read as a proto3 schema. A file with no syntax statement is proto2 by the language's own rule, and an edition file follows other rules again; both are listed as unread, with that reason, never read as declaring nothing. The reader is a small tokenizer written for this — no protoc, no grammar, no network.

Protobuf conceptOpenLore graph primitive
An RPC (rpc GetOrder)a node named by its gRPC method path, /orders.v1.OrderService/GetOrder, language: Protobuf
A message or enuma node named by its full name, orders.v1.Order
The request and response types of an RPC; the message and enum types of a message's fieldsreferences_schema edges
The method a registered server implementation definesan implements_endpoint edge, method → RPC

Type names are resolved with protobuf's own scoping rule, across every .proto file read: the first part of a name is looked up from the innermost scope outward, and a leading dot means "from the root". A file sees only its own declarations and those of the files it imports (plus what they re-export with import public); an import path matches a read file whose path ends with it. So adding an unrelated file whose package shadows a scope never changes what an untouched field resolves to. A name declared in two visible files binds nothing, and a type imported from outside the repository (google.protobuf.Timestamp) draws no edge — both are disclosed. Fields are not nodes, for the same reason as above.

Which code serves an RPC

A gRPC server is wired up at one call that names both the service and its implementation, in code the generator emits for every language. That call is the evidence the edge rests on:

RuntimeRegistrationThe RPC GetOrder is served by
Gopb.RegisterOrderServiceServer(s, &orderServer{})the method GetOrder of orderServer
Pythonorders_pb2_grpc.add_OrderServiceServicer_to_server(OrderServicer(), server)the method GetOrder of OrderServicer
Node (grpc-js)server.addService(proto.orders.v1.OrderService.service, { getOrder })the function the map names under GetOrder or getOrder
Node (grpc-js)server.addService(…, new MyHandlers())the method getOrder of MyHandlers
Node (grpc-js)const impl = { getOrder }; server.addService(…, impl)the function the literal names, as if written at the call

The rules refuse rather than guess:

  • The implementation must be written at the registration: &orderServer{}, OrderServicer(), an object literal, new MyHandlers(). A variable or a factory call (NewServer(), makeHandlers()) names no type, so the registration is disclosed as not-literal.

  • One Node exception, because the file settles it: a bare local name passed to addService is read as the object literal it was bound to, when this file binds it exactly once — one unindented top-level const/let/var whose initializer is an object literal, with no later assignment to the name or one of its members, no Object.assign onto it, no computed access, no delete, and no import of that name. This is the shape every official grpc-node example uses. Anything else — bound twice, bound inside a function, reassigned, imported, or bound to a call's result — keeps the not-literal disclosure. new Impl().wrap(), new Impl(c).handlers, this.handlers and impl.handlers name a value, not a type, and stay disclosed too.

  • A service named by two packages, or declared in two files, binds nothing (ambiguous-service). A service no file read declares — its .proto lives in another repository, or in a file that could not be read, which the detail then says — is unknown-service. A Node service path of three or more parts (proto.billing.v2.PaymentService) binds only to a declared full name it ends with; a one- or two-part path (services.PaymentService) states no package, so it falls back to a service of that simple name declared exactly once.

  • The implementing method is looked for only where the registration's own scope says the type is. Go: a bare orderServer is in the registration's own package (its directory) and nowhere else; v2.OrderServer is in another package whose directory is named v2. Python: a bare class is in the registration's file or, failing that, the only one anywhere; servicer.OrderServicer is in a module named servicer. Node: the registration's file, else — only when the file imports that handler — the only one anywhere. Two candidates in that scope bind nothing (ambiguous-implementation); a lone candidate outside it is never taken. For Go and Python the type's location is settled once per registration: a type defined in two places makes the whole registration ambiguous, so no RPC binds to whichever place alone happens to define its method.

  • Generated stubs are never candidates (Python's *_pb2_grpc.py holds a client class named exactly like the service), and once a Python servicer class is found in the registration's own file, an RPC it does not implement is not looked for elsewhere. Inheritance is not followed: a method defined on a base class in another file is not bound.

  • A Go or Python registration none of whose methods is found where it may be, and a Node map entry whose handler is neither defined nor imported in the registration's file, are implementation-not-found.

  • A Node handler imported into the registration's file binds only in the module it was imported from. A declared path alias is followed: a tsconfig/jsconfig compilerOptions.paths entry or a package.json imports subpath, and only when exactly one mapping matches the specifier, it names exactly one target, and that target is exactly one indexed module. Two mappings matching, a fallback list, a value that is not an array of strings, a target climbing above the repository root, or a conditional imports object leave the specifier unresolved and disclosed. A webpack, jest or vite alias is arbitrary JavaScript and is never read; a tsconfig's extends chain is not followed, so a config that inherits its paths declares none here. A real package binds nothing.

  • A Node class instantiated at the registration is looked for in the registration's own file, or — when the class name is imported — only in the module it was imported from (one barrel re-export followed). A default or whole-module import does not name the class it holds, so it binds nothing; new ns.MyHandlers() binds only when ns is an imported namespace. As for Go and Python the class's location is settled once per registration: a class of that name in two places makes the whole registration ambiguous-implementation.

  • Node keys match the RPC name, its lower-camel form, or grpc-js's lodash camelCase (GetHTTPStatus → getHttpStatus).

  • Node's static-codegen form, addService(services.GreeterService, { … }), is read too, and binds only when it names a declared service — addService is a common method name.

  • Registrations in comments, strings and test files are not read, and generated stubs are skipped: they define the registration function rather than call it.

  • Other styles are not read: Go s.RegisterService(&pb.X_ServiceDesc, impl), Python keyword arguments, and quoted keys in a Node implementation map. Their RPCs stay in unboundRpcs.

What did not bind is in the build result: unresolvedRegistrations (with the reasons above) and unboundRpcs. Registrations are read in Go, Python and Node only, so an RPC served from Java or C# is in unboundRpcs too: that list means "not bound", never "not implemented". gRPC client calls are not read, so a protobuf RPC has no consumes_endpoint edge.

A method that serves an RPC is invoked from outside the code, so — like an HTTP handler — it is a reachability root rather than a dead-code candidate. A registration that bound nothing leaves its implementation unrooted, so find_dead_code names it under contractRootsNotAssessed. What is said after the fact and withholds nothing — a type imported from outside the repository, an extend block — is under notes, not notAssessed.

Registrations are read when the repository is analyzed. Like a specification, they are not re-read by the watcher's per-file rebuild, so run openlore analyze after changing which type a registration names.

Breaking changes

certify_service_contract compares the .proto files of two revisions with protobuf rows in the same closed rule table. A field's number is its identity on the wire, so fields are matched by number:

ChangeClass
A field renamed (same number)dangerous — the binary wire still works; JSON and text formats do not
A field's number, type, or label (singular / repeated / map) changed; moved into or out of a oneofbreaking
A field removed without reserving its numberbreaking — a later field can reuse the number with another type
A field removed with its number reserveddangerous
A field added on a number or name the previous revision reservedbreaking
A reservation removeddangerous
optional added or removeddangerous
Two fields' numbers swapped, or a field moved to another numberbreaking (field-number-changed)
An enum value removed (breaking, or dangerous when its NUMBER is reserved), renumbered (breaking), added (non-breaking)as stated
An enum value renamed, or an alias removed while its number staysdangerous (enum-value-renamed)
An enum value newly deprecateddangerous
An RPC removed, its request or response type changed, its streaming changedbreaking
An RPC or a field newly deprecateddangerous
A message or enum removedbreaking
A field option other than deprecated (json_name, packed, a custom option)potentially-breaking

Messages and RPCs are matched by full name across files, so moving a message to another file is not a change; renaming the package changes every wire name, so everything is removed and added. A gRPC finding names no consumer — client call sites are not read — and says so.

GraphQL

Every .graphql, .graphqls and .gql file is read with a hand-written SDL reader. A file that holds only operations and fragments is a client document, not a schema: it is listed under skippedCandidates with that reason. All schema files read are merged into one schema, as one server sees them — extend type Query { … } in another file adds to Query; a type two files define is ambiguous and binds nothing. A type only ever extended — a Federation 1 subgraph's extend type Query and extend type User @key(…) — is this repository's contract and is compared as one. When any file defines schema { … }, the roots are exactly the ones it names.

GraphQL conceptOpenLore graph primitive
A root field (order on the query root)an operation node Query.order, language: GraphQL
An object, interface, input, enum, union or scalar typea node named by the type, Order
The types a field, an argument, a union member or an implemented interface namesreferences_schema edges
The function a resolver map names for a root fieldan implements_endpoint edge, function → operation

Directives other than @deprecated are outside the model: each one is named in the projection's notes, and hashed so a change to one is graded potentially-breaking, never "no change".

Which code resolves an operation

A schema-first server names the function behind each root field in one literal place:

RuntimeResolver mapQuery.order is resolved by
JS/TS (Apollo, Yoga, graphql-tools){ Query: { order: getOrder } }, const Query = { order() {…} }getOrder, or the method itself
Python (Ariadne)@query.field("order") on def resolve_order, query.set_field("order", fn)the decorated or named function

The same refusals as for gRPC apply. A named function binds in the map's own file — a top-level function or a method written in the map, never a class method, and never the member of a local object (repo.find); an IMPORTED one binds only in the module it was imported from (by its exported name) — never a same-named function elsewhere, and a package or path-alias import binds nothing (implementation-not-found). An arrow function or lambda is inline-implementation; two candidates are ambiguous-implementation; a spread, or a root set from a variable or a call (Query: queryResolvers), is not-literal; a key the schema does not declare is unknown-field. Apollo's x: { subscribe: fn } binds fn.

Not every Query: {…} is a server's. Client configuration is skipped: a file importing a GraphQL client (@apollo/client, urql, Relay…) and no server, and objects inside client constructors (cacheExchange, ApolloClient, InMemoryCache) or under client keys (typePolicies, updates, optimistic), and permission and middleware maps (graphql-shield, graphql-middleware). So are TypeScript resolver TYPES (interface Resolvers { Query: … }), stories and mocks, and a const Query = {…} in a file that is not about resolvers. Maps in comments, strings, test files and *.generated.* files are not read, and neither are schemas under test or fixture paths — a fixture's type Query would make the real root ambiguous. A root type defined in two files is reported as not assessed, which find_dead_code repeats.

An import is followed one level through a barrel (export * from './feature', export { a as b } from …, a Python package's __init__.py); when the module is indexed but does not define or directly re-export the name, the detail says so. A file that writes its OWN schema inline (typeDefs: `type Query {…}` , Ariadne gql("""…""")) serves that schema, so its maps are not bound to the repository's SDL; neither are files importing a mocking library (msw, @graphql-tools/mock). A schema inside a Relay project's own directory that its config names ("relay": { "schema": … }) is the compiler's copy of a server's schema, and a file whose types sit beside @client operations is Apollo local state: analyze reads neither as this repository's contract, and lists both under skippedCandidates. certify_service_contract never drops a schema on a config's say-so — it grades the file and names the config that named it in its boundaries. One schema per repository means a monorepo with several servers' SDL makes their shared roots ambiguous — disclosed, not guessed.

Resolvers of ordinary TYPE fields (Order: { customer }, @order.field("customer")) are not bound: a type field is not a node.

Known boundaries, disclosed where they bite: a schema a frontend vendors for graphql-codegen or apollo.config.js is graded as this repository's contract — now with a boundary line naming that config (see Schema provenance and discovery exclusions below); an Ariadne bindable created in one module and decorated in another is not followed (its operations stay unbound); a map in a directory holding an inline schema is not bound to the repository's SDL; and an operation two maps bind keeps both edges, with a note naming it. Code-first servers (Strawberry, Graphene, Nexus, gqlgen) have no resolver map, so their operations are listed in unboundGraphqlOperations — "not bound", never "not served".

Schema provenance and discovery exclusions

A codegen output or a vendored copy of someone else's contract used to be graded as this repository's own, silently. It is now disclosed, never dropped — a verdict that drops a schema is a gate bypass, and a provenance signal is a guess, so it only annotates.

Each graded schema's specs entry carries provenance when a rule matched:

Rulematchedcertainty
A generator header in the file's bounded head (# @generated, Code generated … DO NOT EDIT., AUTO-GENERATED FILE, …)the marker id, e.g. at-generateddeclared
A path pattern: __generated__/, generated/, *.generated.*that patternpath-pattern
A vendored path: node_modules/, vendor/, vendored/, third_party/that patternpath-pattern

The marker ids and pattern names are the generated-file guard's own vocabulary; a path-pattern guess is never reported as declared, and a header is looked for in a bounded head of the file, never in a 10 MB schema whole.

A client-toolchain config that names a schema — Relay (package.json "relay", relay.config.*), Apollo (apollo.config.*), graphql-config (graphql.config.*, .graphqlrc*) or GraphQL Code Generator (codegen.*) — produces one boundary line naming that config and the schema. A config is read for one literal schema path inside its own directory: a value that climbs out (../schema.graphql), a URL, an interpolation or a glob is never claimed, because a config in this repository must not let OpenLore speak for someone else's schema — or stop speaking about this repository's own. On the analyze side a Relay copy is still dropped (and listed under skippedCandidates); certify_service_contract drops nothing on any config's say-so.

Discovery excludes node_modules/, vendor/, dist/, build/ and .git/ — for untracked files only. A tracked file is part of the repository by definition, and the blanket rule deleted four hand-written Google API protos (google/devtools/build/v1/build_events.proto and friends) from certify because build/ appears in their path. node_modules/ is the one named exception applied to tracked files too: a committed dependency tree is genuinely another project's contract. Either way the exclusion is named in a boundaries line with the count and the first few paths, and the same rule runs on the base revision, so an exclusion never reads as an addition or a removal.

Breaking changes

Operations are matched by root kind and field name — a client writes query { order }, never the root type's name, so renaming Query to RootQuery is not a change. Nullability follows variance:

ChangeClass
An operation or a field removed; an argument removedbreaking
An output made nullable (String! → String), or a type changedbreaking
An output made non-null (String → String!), including inside a listnon-breaking
An argument or input field made required, or a required one addedbreaking
An argument or input field made optional, or an optional one addednon-breaking
A non-null argument or input field losing its default (it is now required)breaking
A default value added, changed or removed otherwise (compared canonically: {a: 1, b: 2} = {b: 2, a: 1}, 1 = 1.0)dangerous
An enum value or union member added (a client switching over it may not handle it)dangerous
An interface newly implementeddangerous
An enum value or union member removed; an interface no longer implementedbreaking
A directive definition removed, a location removed from it, repeatable removed, or a required argument addedbreaking
Deprecation of an operation, field, argument or enum valuedangerous
A directive other than @deprecated used on an element or on schema changedpotentially-breaking
A type or root defined in two files (on either side), or roots named differently by two filespotentially-breaking — not compared

A GraphQL finding names no consumer — client operations are not read — and says so.

Generated clients

A generated API client builds its URL from a base variable — fetch(${BASE}/orders/${id}) — so the HTTP projection extracts no literal path and binds nothing by path: the operation it calls reads as consumed by no one, which is false. Its method name, though, is the operationId, an exact identifier rather than a resemblance.

So where a module's header names a specification as the source it was generated from, a function in that module whose name is exactly an operationId of that specification is linked to that operation with a consumes_endpoint edge.

A wrong edge is worse than a missing one, so the header declaration carries the whole safety of the rule and is held to three conditions — each one closing a wrong edge found by review:

ConditionWhat it stops
The reference is in a comment in the headerA loader that reads the specification at runtime, a fixture path, a string constant.
The same line carries a generation marker (@generated, generated from, DO NOT EDIT, …)// See api/openapi.yaml for the wire contract these mirror. on a hand-written module.
The reference matches on path boundariesapi/openapi.yaml naming third-party/stripe/openapi.yaml — openapi.yaml is the common filename, and a substring test bound a client to a vendored spec, making the graph assert the repository called Stripe.

Projected declarations are not candidates either: a Bicep var getOrder in a file whose header named the specification was otherwise bound, linking a declaration to a declaration. Nor are test modules, whose fixtures mention a specification for conformance and whose helpers collide readily with a short operationId such as list.

Where the name matches more than one function node, or a document declares one operationId twice, nothing binds and the case is disclosed in unresolvedGeneratedBindings. Two same-named functions in ONE module collapse to a single graph node before the guard sees them, so that case binds the surviving node.

Such an edge records synthesizedBy: 'operationId', because it has no literal call site by construction — that is why it is bound by name. verify_claim endpoint-consumed reads that provenance and confirms the consumption instead of reporting a snapshot disagreement it would otherwise infer from finding no call text.

The edge's evidence is a comment in a code file, which the watcher does re-read — but the watcher never re-projects contracts, and contract edges are preserved across a subset rebuild. So deleting a @generated from … header does not drop the binding until the next openlore analyze.

Consumers in other repositories

A service's consumers are usually not in its own repository, so a breaking verdict computed from this repository alone says "no caller in this repository" for exactly the change most worth stopping. Pass federation: true (or --federation) and certify_service_contract also censuses the client call sites of the indexed siblings in .openlore/federation.json, reporting each hit against the sibling's registry name — its stable id — never a path on this machine.

A finding then distinguishes three states, which the single in-repo answer collapsed:

StateWhat the finding says
Called herecalled from src/client.ts:12
Called only in a siblingno caller in this repository, but called from web:src/client.ts:4
Called in neitherno caller in this repository or in 3 censused sibling repositories (1 not censused)

The third is the one that must not read as proof of safety, so it names how many siblings were censused, how many were only partly scanned, and how many were not censused at all.

The same consumer rule applies across repositories as within one — and getting this wrong is how a tool accuses an innocent repository:

RuleWhy
A call counts only if its URL is host-less or names a declared servers hostAcross repositories a host mismatch is the normal case. Without this, a sibling's call to another vendor on the same path was reported as consuming your operation, inside an error finding.
An all-parameter path (/{tenant}/{id}) is excluded and disclosedIt matches almost any URL of that segment count — a Stripe call and a GitHub call were both reported as consumers.
Only breaking and dangerous addresses are censusedThe same scope the in-repository join uses; no finding asks about the rest.
Sibling directories are compared by canonical pathOtherwise the repository under certification is censused as its own sibling (/tmp vs /private/tmp), and two names for one directory count every call site twice.
Every repository-derived string is screenedA registry name is attacker-influenced text; an instruction-shaped one was being served verbatim through the boundary list.

federationCensus reports repos, partial and skipped — a sibling that is missing, unindexed, stale or unbaselined is named as not censused, never counted consumer-free; one whose scan hit a file cap is named as partly scanned; a registry name you asked for that does not exist is reported rather than ignored; and a registry that cannot be read is named rather than answered as though you had not asked.

Consumers come back as one bounded top-level federatedConsumers list with an exact omitted count, each entry tagged with its operation address. Nested per changed element they were unreachable to the response trimmer, and a 200-operation change blew the byte budget and replaced the whole verdict with a cut JSON string. Without the flag, the tool says plainly that consumers outside this repository were not assessed.

Editing a specification

A specification is not a call-graph node and declares no handler of its own, so a diff that edits only openapi.yaml used to brief an agent on nothing at all: no impacted symbols, no tests, no contracts. blast_radius now carries a changedContracts field for exactly that diff — the elements the changed specifications declare, and for each one the indexed handler that serves it and the in-repo call sites that consume it. Consumers are read from the whole repository, not from the diff, because a specification change affects callers the diff never touched.

It is a blast radius, not a verdict. It does not compare against a base revision and does not grade the change; certify_service_contract with a base ref owns that conclusion, and the field's note says so.

Honesty rules, the same as everywhere else here. unbound is the only new claim the field makes, so it is the one held hardest: an element is unbound only when nothing stood in the way of seeing a binding — a concrete-method route is indexed, no route matched it ambiguously, the consumer scan ran and was not capped, and its path is not all-parameter. Each of those is otherwise disclosed as not assessed. Specifically:

SituationWhy unbound is withheld
No route inventoryNo index is not no handler.
Every indexed route has a wildcard method (Django)Such a route cannot be matched to an operation, but it may well serve it.
Several routes match one operationIt is ambiguous, not unbound; the candidates are named and no single handler is.
The operation path is all parameters (/{kind}/{id})It matches too broadly to attribute either a handler or a caller.
The consumer scan was capped or failedAbsence of evidence, not evidence of absence.

A call site counts as a consumer only when its literal URL has no host (relative — this service) or a host the specification's own servers declares; a call to another vendor on the same path shape is not a consumer, and the count of such calls is disclosed. servedBy is read from the route inventory as of the last analyze, which the field says, since a handler added since then is not reflected. The consumer scan reads TypeScript, JavaScript, Python and Go, which it also says. A changed specification that cannot be parsed, cannot be read, was never examined under the scan cap, or parses with no operation the reader recognizes is named rather than dropped.

Not read

Per-FIELD nodes (named schemas are projected; individual properties are not); gRPC client calls and GraphQL client operations as consumers; code-first GraphQL servers (no SDL); registry or network fetches; runtime traffic; contract tests; code generation; any LLM.

512 files · 450 not served here

Advantages

  • Deterministic, local-first operation with no LLM in the hot path ensures consistent, grounded answers. evidence: README.md
  • Provides governance guardrails that flag breaking changes and sensitive boundary violations before code is committed. evidence: README.md
  • Maintains persistent memory across sessions that survives code renames and refactors. evidence: README.md
  • Reduces agent round-trips and costs by providing structural context instead of relying on exploratory file reads. evidence: README.md

Limitations

  • Static analysis cannot recover reflective invocations, eval, or dynamic DI/plugin registries with no visible bindings. evidence: README.md
  • Small, familiar repositories may experience overhead compared to native agent search. evidence: README.md
  • Large monorepos require incremental analysis strategies after the initial full build. evidence: README.md

Security

Risk level: Low risk

This brain runs

Agent instructions that run or fetch something (169)

  • .claude/commands/first-run-hardening.md: Your job: find and fix the bugs that break a NEW USER's first-run experience — the way earlier passes found "unknown command 'orient'", "--no-embed kills BM25", "watcher EMFILE on target/", and "install never builds the index".
  • .claude/commands/first-run-hardening.md: BUILD FIRST so you test current code, not the published version: `npm run build` → drive `node dist/cli/index.js`, never bare `openlore`/`npx openlore` (those resolve the OLD published version).
  • .claude/commands/first-run-hardening.md: When comparing to published behavior, do it explicitly with `npx --yes openlore@latest` and label it.
  • .claude/commands/first-run-hardening.md: Run the REAL first-run flow as a user would, end to end, checking exit codes AND actual output of each step: openlore install # the one-command path
  • .claude/commands/first-run-hardening.md: Also exercise: init → analyze → analyze --no-embed → analyze --force → orient (no task / with task / bad --limit) → install --no-analyze → install --dry-run → install --uninstall → mcp server over stdio (initialize → tools/call orient).
  • .claude/commands/first-run-hardening.md: Idempotency / re-runs: running install or analyze twice must be a clean no-op, not a duplicate or a clobber.
  • .claude/commands/first-run-hardening.md: Treat other people's repos as READ-ONLY fixtures: after each dogfood run, remove ALL openlore artifacts (.openlore, openspec/, CLAUDE.md, ARCHITECTURE.md) and `git checkout` any modified .gitignore / settings.json.
  • .claude/commands/first-run-hardening.md: Verify the repo is clean (`git status`) before moving on.
  • .claude/commands/first-run-hardening.md: Verify honestly: run `npm run typecheck` and `npm run test:run` and report the REAL counts.
  • .claude/commands/first-run-hardening.md: confirm with `git rev-list --left-right --count origin/main...main` (want `0 0`) before and after.
  • .claude/commands/first-run-hardening.md: Branch pruning: only delete branches already merged into origin/main (verify with `git branch -r --merged origin/main`);
  • AGENTS.md: Before committing code (pre-commit hook via `openlore drift --install-hook`)
  • AGENTS.md: openlore drift --install-hook # Install as git pre-commit hook
  • AGENTS.md: [ ] No spec drift — run `openlore drift` to verify specs match code
  • AGENTS.md: - Run `openspec validate --all` to verify structure
  • AGENTS.md: - Run `openlore drift --install-hook` to catch future drift
  • AGENTS.md: Always run `openlore analyze` first.
  • AGENTS.md: Or install the Vibe skill (creates `.vibe/skills/openlore.md` as a `/openlore` slash command):
  • AGENTS.md: Because the output limit is 4 096 tokens (vs 16 000 for Claude), generation produces **shorter specs per run**.
  • AGENTS.md: **One domain at a time** — run `openlore generate` with `--domain <name>` so each invocation focuses on a single domain and stays within the output budget.
  • AGENTS.md: **Use the analysis artifacts** — always run `openlore analyze` first;
  • AGENTS.md: **Expect multi-pass generation** — large codebases may require multiple `generate` runs (one per domain) rather than a single all-domains run.
  • AGENTS.md: Then run `openlore decisions --sync` and retry `git commit`.
  • AGENTS.md: **`reason: "approved_not_synced"` — decisions approved but not written to specs:** Run `openlore decisions --sync` then retry `git commit`.
  • AGENTS.md: If yes: run `openlore decisions --consolidate --gate` and handle the result.
  • AGENTS.md: If no: retry with `git commit --no-verify` to skip the gate.
  • AGENTS.md: If yes: run `openlore decisions --consolidate --gate` and handle the result.
  • AGENTS.md: If no: retry with `git commit --no-verify` to skip the gate.
  • CLAUDE.md: CLI: `openlore coverage-gaps` | | "What's the blast radius of my diff before I commit?" | `blast_radius` — one advisory briefing: callers/layers, tests to run, anchored memories/decisions that will drift, stale specs | | "Post a deterministic structural review on a PR" (CLI, no MCP/agen …[+99 chars]
  • CLAUDE.md: Call `orient "<task description>"` (via the openlore MCP server, or `npx openlore orient --json`) **before touching a module you have not yet read in this session**, and when a task spans several modules.
  • CLAUDE.md: re-run `openlore install --preset <name>` to change it and regenerate these instructions.
  • examples/mistral-vibe/skills/openlore-analyze-codebase/SKILL.md: Suggest running `/openlore-plan-refactor` once the user has enough context to act, then `/openlore-execute-refactor` to apply the plan
  • examples/mistral-vibe/skills/openlore-analyze-codebase/SKILL.md: To activate `search_specs`, run `openlore analyze --embed` or `openlore analyze --reindex-specs`.
  • examples/mistral-vibe/skills/openlore-brainstorm/SKILL.md: Using Domain Sketch — structural analysis will be available after `openlore analyze` is run on the first implementation."
  • examples/mistral-vibe/skills/openlore-brainstorm/SKILL.md: - First implementation — run `openlore analyze && openlore generate` after
  • examples/mistral-vibe/skills/openlore-debug/SKILL.md: **Prerequisite**: openlore analysis must exist (`openlore analyze` has been run).
  • examples/mistral-vibe/skills/openlore-debug/SKILL.md: If `orient` returns `"error": "no cache"` → run `analyze_codebase` first, then retry.
  • examples/mistral-vibe/skills/openlore-debug/SKILL.md: Run it.
  • examples/mistral-vibe/skills/openlore-debug/SKILL.md: **Apply the fix**, then run the test again.
  • examples/mistral-vibe/skills/openlore-debug/SKILL.md: Run the full test suite.
  • examples/mistral-vibe/skills/openlore-debug/SKILL.md: If the domain spec does not exist yet (`uncovered` from Step 8), note the invariant in the story/issue instead and flag it for the next `openlore generate` run.
  • examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md: "execute the planned refactoring"
  • examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md: explicit command `/openlore-execute-refactor`
  • examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md: **Prerequisite**: the `openlore-plan-refactor` skill must have been run and the plan confirmed.
  • examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md: If a coverage tool is available, run it on the target file and compare against the coverage baseline in the plan.
  • examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md: git status # must show: nothing to commit, working tree clean
  • examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md: git log --oneline -1 # note this commit hash — your restore point
  • examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md: For **each change** in the plan, execute the full mini-development cycle below.
  • examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md: **4 — TEST** Run the test gate from the plan entry.
  • examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md: Run the full test suite one final time to confirm the refactored state is clean.
  • examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md: Apply renames one file at a time, run tests after each, and respect the ≤ 50-line edit constraint.**
  • examples/mistral-vibe/skills/openlore-generate/SKILL.md: OpenLore does not validate OpenSpec structure, so run the `openspec validate` follow-up the receipt names.
  • examples/mistral-vibe/skills/openlore-generate/SKILL.md: **If validation cannot run — the CLI is missing, broken, or fails for any reason — say so explicitly and report the spec as NOT validated.** Never present an unvalidated spec as complete;
  • examples/mistral-vibe/skills/openlore-generate/SKILL.md: Finalize: after validation, run `openlore mapping refresh` when shell access is available so the persisted link index matches the spec you wrote.
  • examples/mistral-vibe/skills/openlore-generate/SKILL.md: If you cannot run it, say so explicitly — correctness is unaffected, because audit and Repair re-derive the index in memory, and only the cache is stale.
  • examples/mistral-vibe/skills/openlore-implement-story/SKILL.md: **Prerequisite**: openlore analysis must exist (`openlore analyze` has been run).
  • examples/mistral-vibe/skills/openlore-implement-story/SKILL.md: If `orient` returns `"error": "no cache"` → run `analyze_codebase` first, then retry.
  • examples/mistral-vibe/skills/openlore-implement-story/SKILL.md: | Situation | Approach | |---|---| | `risk_context` present, risk 🟢 < 40 | Skip to Step 3 — use insertion point from context | | `risk_context` present, risk 🟡 40–69 | Run Step 2 impact check, then proceed | | `risk_context` present, risk 🔴 ≥ 70 | Stop — a blocking refactor story mus …[+81 chars]
  • examples/mistral-vibe/skills/openlore-implement-story/SKILL.md: Run `/openlore-execute-refactor` on the blocking function first, or create a blocking refactor task and return to this story once the risk is resolved.
  • examples/mistral-vibe/skills/openlore-implement-story/SKILL.md: Run the `openlore-repair` host skill before implementing.
  • examples/mistral-vibe/skills/openlore-implement-story/SKILL.md: Only run once tests are green.
  • examples/mistral-vibe/skills/openlore-implement-story/SKILL.md: | Drift type | Resolution | |---|---| | `uncovered` on new files | Note it — propose `openlore generate` post-sprint | | `gap` on existing domain | Run the `openlore-repair` host skill | | `stale` | Fix the reference | | No drift | Done |
  • examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md: To apply the plan, use the `openlore-execute-refactor` skill.
  • examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md: Each change = one atomic edit → one diff verification → one full test run → ✅ or rollback.
  • examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md: If a recent analysis already exists, skip unless the user explicitly requests a fresh run.
  • examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md: | Ecosystem | Command | |---|---| | Node.js | `npm test -- --coverage --collectCoverageFrom="<files>"` | | Python | `pytest --cov=<module> --cov-report=term-missing` | | Rust | `cargo tarpaulin --include-files <files>` | | Go | `go test -cover ./...` |
  • examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md: **Test gate**: exact test command to run after this change
  • examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md: Workflow: /openlore-plan-refactor → /openlore-execute-refactor
  • examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md: - `git diff --stat` shows only the expected files
  • examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md: Hash: <to be filled by the execute workflow>
  • examples/mistral-vibe/skills/openlore-write-tests/SKILL.md: **Prerequisite**: openlore analysis must exist (`openlore analyze` has been run).
  • examples/mistral-vibe/skills/openlore-write-tests/SKILL.md: If `orient` returns `"error": "no cache"` → run `openlore analyze` first, then retry.
  • examples/mistral-vibe/skills/openlore-write-tests/SKILL.md: | File found | Framework | |---|---| | `vitest.config.*`, `vitest.config.ts` | Vitest — runner: `npx vitest run <file>` | | `jest.config.*` | Jest — runner: `npx jest <file>` | | `pytest.ini`, `pyproject.toml` (with `[tool.pytest]`), `setup.cfg` | pytest — runner: `pytest <file> -v` | …[+147 chars]
  • examples/mistral-vibe/skills/openlore-write-tests/SKILL.md: Run the test file with `$TEST_RUNNER`.
  • examples/opencode-skills/openlore-analyze-codebase/SKILL.md: Suggest running `/openlore-plan-refactor` once the user has enough context to act, then `/openlore-execute-refactor` to apply the plan
  • examples/opencode-skills/openlore-analyze-codebase/SKILL.md: To activate `search_specs`, run `openlore analyze --embed` or `openlore analyze --reindex-specs`.
  • examples/opencode-skills/openlore-brainstorm/SKILL.md: Using Domain Sketch — structural analysis will be available after `openlore analyze` is run on the first implementation."
  • examples/opencode-skills/openlore-brainstorm/SKILL.md: - First implementation — run `openlore analyze && openlore generate` after
  • examples/opencode-skills/openlore-debug/SKILL.md: **Prerequisite**: openlore analysis must exist (`openlore analyze` has been run).
  • examples/opencode-skills/openlore-debug/SKILL.md: If `orient` returns `"error": "no cache"` → run `analyze_codebase` first, then retry.
  • examples/opencode-skills/openlore-debug/SKILL.md: Run it.
  • examples/opencode-skills/openlore-debug/SKILL.md: **Apply the fix**, then run the test again.
  • examples/opencode-skills/openlore-debug/SKILL.md: Run the full test suite.
  • examples/opencode-skills/openlore-debug/SKILL.md: If the domain spec does not exist yet (`uncovered` from Step 8), note the invariant in the story/issue instead and flag it for the next `openlore generate` run.
  • examples/opencode-skills/openlore-execute-refactor/SKILL.md: "execute the planned refactoring"
  • examples/opencode-skills/openlore-execute-refactor/SKILL.md: explicit command `/openlore-execute-refactor`
  • examples/opencode-skills/openlore-execute-refactor/SKILL.md: **Prerequisite**: the `openlore-plan-refactor` skill must have been run and the plan confirmed.
  • examples/opencode-skills/openlore-execute-refactor/SKILL.md: If a coverage tool is available, run it on the target file and compare against the coverage baseline in the plan.
  • examples/opencode-skills/openlore-execute-refactor/SKILL.md: git status # must show: nothing to commit, working tree clean
  • examples/opencode-skills/openlore-execute-refactor/SKILL.md: git log --oneline -1 # note this commit hash — your restore point
  • examples/opencode-skills/openlore-execute-refactor/SKILL.md: For **each change** in the plan, execute the full mini-development cycle below.
  • examples/opencode-skills/openlore-execute-refactor/SKILL.md: **4 — TEST** Run the test gate from the plan entry.
  • examples/opencode-skills/openlore-execute-refactor/SKILL.md: Run the full test suite one final time to confirm the refactored state is clean.
  • examples/opencode-skills/openlore-execute-refactor/SKILL.md: Apply renames one file at a time, run tests after each, and respect the ≤ 50-line edit constraint.**
  • examples/opencode-skills/openlore-generate/SKILL.md: OpenLore does not validate OpenSpec structure, so run the `openspec validate` follow-up the receipt names.
  • examples/opencode-skills/openlore-generate/SKILL.md: **If validation cannot run — the CLI is missing, broken, or fails for any reason — say so explicitly and report the spec as NOT validated.** Never present an unvalidated spec as complete;
  • examples/opencode-skills/openlore-generate/SKILL.md: Finalize: after validation, run `openlore mapping refresh` when shell access is available so the persisted link index matches the spec you wrote.
  • examples/opencode-skills/openlore-generate/SKILL.md: If you cannot run it, say so explicitly — correctness is unaffected, because audit and Repair re-derive the index in memory, and only the cache is stale.
  • examples/opencode-skills/openlore-implement-story/SKILL.md: **Prerequisite**: openlore analysis must exist (`openlore analyze` has been run).
  • examples/opencode-skills/openlore-implement-story/SKILL.md: If `orient` returns `"error": "no cache"` → run `analyze_codebase` first, then retry.
  • examples/opencode-skills/openlore-implement-story/SKILL.md: | Situation | Approach | |---|---| | `risk_context` present, risk 🟢 < 40 | Skip to Step 3 — use insertion point from context | | `risk_context` present, risk 🟡 40–69 | Run Step 2 impact check, then proceed | | `risk_context` present, risk 🔴 ≥ 70 | Stop — a blocking refactor story mus …[+81 chars]
  • examples/opencode-skills/openlore-implement-story/SKILL.md: Run `/openlore-execute-refactor` on the blocking function first, or create a blocking refactor task and return to this story once the risk is resolved.
  • examples/opencode-skills/openlore-implement-story/SKILL.md: Run the `openlore-repair` host skill before implementing.
  • examples/opencode-skills/openlore-implement-story/SKILL.md: Only run once tests are green.
  • examples/opencode-skills/openlore-implement-story/SKILL.md: | Drift type | Resolution | |---|---| | `uncovered` on new files | Note it — propose `openlore generate` post-sprint | | `gap` on existing domain | Run the `openlore-repair` host skill | | `stale` | Fix the reference | | No drift | Done |
  • examples/opencode-skills/openlore-plan-refactor/SKILL.md: To apply the plan, use the `openlore-execute-refactor` skill.
  • examples/opencode-skills/openlore-plan-refactor/SKILL.md: Each change = one atomic edit → one diff verification → one full test run → ✅ or rollback.
  • examples/opencode-skills/openlore-plan-refactor/SKILL.md: If a recent analysis already exists, skip unless the user explicitly requests a fresh run.
  • examples/opencode-skills/openlore-plan-refactor/SKILL.md: | Ecosystem | Command | |---|---| | Node.js | `npm test -- --coverage --collectCoverageFrom="<files>"` | | Python | `pytest --cov=<module> --cov-report=term-missing` | | Rust | `cargo tarpaulin --include-files <files>` | | Go | `go test -cover ./...` |
  • examples/opencode-skills/openlore-plan-refactor/SKILL.md: **Test gate**: exact test command to run after this change
  • examples/opencode-skills/openlore-plan-refactor/SKILL.md: Workflow: /openlore-plan-refactor → /openlore-execute-refactor
  • examples/opencode-skills/openlore-plan-refactor/SKILL.md: - `git diff --stat` shows only the expected files
  • examples/opencode-skills/openlore-plan-refactor/SKILL.md: Hash: <to be filled by the execute workflow>
  • examples/opencode-skills/openlore-write-tests/SKILL.md: **Prerequisite**: openlore analysis must exist (`openlore analyze` has been run).
  • examples/opencode-skills/openlore-write-tests/SKILL.md: If `orient` returns `"error": "no cache"` → run `openlore analyze` first, then retry.
  • examples/opencode-skills/openlore-write-tests/SKILL.md: | File found | Framework | |---|---| | `vitest.config.*`, `vitest.config.ts` | Vitest — runner: `npx vitest run <file>` | | `jest.config.*` | Jest — runner: `npx jest <file>` | | `pytest.ini`, `pyproject.toml` (with `[tool.pytest]`), `setup.cfg` | pytest — runner: `pytest <file> -v` | …[+147 chars]
  • examples/opencode-skills/openlore-write-tests/SKILL.md: Run the test file with `$TEST_RUNNER`.
  • skills/openlore-analyze-codebase/SKILL.md: Suggest running `/openlore-plan-refactor` once the user has enough context to act, then `/openlore-execute-refactor` to apply the plan
  • skills/openlore-analyze-codebase/SKILL.md: To activate `search_specs`, run `openlore analyze --embed` or `openlore analyze --reindex-specs`.
  • skills/openlore-brainstorm/SKILL.md: Using Domain Sketch — structural analysis will be available after `openlore analyze` is run on the first implementation."
  • skills/openlore-brainstorm/SKILL.md: - First implementation — run `openlore analyze && openlore generate` after
  • skills/openlore-debug/SKILL.md: **Prerequisite**: openlore analysis must exist (`openlore analyze` has been run).
  • skills/openlore-debug/SKILL.md: If `orient` returns `"error": "no cache"` → run `analyze_codebase` first, then retry.
  • skills/openlore-debug/SKILL.md: Run it.
  • skills/openlore-debug/SKILL.md: **Apply the fix**, then run the test again.
  • skills/openlore-debug/SKILL.md: Run the full test suite.
  • skills/openlore-debug/SKILL.md: If the domain spec does not exist yet (`uncovered` from Step 8), note the invariant in the story/issue instead and flag it for the next `openlore generate` run.
  • skills/openlore-execute-refactor/SKILL.md: "execute the planned refactoring"
  • skills/openlore-execute-refactor/SKILL.md: explicit command `/openlore-execute-refactor`
  • skills/openlore-execute-refactor/SKILL.md: **Prerequisite**: the `openlore-plan-refactor` skill must have been run and the plan confirmed.
  • skills/openlore-execute-refactor/SKILL.md: If a coverage tool is available, run it on the target file and compare against the coverage baseline in the plan.
  • skills/openlore-execute-refactor/SKILL.md: git status # must show: nothing to commit, working tree clean
  • skills/openlore-execute-refactor/SKILL.md: git log --oneline -1 # note this commit hash — your restore point
  • skills/openlore-execute-refactor/SKILL.md: For **each change** in the plan, execute the full mini-development cycle below.
  • skills/openlore-execute-refactor/SKILL.md: **4 — TEST** Run the test gate from the plan entry.
  • skills/openlore-execute-refactor/SKILL.md: Run the full test suite one final time to confirm the refactored state is clean.
  • skills/openlore-execute-refactor/SKILL.md: Apply renames one file at a time, run tests after each, and respect the ≤ 50-line edit constraint.**
  • skills/openlore-generate/SKILL.md: OpenLore does not validate OpenSpec structure, so run the `openspec validate` follow-up the receipt names.
  • skills/openlore-generate/SKILL.md: **If validation cannot run — the CLI is missing, broken, or fails for any reason — say so explicitly and report the spec as NOT validated.** Never present an unvalidated spec as complete;
  • skills/openlore-generate/SKILL.md: Finalize: after validation, run `openlore mapping refresh` when shell access is available so the persisted link index matches the spec you wrote.
  • skills/openlore-generate/SKILL.md: If you cannot run it, say so explicitly — correctness is unaffected, because audit and Repair re-derive the index in memory, and only the cache is stale.
  • skills/openlore-implement-story/SKILL.md: **Prerequisite**: openlore analysis must exist (`openlore analyze` has been run).
  • skills/openlore-implement-story/SKILL.md: If `orient` returns `"error": "no cache"` → run `analyze_codebase` first, then retry.
  • skills/openlore-implement-story/SKILL.md: | Situation | Approach | |---|---| | `risk_context` present, risk 🟢 < 40 | Skip to Step 3 — use insertion point from context | | `risk_context` present, risk 🟡 40–69 | Run Step 2 impact check, then proceed | | `risk_context` present, risk 🔴 ≥ 70 | Stop — a blocking refactor story mus …[+81 chars]
  • skills/openlore-implement-story/SKILL.md: Run `/openlore-execute-refactor` on the blocking function first, or create a blocking refactor task and return to this story once the risk is resolved.
  • skills/openlore-implement-story/SKILL.md: Run the `openlore-repair` host skill before implementing.
  • skills/openlore-implement-story/SKILL.md: Only run once tests are green.
  • skills/openlore-implement-story/SKILL.md: | Drift type | Resolution | |---|---| | `uncovered` on new files | Note it — propose `openlore generate` post-sprint | | `gap` on existing domain | Run the `openlore-repair` host skill | | `stale` | Fix the reference | | No drift | Done |
  • skills/openlore-orient/SKILL.md: This is the lowest-latency path and gives the model access to whatever tool surface the server was wired with — the lean 10-tool `navigation` preset by default (`openlore install --preset full` wires all 62), not just `orient`.
  • skills/openlore-orient/SKILL.md: bash scripts/orient.sh "<task description>"
  • skills/openlore-orient/SKILL.md: The wrapper tries the direct CLI subcommand first (`npx --yes openlore orient --json --task "<task>"`) and falls back to driving the `openlore mcp` server over stdio JSON-RPC via the sibling `orient-via-mcp.mjs` helper on older openlore versions that predate the CLI subcommand.
  • skills/openlore-orient/SKILL.md: Run `npx openlore analyze` once to seed the graph, then retry.
  • skills/openlore-orient/SKILL.md: Re-run `npx openlore analyze` to rebuild.
  • skills/openlore-plan-refactor/SKILL.md: To apply the plan, use the `openlore-execute-refactor` skill.
  • skills/openlore-plan-refactor/SKILL.md: Each change = one atomic edit → one diff verification → one full test run → ✅ or rollback.
  • skills/openlore-plan-refactor/SKILL.md: If a recent analysis already exists, skip unless the user explicitly requests a fresh run.
  • skills/openlore-plan-refactor/SKILL.md: | Ecosystem | Command | |---|---| | Node.js | `npm test -- --coverage --collectCoverageFrom="<files>"` | | Python | `pytest --cov=<module> --cov-report=term-missing` | | Rust | `cargo tarpaulin --include-files <files>` | | Go | `go test -cover ./...` |
  • skills/openlore-plan-refactor/SKILL.md: **Test gate**: exact test command to run after this change
  • skills/openlore-plan-refactor/SKILL.md: Workflow: /openlore-plan-refactor → /openlore-execute-refactor
  • skills/openlore-plan-refactor/SKILL.md: - `git diff --stat` shows only the expected files
  • skills/openlore-plan-refactor/SKILL.md: Hash: <to be filled by the execute workflow>
  • skills/openlore-repair/SKILL.md: OpenLore does not validate OpenSpec structure, so run the `openspec validate` follow-up the receipt names.
  • skills/openlore-repair/SKILL.md: **If validation cannot run — the CLI is missing, broken, or fails for any reason — say so explicitly and report the spec as NOT validated.** Never present an unvalidated edit as complete;
  • skills/openlore-repair/SKILL.md: Finalize: after validation, run `openlore mapping refresh` when shell access is available so the persisted link index matches the spec you edited.
  • skills/openlore-repair/SKILL.md: If you cannot run it, say so explicitly — correctness is unaffected, because audit and Repair re-derive the index in memory, and only the cache is stale.
  • skills/openlore-write-tests/SKILL.md: **Prerequisite**: openlore analysis must exist (`openlore analyze` has been run).
  • skills/openlore-write-tests/SKILL.md: If `orient` returns `"error": "no cache"` → run `openlore analyze` first, then retry.
  • skills/openlore-write-tests/SKILL.md: | File found | Framework | |---|---| | `vitest.config.*`, `vitest.config.ts` | Vitest — runner: `npx vitest run <file>` | | `jest.config.*` | Jest — runner: `npx jest <file>` | | `pytest.ini`, `pyproject.toml` (with `[tool.pytest]`), `setup.cfg` | pytest — runner: `pytest <file> -v` | …[+147 chars]
  • skills/openlore-write-tests/SKILL.md: Run the test file with `$TEST_RUNNER`.

Package scripts (1)

  • package.json (postinstall): node scripts/postinstall.mjs
  • Low risk: network-instructions (.claude/commands/first-run-hardening.md:21)
  • Low risk: scripts-present (bench/retrieval/run.ts)
  • Low risk: scripts-present (bench/run.ts)
  • Low risk: autonomy-phrase (examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md:240)
  • Low risk: autonomy-phrase (examples/opencode-skills/openlore-execute-refactor/SKILL.md:240)
  • Low risk: scripts-present (scripts/api-consumer-smoke.mjs)
  • Low risk: scripts-present (scripts/copy-assets.mjs)
  • Low risk: scripts-present (scripts/postinstall.mjs)
  • Low risk: autonomy-phrase (skills/openlore-execute-refactor/SKILL.md:240)
  • Low risk: network-instructions (skills/openlore-orient/SKILL.md:37)
  • Low risk: scripts-present (skills/openlore-orient/scripts/orient.sh)

Attribution and licence

Created by
Clay Good
Declared authors
  • Clay Good (declared in package.json)
Publisher
Clay Good (Community)
Source
clay-good/OpenLore · commit c45c2ca
Licence
MIT License (files served here)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Clay Good.

Install from source

450 of its files are not served here, so there is no ZIP. clay-good/OpenLore at c45c2ca