Skip to content

Latest commit

 

History

History
297 lines (231 loc) · 12.3 KB

File metadata and controls

297 lines (231 loc) · 12.3 KB

EngramGraph CLI

Language: English · 繁體中文 · 简体中文

The egr CLI indexes a repository into the graph and queries it from the shell or CI. It is a thin layer over the same tested functions the library and MCP server use — zero LLM, deterministic.

egr <command> [args] [options]

Graph DB location

Every command reads/writes one Kuzu database. Its path is resolved in this priority order:

  1. env ENGRAM_DB (a full path; highest), else
  2. --graph <name>./.engram/<name>.db, else
  3. --isolation git-branch (or env ENGRAM_ISOLATION=git-branch) → a per-branch DB <git-common-dir>/engram/<branch>.db, else
  4. the default single ./.engram/graph.db.

The directory is created on demand and the schema is ensured on every open (idempotent), so the first index works against an empty repo. See Branch / project isolation below.

Global options

Option Description
--json Emit raw JSON instead of the human-readable summary
--graph <name> Use ./.engram/<name>.db — an explicitly named project graph
--isolation <mode> single (default) or git-branch (one graph per branch)
-h, --help Show usage
-v, --version Show the package version

Commands

index <dir> [--docs] [--clean] [--scip <path>]

Recursively indexes source files under <dir> into the code graph (tree-sitter → Function / Class / Module nodes + cross-file CALLS). With --docs, also indexes *.md files into the knowledge graph (front-matter → Spec / Decision + IMPACTS / SUPERSEDES).

  • Code extensions: .ts .tsx .js .jsx .mts .cts .mjs .cjs .cs .py .go .java .kt .kts .rs .cpp .cc .cxx .hpp .h .hh .rb .php .dart (.d.ts excluded).
  • Skipped directories: node_modules, dist, .engram, .git, coverage, bin, obj, __pycache__, .venv, venv, vendor, target, build.
  • --clean: drop the graph's data before indexing. Indexing is otherwise an upsert (MERGE) that never deletes, so a node removed from the code lingers; --clean rebuilds from scratch to prune it.
egr index ./src
egr index . --docs
egr index ./src --clean   # rebuild, pruning deleted nodes

Output counts: files, functions, classes, calls, plus ambiguous (callee name matched > 1 function — skipped) and unresolved (callee matched none — skipped); with --docs, specs / decisions / impacts / supersedes.

--scip <path> — overlay a SCIP index for higher-precision CALLS

tree-sitter's own name-based CALLS resolution is deliberately conservative: when a callee name matches more than one function across the repo it skips the call rather than guessing (ambiguous in the output above). A SCIP index — produced by a real compiler/type-checker-backed indexer for the language in question — carries unambiguous symbol references, so --scip overlays it on top of the tree-sitter pass to resolve calls tree-sitter alone can't, and to upgrade the confidence of ones it already resolved.

# 1. Produce the .scip file yourself, with an indexer for your language.
#    egr does NOT invoke dotnet/java/maven or any other build toolchain —
#    that step is entirely your own build environment's responsibility.
dotnet tool install --global scip-dotnet   # once
scip-dotnet index MyProject.csproj --output index.scip

# 2. Point egr at it. --scip always runs a full tree-sitter pass first, then
#    overlays the SCIP data — a single command is a complete, from-scratch
#    index; you do not need to have run a plain `egr index` before this.
egr index . --scip index.scip

Requirements and failure modes:

  • <dir> must be the SAME project root the external indexer was run against. A SCIP index's occurrence paths are relative to that root; if they don't match <dir>'s own file paths, egr fails with a "none of the N document path(s) ... matched any source file under <dir>" error rather than silently ingesting nothing. SCIP paths are always /-separated by spec; egr's own paths are normalized to / as well regardless of host OS, so this comparison is designed to line up on Windows too, not just POSIX — verified with string-level unit tests against simulated Windows-style paths (no real Windows host to test against in this project's own CI). If --scip reports 0 definitions/calls resolved despite matching files, a warning is printed; one possible cause is a stale .scip file — generated before the source tree was subsequently edited, so it no longer matches on content even though the paths agree.
  • A missing or non-SCIP file at <path> fails with a clear "file not found" or "could not be parsed as a SCIP protobuf index" error.
  • A graph DB whose CALLS (or Function/Class) table predates a schema change like this one's provider/confidence columns is migrated automatically and non-destructively the moment any egr command opens it: missing columns are added in place via ALTER TABLE ... ADD (existing rows keep every other property they already have), after first checkpointing and backing up the DB file to a .pre-migration-backup sibling (never overwriting a prior backup). No --clean, no deleting the DB file required to stop hitting the schema error — --clean still only deletes row data via DETACH DELETE, never table schema, but that's no longer the mechanism that closes this gap. When a migration actually runs, egr prints a one-line notice on stderr naming the column(s) added and the backup path. Function.provider/Class.provider/CALLS.provider are backfilled to "tree-sitter" on migrated rows (a known historical fact, not a guess — tree-sitter was the only extraction provider before these columns existed), so a plain egr index re-index right afterwards fully un-freezes them; a CALLS edge's confidence is deliberately left NULL (we don't know which resolution tier a historical edge was), so a SCIP overlay's ability to upgrade that specific edge still needs one such plain re-index first. This does NOT change the separate, pre-existing fact that a plain re-index resets Function.confidence (SAGE's feedback-adjusted score) back to its 1.0 default regardless of migration — this feature only prevents that value from being destroyed by the migration itself, not by ordinary re-indexing after it. See src/graph-db/schema-migration.ts for the full mechanism and its documented limits.
  • Currently verified against scip-dotnet (C#) and scip-java (Java) output; any SCIP-conformant indexer for a tree-sitter-supported language should work the same way in principle (the merge logic is language-generic), but this has not actually been tried against a third indexer.

Output adds a scip block: documentsInIndex (documents in the .scip file), filesMatched (how many of those overlapped <dir>'s own files — less than documentsInIndex is normal, e.g. compiler-generated files an indexer sees but egr deliberately skips), definitionsResolved / definitionsUnresolved, callsEmitted, and the two skip counters callsSkippedNoEnclosingCaller / callsSkippedUnresolvedTarget. If files matched but resolution came back at zero, the human-readable output adds a WARNING line rather than silently reporting an all-zero result as success.

callers <symbol> [--depth N]

Functions that (transitively, up to --depth, default 1) call <symbol>. "What breaks if I change this?"

egr callers callChain --depth 2

callees <symbol> [--depth N]

Functions that <symbol> (transitively, up to --depth, default 1) calls.

egr callees createMcpServer

--depth is clamped to 1..10. A symbol is matched by name; if a name is reused across files, all matches are considered.

impact <spec-id> [--max-hops N]

Decisions in the impact chain of a spec — which Decision nodes affect this Spec, via the direct IMPACTS edge plus a multi-hop SUPERSEDES chain (--max-hops, default 3, clamped to 1..10).

egr impact SPEC-001
egr impact SPEC-001 --max-hops 5 --json

Each result row shows the decision id, how it was reached (direct | supersedes), and its title.

feedback <type> <node-id> [--label L]

Evolve a node's SAGE confidence from one feedback event.

  • <type>: test_fail (negative, weight 1.0), test_pass (positive, 0.4), human_fix (positive, 0.6), status_change (neutral).
  • --label: Function (default) | Spec | Decision | Doc.
  • The node is matched by id (for Decision / Spec the id is e.g. ADR-1 / SPEC-1; for Function it is the scope-qualified id such as src/a.ts#a).
egr feedback test_fail "src/api/server.ts#createServer"
egr feedback human_fix ADR-002 --label Decision

Prints before → after, or "node not found" if the id/label miss.

top <label> [--limit N]

Highest-confidence nodes of a label, confidence-descending.

  • <label>: Function | Spec | Decision | Doc.
  • --limit: default 10, clamped to 1..1000.
egr top Function --limit 20
egr top Decision --json

gc [--dry-run]

Garbage-collect per-branch graphs whose branch no longer exists. Inspects <git-common-dir>/engram/; a <name>.db is an orphan when no current local branch maps to <name>. --dry-run lists without deleting. No-op outside a git repo.

egr gc --dry-run
egr gc

doctor

What this installation can actually do on this machine — the first thing to run when a language is missing from your graph.

egr doctor
egr doctor --json

Reports the egr/Node version and platform, the graph DB path, every language with whether its native module loaded (and the reason if it did not), which native dependencies this platform had to compile from source, which commands need network access, and the MCP registration command.

It does not open the graph, so it still answers when the graph is missing or unreadable — which is when people run it.

Why this is a command and not an install-time message. It was one, and it reached nobody: npm suppresses lifecycle-script output by default and npm ≥ 11 gates those scripts behind an approval prompt, so the notice appeared zero times on a real npm install -g. A command you type cannot be suppressed by a package manager.

serve [--port 3000]

Run the REST server (Hono) over the graph DB. Routes are mounted under /graph/* plus GET /health. Long-running — manages its own lifecycle. See API.md for the route surface.

egr serve --port 3000

mcp

Run the MCP server over stdio for coding assistants. Identical to the egr-mcp bin. Long-running. See MCP.md for assistant setup.

egr mcp

Branch / project isolation

By default all commands share one ./.engram/graph.db. Because .engram/ is gitignored and lives in the work tree, git checkout does not swap it — different branches share the same graph. Three ways to isolate:

  1. --isolation git-branch (or set ENGRAM_ISOLATION=git-branch once in your shell): each branch gets its own <git-common-dir>/engram/<branch>.db, which survives checkouts and never pollutes the work tree. Branch names are sanitized with a hash suffix so feature/x and feature-x never collide. Use egr gc to reclaim graphs of deleted branches.
  2. --graph <name>: an explicit, git-independent project graph — handy for a detached HEAD or when branch names are ad-hoc.
  3. git worktree: each branch checked out in its own directory naturally gets its own ./.engram/graph.db — zero flags, the cleanest isolation when branches map to long-lived separate projects.

MCP caveat: the MCP server binds to one graph at startup (it logs the path to stderr). It does not follow a later git checkout — reconnect/restart the server (or launch it with --graph / ENGRAM_ISOLATION) to switch.

CI example

export ENGRAM_DB="$PWD/.engram/graph.db"
egr index ./src --docs
# Fail the job if a high-risk symbol gained new callers, query with --json, etc.
egr callers paymentGateway --depth 3 --json > callers.json

Exit codes

0 on success; 1 on error (the message is written to stderr as egr: <message>).