Not a wrapper around the GitHub API. A compiler pass — issues, PRs, files, and their real relationships become append-only, evidence-backed knowledge that any MCP-speaking agent can query directly.
A coding agent can grep your issues and diff your PRs, but it re-derives the same facts every session: which files a PR actually touched, which issue a merge closed, what depends on what. None of it persists, and none of it is traceable back to a source.
Object · Relationship · Event · Evidence — everything GitHub emits gets mapped onto these before it ever reaches an agent.
GitHubObserver walks GET /issues?state=all
and GET /pulls/{n}/files, distinguishing issues from PRs the same
way GitHub's own API does. A MockGitHubClient exercises the real
mapping logic with zero network dependency — the same discipline used for every other
connector in the codebase.
"{owner}/{repo}#{number}"[connectors.github] — owner, repo, token env varpub struct GitHubItem { pub number: u64, pub title: String, pub body: String, pub state: String, // "open" | "closed" pub is_pull_request: bool, pub files_changed: Vec<String>, } pub trait GitHubClient: Send + Sync { async fn list_items( &self, owner: &str, repo: &str ) -> Result<Vec<GitHubItem>, GitHubClientError>; }
// PR → changed file References(pr_id, file_id) evidence: "the PR's file-change entry" // body text: "Fixes #12" References(issue_47, issue_12) evidence: "body sentence containing keyword" // deterministic id — stable across re-runs Uuid::new_v5(NAMESPACE, "github:{owner}/{repo}#{n}")
Every PR's files_changed becomes a References
edge to that file's object. Every recognized closing keyword — closes, fixes, resolves —
becomes a References edge between items. No LLM guesses at the
relationship; GitHub already told us what closed what.
ekos recover converges.
The ledger never edits history — it only appends. That single invariant is what makes
ekos_diff possible downstream: "what has changed since T" is a
query over the log, not a guess from two snapshots.
ekos mcp servestdio, newline-delimited JSON-RPC 2.0 — the transport every MCP client already speaks. No network surface, no auth story, no new dependency.
| MCP tool | Answers |
|---|---|
| ekos_search | Find an object by name or kind |
| ekos_neighborhood | What's connected to this, N hops out |
| ekos_dependents | What breaks if this changes — incoming vs. outgoing edges |
| ekos_impact | Multi-hop blast radius across the whole graph |
| ekos_state | Reconstruct state as of any RFC 3339 timestamp |
| ekos_diff | What knowledge changed since T |
| ekos_ekl | Run a structured knowledge-query expression |
| ekos_status | Ledger entry / object / relationship counts |
| ekos_transformation_explain | What a legacy ETL/SQL transformation actually does, evidence per step |
| ekos_transformation_diff | Did a migration change a transformation's meaning |
| ekos_identity_review | Confirm/reject a cross-system identity match — the one write-capable tool |
The handler holds no write path — an agent can query the ledger but never mutate it.
Every Object and Relationship cites the exact source line that produced it — no unsourced claims.
Nothing is edited in place, so "what changed since T" is a real, replayable query.
The same issue or PR resolves to the same object across every rebuild — no drift, no duplicates.
# 1. clone — the Cargo workspace root is ekos/, not the repo root $ git clone https://github.com/alexeyban/EKOS.git $ cd EKOS/ekos # 2. build (needs Rust stable via rustup.rs + a C toolchain) $ cargo build --release --workspace $ cargo install --path crates/cli # 3. compile a workspace's knowledge, once $ cd /path/to/your/repo $ ekos init $ ekos build && ekos recover && ekos resolve && ekos compile && ekos commit # 4. serve it over MCP, stdio $ ekos mcp serve --workspace .
Prerequisites: Rust stable (2024 edition needs rustc 1.85+) and a C/C++ toolchain —
rusqlite's bundled SQLite and the zstd
crate both compile native C at build time even though the project itself is pure Rust.
Windows has a native path too (winget + MSVC build tools); WSL2
is the path of least friction if you'd rather follow the Linux steps unmodified.
ekos --help · cargo test --workspace — 500+ tests[connectors.github] in ekos.toml — owner, repo, token env var# register the server — path-based $ claude mcp add ekos -- ekos --config ekos.toml \ mcp serve --workspace /path/to/workspace # or path-free, via env vars $ claude mcp add ekos --env EKOS_WORKSPACE=/path/to/workspace \ -- ekos mcp serve
"What does PR #47 actually close, and which files does it touch?" → ekos_neighborhood(pr_47_id) { "relationships": [ { "kind": "References", "to": "issue_12", "evidence": "body: closes #12" }, { "kind": "References", "to": "file:src/auth.rs", "evidence": "PR file-change entry" } ] }