Skip to content

Latest commit

 

History

History
222 lines (176 loc) · 11 KB

API.md

File metadata and controls

222 lines (176 loc) · 11 KB

EngramGraph API

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

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.

graph-db — Kuzu abstraction

class GraphConnection

  • static open(dbPath: string): GraphConnection — open (or create) a Kuzu database at dbPath.
  • query(cypher: string, params?: Record<string, KuzuValue>): Promise<GraphRow[]>
  • close(): Promise<void> — see the teardown caveat in CONTRIBUTING.md; prefer a long-lived connection.

initSchema(conn): Promise<void>

Idempotently creates the 6 node + 8 relationship tables. Also exported: NODE_TABLE_DDL, REL_TABLE_DDL, NODE_TABLES, REL_TABLES.

clearGraph(conn): Promise<void>

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).

resolveDbPath(loc?) / openGraph(loc?)

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".

writeFragment(conn, fragment: GraphFragment): Promise<void>

Persist a provider-agnostic { nodes, edges } fragment.

Schema (DDL)

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.

code-graph — source → graph

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.

Queries

  • callers(conn, name: string, depth = 1, file?: string): Promise<CallNode[]> — functions that transitively call name (depth clamped 1..10). file is the stored file of one definition (see resolveDefinitionFile); without it every definition of the name answers together.
  • callees(conn, name: string, depth = 1, file?: string): Promise<CallNode[]> — functions name transitively calls.
  • callChain(conn, symbol: string, direction: CallDirection = "both", depth = 1, file?: string): Promise<CallChainResult>. A file that holds no definition of symbol throws DefinitionFileError.

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.

knowledge-graph — spec/decision markdown → graph

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); null if no id can be resolved (from front-matter id, the fallbackId, or the body).
  • classifyRef(id): ClassifiedRef — classify an id as Spec or Decision.
  • impactAnalysis(conn, nodeId: string, maxHops = 3): Promise<ImpactAnalysisResult> — decisions in a spec's impact chain; maxHops (SUPERSEDES depth) clamped 1..10. ImpactAnalysisResult = { nodeId, decisions: ImpactNode[] }, ImpactNode = { id, title, via: "direct" | "supersedes" }.
  • XspecDecKnowledgeSource — the reference KnowledgeSource instance.

Front-matter schema

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.

sage — self-evolving confidence

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; null if the node is absent. ConfidenceUpdate = { nodeId, label, before, after }.
  • feedbackForEventType(type: IngestEventType): { signal, weight } — maps test_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 (limit clamped 1..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.

adapters — pluggable interfaces + defaults

  • Knowledge source — KnowledgeSource; default MarkdownKnowledgeSource (generic front-matter markdown → Doc nodes). Helpers parseFrontMatter, extractRefs; type MarkdownDoc.
  • Isolation model — IsolationModel.dbPath(ctx?: IsolationContext): string. SingleRepoIsolation (default, one graph.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. Types FeedbackEvent, FeedbackSignal.

api — REST (Hono)

  • createServer(options?: { connection?: GraphConnection }): Hono — always mounts GET /health. When connection is given, mounts the graph routes: /graph/impact-analysis, /graph/ingest, /graph/call-chain.

mcp — Model Context Protocol

  • 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).

embedded — in-process client

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.