Library reference for engramgraph. Everything below is exported from
the package root:
import { /* ... */ } from "engramgraph";The package is ESM-first with a CJS build; types are bundled. Runtime: Node ≥ 22.
static open(dbPath: string): GraphConnection— open (or create) a Kuzu database atdbPath.query(cypher: string, params?: Record<string, KuzuValue>): Promise<GraphRow[]>close(): Promise<void>— see the teardown caveat in CONTRIBUTING.md; prefer a long-lived connection.
Idempotently creates the 6 node + 8 relationship tables. Also exported:
NODE_TABLE_DDL, REL_TABLE_DDL, NODE_TABLES, REL_TABLES.
Delete all data while keeping the tables (DETACH DELETE per node table), so a
re-index prunes nodes no longer present (the MERGE writer never deletes).
Resolve a graph DB path / open it (creating dirs + schema). loc is a string
path or GraphLocationOptions = { dbPath?, graph?, isolation?, cwd? }.
Priority: dbPath > env ENGRAM_DB > graph name → .engram/<name>.db >
isolation: "git-branch" (per current branch) > default .engram/graph.db.
IsolationMode = "single" | "git-branch".
Persist a provider-agnostic { nodes, edges } fragment.
NODE Function(id, name, file, start_line, confidence, provider, definition_lines) PK id
NODE Class(id, name, file, provider) PK id
NODE Module(id, path) PK id
NODE Spec(id, title, status, confidence, origin, path) PK id
NODE Decision(id, title, date, confidence, origin, path) PK id
NODE Doc(id, title, status, confidence, origin) PK id
REL CALLS(Function → Function, call_count, confidence, provider)
REL IMPORTS(Module → Module)
REL DEFINES(Module → Function)
REL IMPLEMENTS(Module → Spec)
REL IMPACTS(Decision → Spec)
REL SUPERSEDES(Decision → Decision)
REL RELATES(Spec → Spec)
REL DECISION_RELATES(Decision → Decision)
REL REFERENCES(Doc → Doc)
Types: GraphRow, GraphNode, GraphEdge, GraphFragment, NodeLabel,
RelLabel, and the per-node FunctionNode / ClassNode / ModuleNode /
SpecNode / DecisionNode / DocNode.
tree-sitter parses .ts / .tsx / .js into Function / Class / Module
nodes and resolves CALLS edges. Function ids are scope-qualified and
stable across re-indexing — file#outer.helper, file#Class.method.
extractCodeGraph(source: string, opts: ExtractOptions): Extraction— parse one file to a fragment (no DB write).ExtractOptions = { filePath, language? }.extractProject(files: ProjectFile[]): ProjectExtraction— parse a repo, resolving CALLS across files.indexFile(conn, source, opts: ExtractOptions): Promise<IndexResult>— extract + write one file.IndexResult = { module, functions, classes, calls }.indexProject(conn, files: ProjectFile[]): Promise<ProjectIndexResult>— index a whole repo (cross-file CALLS).ProjectIndexResult = { files, functions, classes, calls, ambiguous, unresolved }(ambiguous = callee name matched > 1 function; unresolved = matched none — both skipped).
ProjectFile = { path, source, language? }; language is inferred from the
path extension when omitted.
callers(conn, name: string, depth = 1, file?: string): Promise<CallNode[]>— functions that transitively callname(depth clamped1..10).fileis the stored file of one definition (seeresolveDefinitionFile); without it every definition of the name answers together.callees(conn, name: string, depth = 1, file?: string): Promise<CallNode[]>— functionsnametransitively calls.callChain(conn, symbol: string, direction: CallDirection = "both", depth = 1, file?: string): Promise<CallChainResult>. Afilethat holds no definition ofsymbolthrowsDefinitionFileError.
CallNode = { id, name, file }. CallDirection = "callers" | "callees" | "both".
CallChainResult = { symbol, direction, depth, callers, callees, definitions, mergedDefinitions, file? } — definitions is every function of that name,
{ id, file, startLine }; mergedDefinitions is how many the answer combines
(more than 1 means the callers of different functions share one list); file is
set when the answer was narrowed. Definitions that share a node id (two overloads of one class,
a redefinition) are one row in the graph and several entries here: the same id, a different
startLine each, read from Function.definition_lines (a graph indexed before that column
existed reports one definition until it is indexed again). The REST route POST /graph/call-chain returns
the same fields and does not take file.
A reference knowledge adapter: spec documents → Spec, decision / ADR
documents → Decision, relationship front-matter + [[ref]] links →
IMPACTS / SUPERSEDES / DECISION_RELATES. Only the supersedes field makes a
SUPERSEDES edge; any other Decision → Decision reference is DECISION_RELATES.
indexKnowledgeDocs(conn, docs: KnowledgeDoc[]): Promise<KnowledgeIndexResult>—KnowledgeDoc = { content, fallbackId? }; result counts{ specs, decisions, impacts, supersedes, relates, decisionRelates }.parseKnowledgeDoc(doc): ParsedKnowledgeDoc | null— parse one doc (no write);nullif no id can be resolved (from front-matterid, thefallbackId, or the body).classifyRef(id): ClassifiedRef— classify an id asSpecorDecision.impactAnalysis(conn, nodeId: string, maxHops = 3): Promise<ImpactAnalysisResult>— decisions in a spec's impact chain;maxHops(SUPERSEDES depth) clamped1..10.ImpactAnalysisResult = { nodeId, decisions: ImpactNode[] },ImpactNode = { id, title, via: "direct" | "supersedes" }.XspecDecKnowledgeSource— the referenceKnowledgeSourceinstance.
Knowledge ingestion reads a leading --- YAML-ish front-matter block:
| Field | Meaning |
|---|---|
id |
Node id (else fallbackId, else inferred from body) |
title |
Node title |
status |
Node status (default unknown) |
related, impacts, impacted_by, supersedes, implements |
Relationship fields → IMPACTS / SUPERSEDES / DECISION_RELATES edges. Only supersedes (exactly this spelling) builds SUPERSEDES; related, depends_on, impacts, impacted_by, implements between two decisions build DECISION_RELATES |
Inline [[ref]] links in the body are also extracted as references. A body link
between two decisions is DECISION_RELATES, never SUPERSEDES (a mention is not a
replacement). Re-indexing a decision replaces its own outgoing SUPERSEDES /
DECISION_RELATES edges with what its document says now.
Confidence lives in [MIN_CONFIDENCE, MAX_CONFIDENCE] = [0.1, 1.0]. A signal
moves it by weight × STEP (STEP = 0.25), clamped.
applyFeedback(conn, event: FeedbackEvent, label: ConfidenceLabel = "Function"): Promise<ConfidenceUpdate | null>— apply one event;nullif the node is absent.ConfidenceUpdate = { nodeId, label, before, after }.feedbackForEventType(type: IngestEventType): { signal, weight }— mapstest_fail→ negative/1.0,test_pass→ positive/0.4,human_fix→ positive/0.6,status_change→ neutral/0.ingestFeedback(...),runEvolution(...)— batch feedback / evolution loop.topByConfidence(conn, label: ConfidenceLabel, limit = 10): Promise<RankedNode[]>— highest confidence first (limitclamped1..1000).RankedNode = { id, confidence }.rankedImpact(conn, nodeId, maxHops?)— impact decisions ranked by confidence.
ConfidenceLabel = "Function" | "Spec" | "Decision" | "Doc".
FeedbackEvent = { nodeId, signal: "positive"|"negative"|"neutral", weight, source? }.
Constants STEP, MIN_CONFIDENCE, MAX_CONFIDENCE are exported.
- Knowledge source —
KnowledgeSource; defaultMarkdownKnowledgeSource(generic front-matter markdown →Docnodes). HelpersparseFrontMatter,extractRefs; typeMarkdownDoc. - Isolation model —
IsolationModel.dbPath(ctx?: IsolationContext): string.SingleRepoIsolation(default, onegraph.db) |OrgProjectIsolation(org-{orgId}/project-{projectId}/graph.db) |GitBranchIsolation( per-branch<git-common-dir>/engram/<branch>.db, with a fallback model). - Signal source —
SignalSource → FeedbackEvent[];GitHistorySignalSource,TestExitCodeSignalSource. TypesFeedbackEvent,FeedbackSignal.
createServer(options?: { connection?: GraphConnection }): Hono— always mountsGET /health. Whenconnectionis given, mounts the graph routes:/graph/impact-analysis,/graph/ingest,/graph/call-chain.
createMcpServer(conn: GraphConnection | GraphSource): McpServer— registers the tools of MCP.md: 14 on a writable connection (index_code,index_docs,call_chain,impact_analysis,ingest_feedback,implementers,implemented_specs,related,blindspots,signatures,doctor,refs_check,cochange,lint), and 10 on a read-only source such as the stdio server's (without the four that write:index_code,index_docs,ingest_feedback,related).
class EmbeddedClient {
constructor(isolation?: IsolationModel, ctx?: IsolationContext);
init(): Promise<void>; // open DB + ensure schema (idempotent)
query(cypher, params?): Promise<GraphRow[]>;
// high-level facade — same ops as REST/MCP, no raw GraphConnection needed:
indexCode(files: ProjectFile[]): Promise<ProjectIndexResult>;
indexDocs(docs: KnowledgeDoc[]): Promise<KnowledgeIndexResult>;
callChain(symbol, direction?, depth?): Promise<CallChainResult>;
callers(name, depth?): Promise<CallNode[]>;
callees(name, depth?): Promise<CallNode[]>;
impactAnalysis(nodeId, maxHops?): Promise<ImpactAnalysisResult>;
ingestFeedback(nodeId, type, nodeLabel?, weight?): Promise<ConfidenceUpdate | null>;
topByConfidence(label, limit?): Promise<RankedNode[]>;
close(): Promise<void>; // shutdown only (teardown caveat)
}Defaults to SingleRepoIsolation. Zero HTTP overhead — wraps GraphConnection
directly for same-process consumers. Beyond raw query, the high-level facade
exposes the same operations as the REST/MCP surfaces, so embedded consumers
(e.g. an in-process host application) never need to hold the raw GraphConnection. The
connection is long-lived — init() is idempotent; close() is shutdown-only.