engramx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,247 @@
1
+ // src/hooks.ts
2
+ import { existsSync, readFileSync, writeFileSync, chmodSync, unlinkSync } from "fs";
3
+ import { join } from "path";
4
+ var HOOK_START = "# engram-hook-start";
5
+ var HOOK_END = "# engram-hook-end";
6
+ var POST_COMMIT_SCRIPT = `
7
+ ${HOOK_START}
8
+ # Auto-rebuild engram graph after commit (AST only, no LLM needed)
9
+ ENGRAM_BIN=$(command -v engram 2>/dev/null)
10
+ if [ -z "$ENGRAM_BIN" ]; then
11
+ ENGRAM_BIN=$(npm root -g 2>/dev/null)/engram/dist/cli.js
12
+ fi
13
+
14
+ if [ -d ".engram" ] && [ -f "$ENGRAM_BIN" ]; then
15
+ node "$ENGRAM_BIN" init . --quiet 2>/dev/null &
16
+ fi
17
+ ${HOOK_END}
18
+ `;
19
+ var POST_CHECKOUT_SCRIPT = `
20
+ ${HOOK_START}
21
+ # Auto-rebuild engram graph on branch switch
22
+ PREV_HEAD=$1
23
+ NEW_HEAD=$2
24
+ BRANCH_SWITCH=$3
25
+
26
+ if [ "$BRANCH_SWITCH" != "1" ]; then
27
+ exit 0
28
+ fi
29
+
30
+ ENGRAM_BIN=$(command -v engram 2>/dev/null)
31
+ if [ -z "$ENGRAM_BIN" ]; then
32
+ ENGRAM_BIN=$(npm root -g 2>/dev/null)/engram/dist/cli.js
33
+ fi
34
+
35
+ if [ -d ".engram" ] && [ -f "$ENGRAM_BIN" ]; then
36
+ echo "[engram] Branch switched \u2014 rebuilding graph..."
37
+ node "$ENGRAM_BIN" init . --quiet 2>/dev/null &
38
+ fi
39
+ ${HOOK_END}
40
+ `;
41
+ function findGitRoot(from) {
42
+ let current = from;
43
+ while (current !== "/") {
44
+ if (existsSync(join(current, ".git"))) return current;
45
+ current = join(current, "..");
46
+ }
47
+ return null;
48
+ }
49
+ function installHook(hooksDir, name, script) {
50
+ const hookPath = join(hooksDir, name);
51
+ if (existsSync(hookPath)) {
52
+ const content = readFileSync(hookPath, "utf-8");
53
+ if (content.includes(HOOK_START)) {
54
+ return `${name}: already installed`;
55
+ }
56
+ writeFileSync(hookPath, content.trimEnd() + "\n\n" + script);
57
+ return `${name}: appended to existing hook`;
58
+ }
59
+ writeFileSync(hookPath, "#!/bin/bash\n" + script);
60
+ chmodSync(hookPath, 493);
61
+ return `${name}: installed`;
62
+ }
63
+ function uninstallHook(hooksDir, name) {
64
+ const hookPath = join(hooksDir, name);
65
+ if (!existsSync(hookPath)) return `${name}: not installed`;
66
+ const content = readFileSync(hookPath, "utf-8");
67
+ if (!content.includes(HOOK_START)) return `${name}: engram hook not found`;
68
+ const cleaned = content.replace(new RegExp(`\\n?${HOOK_START}[\\s\\S]*?${HOOK_END}\\n?`, "g"), "").trim();
69
+ if (!cleaned || cleaned === "#!/bin/bash") {
70
+ unlinkSync(hookPath);
71
+ return `${name}: removed`;
72
+ }
73
+ writeFileSync(hookPath, cleaned + "\n");
74
+ return `${name}: engram section removed (other hooks preserved)`;
75
+ }
76
+ function install(projectRoot) {
77
+ const gitRoot = findGitRoot(projectRoot);
78
+ if (!gitRoot) return "Error: not a git repository";
79
+ const hooksDir = join(gitRoot, ".git", "hooks");
80
+ const results = [
81
+ installHook(hooksDir, "post-commit", POST_COMMIT_SCRIPT),
82
+ installHook(hooksDir, "post-checkout", POST_CHECKOUT_SCRIPT)
83
+ ];
84
+ return results.join("\n");
85
+ }
86
+ function uninstall(projectRoot) {
87
+ const gitRoot = findGitRoot(projectRoot);
88
+ if (!gitRoot) return "Error: not a git repository";
89
+ const hooksDir = join(gitRoot, ".git", "hooks");
90
+ const results = [
91
+ uninstallHook(hooksDir, "post-commit"),
92
+ uninstallHook(hooksDir, "post-checkout")
93
+ ];
94
+ return results.join("\n");
95
+ }
96
+ function status(projectRoot) {
97
+ const gitRoot = findGitRoot(projectRoot);
98
+ if (!gitRoot) return "Not a git repository";
99
+ const hooksDir = join(gitRoot, ".git", "hooks");
100
+ const check = (name) => {
101
+ const p = join(hooksDir, name);
102
+ if (!existsSync(p)) return "not installed";
103
+ return readFileSync(p, "utf-8").includes(HOOK_START) ? "installed" : "not installed";
104
+ };
105
+ return `post-commit: ${check("post-commit")}
106
+ post-checkout: ${check("post-checkout")}`;
107
+ }
108
+
109
+ // src/autogen.ts
110
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
111
+ import { join as join2 } from "path";
112
+ var AUTOGEN_START = "<!-- engram:start -->";
113
+ var AUTOGEN_END = "<!-- engram:end -->";
114
+ function generateSummary(store) {
115
+ const stats = store.getStats();
116
+ const gods = store.getGodNodes(8);
117
+ const allNodes = store.getAllNodes();
118
+ const allEdges = store.getAllEdges();
119
+ const filesByDir = /* @__PURE__ */ new Map();
120
+ for (const node of allNodes) {
121
+ if (node.kind === "file" && node.sourceFile) {
122
+ const dir = node.sourceFile.split("/").slice(0, -1).join("/") || ".";
123
+ if (!filesByDir.has(dir)) filesByDir.set(dir, []);
124
+ filesByDir.get(dir).push(node.label);
125
+ }
126
+ }
127
+ const kindCounts = /* @__PURE__ */ new Map();
128
+ for (const node of allNodes) {
129
+ kindCounts.set(node.kind, (kindCounts.get(node.kind) ?? 0) + 1);
130
+ }
131
+ const importEdges = allEdges.filter((e) => e.relation === "imports");
132
+ const mostImported = /* @__PURE__ */ new Map();
133
+ for (const edge of importEdges) {
134
+ mostImported.set(edge.target, (mostImported.get(edge.target) ?? 0) + 1);
135
+ }
136
+ const topImported = [...mostImported.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
137
+ const lines = [
138
+ AUTOGEN_START,
139
+ "## Codebase Structure (auto-generated by engram)",
140
+ "",
141
+ `**Graph:** ${stats.nodes} nodes, ${stats.edges} edges | ${stats.extractedPct}% extracted, ${stats.inferredPct}% inferred`,
142
+ ""
143
+ ];
144
+ if (gods.length > 0) {
145
+ lines.push("**Core entities:**");
146
+ for (const g of gods) {
147
+ lines.push(`- \`${g.node.label}\` (${g.node.kind}, ${g.degree} connections) \u2014 ${g.node.sourceFile}`);
148
+ }
149
+ lines.push("");
150
+ }
151
+ if (filesByDir.size > 0) {
152
+ lines.push("**Structure:**");
153
+ for (const [dir, files] of [...filesByDir.entries()].sort()) {
154
+ lines.push(`- \`${dir}/\` \u2014 ${files.join(", ")}`);
155
+ }
156
+ lines.push("");
157
+ }
158
+ if (topImported.length > 0) {
159
+ lines.push("**Key dependencies (most imported):**");
160
+ for (const [target, count] of topImported) {
161
+ const node = store.getNode(target);
162
+ lines.push(`- \`${node?.label ?? target}\` (imported by ${count} files)`);
163
+ }
164
+ lines.push("");
165
+ }
166
+ const decisions = allNodes.filter((n) => n.kind === "decision");
167
+ const patterns = allNodes.filter((n) => n.kind === "pattern");
168
+ const mistakes = allNodes.filter((n) => n.kind === "mistake");
169
+ if (decisions.length > 0) {
170
+ lines.push("**Decisions:**");
171
+ for (const d of decisions.slice(0, 5)) {
172
+ lines.push(`- ${d.label}`);
173
+ }
174
+ lines.push("");
175
+ }
176
+ if (patterns.length > 0) {
177
+ lines.push("**Patterns:**");
178
+ for (const p of patterns.slice(0, 5)) {
179
+ lines.push(`- ${p.label}`);
180
+ }
181
+ lines.push("");
182
+ }
183
+ if (mistakes.length > 0) {
184
+ lines.push("**Known issues:**");
185
+ for (const m of mistakes.slice(0, 3)) {
186
+ lines.push(`- ${m.label}`);
187
+ }
188
+ lines.push("");
189
+ }
190
+ lines.push(
191
+ '**Tip:** Run `engram query "your question"` for structural context instead of reading files.',
192
+ AUTOGEN_END
193
+ );
194
+ return lines.join("\n");
195
+ }
196
+ function writeToFile(filePath, summary) {
197
+ let content = "";
198
+ if (existsSync2(filePath)) {
199
+ content = readFileSync2(filePath, "utf-8");
200
+ }
201
+ if (content.includes(AUTOGEN_START)) {
202
+ content = content.replace(
203
+ new RegExp(`${AUTOGEN_START}[\\s\\S]*?${AUTOGEN_END}`, "g"),
204
+ summary
205
+ );
206
+ } else {
207
+ content = content.trimEnd() + "\n\n" + summary + "\n";
208
+ }
209
+ writeFileSync2(filePath, content);
210
+ }
211
+ async function autogen(projectRoot, target) {
212
+ const { getStore } = await import("./core-M5N34VUU.js");
213
+ const store = await getStore(projectRoot);
214
+ try {
215
+ const summary = generateSummary(store);
216
+ const stats = store.getStats();
217
+ let targetFile;
218
+ if (target === "claude") {
219
+ targetFile = join2(projectRoot, "CLAUDE.md");
220
+ } else if (target === "cursor") {
221
+ targetFile = join2(projectRoot, ".cursorrules");
222
+ } else if (target === "agents") {
223
+ targetFile = join2(projectRoot, "AGENTS.md");
224
+ } else {
225
+ if (existsSync2(join2(projectRoot, "CLAUDE.md"))) {
226
+ targetFile = join2(projectRoot, "CLAUDE.md");
227
+ } else if (existsSync2(join2(projectRoot, ".cursorrules"))) {
228
+ targetFile = join2(projectRoot, ".cursorrules");
229
+ } else if (existsSync2(join2(projectRoot, "AGENTS.md"))) {
230
+ targetFile = join2(projectRoot, "AGENTS.md");
231
+ } else {
232
+ targetFile = join2(projectRoot, "CLAUDE.md");
233
+ }
234
+ }
235
+ writeToFile(targetFile, summary);
236
+ return { file: targetFile, nodesIncluded: stats.nodes };
237
+ } finally {
238
+ store.close();
239
+ }
240
+ }
241
+
242
+ export {
243
+ install,
244
+ uninstall,
245
+ status,
246
+ autogen
247
+ };
package/dist/cli.js ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ autogen,
4
+ install,
5
+ status,
6
+ uninstall
7
+ } from "./chunk-WJUA4VZ7.js";
8
+ import {
9
+ benchmark,
10
+ godNodes,
11
+ init,
12
+ learn,
13
+ path,
14
+ query,
15
+ stats
16
+ } from "./chunk-44GN6IRQ.js";
17
+
18
+ // src/cli.ts
19
+ import { Command } from "commander";
20
+ import chalk from "chalk";
21
+ var program = new Command();
22
+ program.name("engram").description("AI coding memory that learns from every session").version("0.1.0");
23
+ program.command("init").description("Scan codebase and build knowledge graph (zero LLM cost)").argument("[path]", "Project directory", ".").action(async (projectPath) => {
24
+ console.log(chalk.dim("\u{1F50D} Scanning codebase..."));
25
+ const result = await init(projectPath);
26
+ console.log(
27
+ chalk.green("\u{1F333} AST extraction complete") + chalk.dim(` (${result.timeMs}ms, 0 tokens used)`)
28
+ );
29
+ console.log(
30
+ ` ${chalk.bold(String(result.nodes))} nodes, ${chalk.bold(String(result.edges))} edges from ${chalk.bold(String(result.fileCount))} files (${result.totalLines.toLocaleString()} lines)`
31
+ );
32
+ const bench = await benchmark(projectPath);
33
+ if (bench.naiveFullCorpus > 0 && bench.reductionVsRelevant > 1) {
34
+ console.log(
35
+ chalk.cyan(`
36
+ \u{1F4CA} Token savings: ${chalk.bold(bench.reductionVsRelevant + "x")} fewer tokens vs relevant files (${bench.reductionVsFull}x vs full corpus)`)
37
+ );
38
+ console.log(
39
+ chalk.dim(` Full corpus: ~${bench.naiveFullCorpus.toLocaleString()} tokens | Graph query: ~${bench.avgQueryTokens.toLocaleString()} tokens`)
40
+ );
41
+ }
42
+ console.log(chalk.green("\n\u2705 Ready. Your AI now has persistent memory."));
43
+ console.log(chalk.dim(" Graph stored in .engram/graph.db"));
44
+ });
45
+ program.command("query").description("Query the knowledge graph").argument("<question>", "Natural language question or keywords").option("--dfs", "Use DFS traversal", false).option("-d, --depth <n>", "Traversal depth", "3").option("-b, --budget <n>", "Token budget", "2000").option("-p, --project <path>", "Project directory", ".").action(async (question, opts) => {
46
+ const result = await query(opts.project, question, {
47
+ mode: opts.dfs ? "dfs" : "bfs",
48
+ depth: Number(opts.depth),
49
+ tokenBudget: Number(opts.budget)
50
+ });
51
+ if (result.nodesFound === 0) {
52
+ console.log(chalk.yellow("No matching nodes found."));
53
+ return;
54
+ }
55
+ console.log(chalk.dim(`Found ${result.nodesFound} nodes (~${result.estimatedTokens} tokens)
56
+ `));
57
+ console.log(result.text);
58
+ });
59
+ program.command("path").description("Find shortest path between two concepts").argument("<source>", "Source concept").argument("<target>", "Target concept").option("-p, --project <path>", "Project directory", ".").action(async (source, target, opts) => {
60
+ const result = await path(opts.project, source, target);
61
+ console.log(result.text);
62
+ });
63
+ program.command("gods").description("Show most connected entities (god nodes)").option("-n, --top <n>", "Number of nodes", "10").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
64
+ const gods = await godNodes(opts.project, Number(opts.top));
65
+ if (gods.length === 0) {
66
+ console.log(chalk.yellow("No nodes found. Run `engram init` first."));
67
+ return;
68
+ }
69
+ console.log(chalk.bold("God nodes (most connected):\n"));
70
+ for (let i = 0; i < gods.length; i++) {
71
+ const g = gods[i];
72
+ console.log(
73
+ ` ${chalk.dim(String(i + 1) + ".")} ${chalk.bold(g.label)} ${chalk.dim(`[${g.kind}]`)} \u2014 ${g.degree} edges ${chalk.dim(g.sourceFile)}`
74
+ );
75
+ }
76
+ });
77
+ program.command("stats").description("Show knowledge graph statistics and token savings").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
78
+ const s = await stats(opts.project);
79
+ const bench = await benchmark(opts.project);
80
+ console.log(chalk.bold("\n\u{1F4CA} engram stats\n"));
81
+ console.log(` Nodes: ${chalk.bold(String(s.nodes))}`);
82
+ console.log(` Edges: ${chalk.bold(String(s.edges))}`);
83
+ console.log(
84
+ ` Confidence: ${chalk.green(s.extractedPct + "% EXTRACTED")} \xB7 ${chalk.yellow(s.inferredPct + "% INFERRED")} \xB7 ${chalk.red(s.ambiguousPct + "% AMBIGUOUS")}`
85
+ );
86
+ if (s.lastMined > 0) {
87
+ const ago = Math.round((Date.now() - s.lastMined) / 6e4);
88
+ console.log(` Last mined: ${ago < 60 ? ago + "m ago" : Math.round(ago / 60) + "h ago"}`);
89
+ }
90
+ if (bench.naiveFullCorpus > 0) {
91
+ console.log(`
92
+ ${chalk.cyan("Token savings:")}`);
93
+ console.log(` Full corpus: ~${bench.naiveFullCorpus.toLocaleString()} tokens`);
94
+ console.log(` Avg query: ~${bench.avgQueryTokens.toLocaleString()} tokens`);
95
+ console.log(` vs relevant: ${chalk.bold.cyan(bench.reductionVsRelevant + "x")} fewer tokens`);
96
+ console.log(` vs full: ${chalk.bold.cyan(bench.reductionVsFull + "x")} fewer tokens`);
97
+ }
98
+ console.log();
99
+ });
100
+ program.command("learn").description("Teach engram a decision, pattern, or lesson").argument("<text>", "What to remember (e.g., 'We chose JWT over sessions for horizontal scaling')").option("-p, --project <path>", "Project directory", ".").action(async (text, opts) => {
101
+ const result = await learn(opts.project, text);
102
+ if (result.nodesAdded > 0) {
103
+ console.log(chalk.green(`\u{1F9E0} Learned ${result.nodesAdded} new insight(s).`));
104
+ } else {
105
+ console.log(chalk.yellow("No patterns extracted. Try a more specific statement."));
106
+ }
107
+ });
108
+ program.command("bench").description("Run token reduction benchmark").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
109
+ const result = await benchmark(opts.project);
110
+ console.log(chalk.bold("\n\u26A1 engram token reduction benchmark\n"));
111
+ console.log(` Full corpus: ~${result.naiveFullCorpus.toLocaleString()} tokens`);
112
+ console.log(` Avg graph query: ~${result.avgQueryTokens.toLocaleString()} tokens`);
113
+ console.log(` vs relevant: ${chalk.bold.green(result.reductionVsRelevant + "x")} fewer tokens`);
114
+ console.log(` vs full corpus: ${chalk.bold.green(result.reductionVsFull + "x")} fewer tokens
115
+ `);
116
+ for (const pq of result.perQuestion) {
117
+ console.log(` ${chalk.dim(`[${pq.reductionRelevant}x relevant / ${pq.reductionFull}x full]`)} ${pq.question}`);
118
+ }
119
+ console.log();
120
+ });
121
+ var hooks = program.command("hooks").description("Manage git hooks");
122
+ hooks.command("install").description("Install post-commit and post-checkout hooks").argument("[path]", "Project directory", ".").action((p) => console.log(install(p)));
123
+ hooks.command("uninstall").description("Remove engram git hooks").argument("[path]", "Project directory", ".").action((p) => console.log(uninstall(p)));
124
+ hooks.command("status").description("Check if hooks are installed").argument("[path]", "Project directory", ".").action((p) => console.log(status(p)));
125
+ program.command("gen").description("Generate CLAUDE.md / .cursorrules section from graph").option("-p, --project <path>", "Project directory", ".").option("-t, --target <type>", "Target file: claude, cursor, agents").action(async (opts) => {
126
+ const result = await autogen(opts.project, opts.target);
127
+ console.log(chalk.green(`\u2705 Updated ${result.file} (${result.nodesIncluded} nodes)`));
128
+ });
129
+ program.parse();
@@ -0,0 +1,22 @@
1
+ import {
2
+ benchmark,
3
+ getDbPath,
4
+ getStore,
5
+ godNodes,
6
+ init,
7
+ learn,
8
+ path,
9
+ query,
10
+ stats
11
+ } from "./chunk-44GN6IRQ.js";
12
+ export {
13
+ benchmark,
14
+ getDbPath,
15
+ getStore,
16
+ godNodes,
17
+ init,
18
+ learn,
19
+ path,
20
+ query,
21
+ stats
22
+ };
package/dist/index.js ADDED
@@ -0,0 +1,38 @@
1
+ import {
2
+ autogen,
3
+ install,
4
+ uninstall
5
+ } from "./chunk-WJUA4VZ7.js";
6
+ import {
7
+ GraphStore,
8
+ SUPPORTED_EXTENSIONS,
9
+ benchmark,
10
+ extractDirectory,
11
+ extractFile,
12
+ godNodes,
13
+ init,
14
+ learn,
15
+ path,
16
+ query,
17
+ queryGraph,
18
+ shortestPath,
19
+ stats
20
+ } from "./chunk-44GN6IRQ.js";
21
+ export {
22
+ GraphStore,
23
+ SUPPORTED_EXTENSIONS,
24
+ autogen,
25
+ benchmark,
26
+ extractDirectory,
27
+ extractFile,
28
+ godNodes,
29
+ init,
30
+ install as installHooks,
31
+ learn,
32
+ path,
33
+ query,
34
+ queryGraph,
35
+ shortestPath,
36
+ stats,
37
+ uninstall as uninstallHooks
38
+ };
package/dist/serve.js ADDED
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ benchmark,
4
+ godNodes,
5
+ path,
6
+ query,
7
+ stats
8
+ } from "./chunk-44GN6IRQ.js";
9
+
10
+ // src/serve.ts
11
+ var PROJECT_ROOT = process.argv[2] || process.cwd();
12
+ var TOOLS = [
13
+ {
14
+ name: "query_graph",
15
+ description: "Search the knowledge graph using natural language. Returns relevant code structure as compact context.",
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {
19
+ question: { type: "string", description: "Natural language question or keywords" },
20
+ mode: { type: "string", enum: ["bfs", "dfs"], default: "bfs" },
21
+ depth: { type: "integer", default: 3, description: "Traversal depth (1-6)" },
22
+ token_budget: { type: "integer", default: 2e3, description: "Max output tokens" }
23
+ },
24
+ required: ["question"]
25
+ }
26
+ },
27
+ {
28
+ name: "god_nodes",
29
+ description: "Return the most connected entities \u2014 the core abstractions of the codebase.",
30
+ inputSchema: { type: "object", properties: { top_n: { type: "integer", default: 10 } } }
31
+ },
32
+ {
33
+ name: "graph_stats",
34
+ description: "Return summary: node/edge counts, confidence breakdown, token savings.",
35
+ inputSchema: { type: "object", properties: {} }
36
+ },
37
+ {
38
+ name: "shortest_path",
39
+ description: "Find the shortest path between two concepts in the graph.",
40
+ inputSchema: {
41
+ type: "object",
42
+ properties: {
43
+ source: { type: "string", description: "Source concept" },
44
+ target: { type: "string", description: "Target concept" }
45
+ },
46
+ required: ["source", "target"]
47
+ }
48
+ },
49
+ {
50
+ name: "benchmark",
51
+ description: "Compare token cost of graph queries vs reading raw files.",
52
+ inputSchema: { type: "object", properties: {} }
53
+ }
54
+ ];
55
+ async function handleToolCall(name, args) {
56
+ switch (name) {
57
+ case "query_graph": {
58
+ const result = await query(PROJECT_ROOT, args.question, {
59
+ mode: args.mode ?? "bfs",
60
+ depth: args.depth ?? 3,
61
+ tokenBudget: args.token_budget ?? 2e3
62
+ });
63
+ return `${result.nodesFound} nodes found (~${result.estimatedTokens} tokens)
64
+
65
+ ${result.text}`;
66
+ }
67
+ case "god_nodes": {
68
+ const gods = await godNodes(PROJECT_ROOT, args.top_n ?? 10);
69
+ return gods.map((g, i) => `${i + 1}. ${g.label} [${g.kind}] \u2014 ${g.degree} edges (${g.sourceFile})`).join("\n");
70
+ }
71
+ case "graph_stats": {
72
+ const s = await stats(PROJECT_ROOT);
73
+ return `Nodes: ${s.nodes}
74
+ Edges: ${s.edges}
75
+ EXTRACTED: ${s.extractedPct}%
76
+ INFERRED: ${s.inferredPct}%
77
+ AMBIGUOUS: ${s.ambiguousPct}%`;
78
+ }
79
+ case "shortest_path": {
80
+ const result = await path(PROJECT_ROOT, args.source, args.target);
81
+ return result.text;
82
+ }
83
+ case "benchmark": {
84
+ const b = await benchmark(PROJECT_ROOT);
85
+ return [
86
+ `Full corpus: ~${b.naiveFullCorpus.toLocaleString()} tokens`,
87
+ `Avg graph query: ~${b.avgQueryTokens.toLocaleString()} tokens`,
88
+ `Reduction vs full corpus: ${b.reductionVsFull}x`,
89
+ `Reduction vs relevant files: ${b.reductionVsRelevant}x`,
90
+ "",
91
+ ...b.perQuestion.map((pq) => `[${pq.reductionFull}x full / ${pq.reductionRelevant}x relevant] ${pq.question}`)
92
+ ].join("\n");
93
+ }
94
+ default:
95
+ return `Unknown tool: ${name}`;
96
+ }
97
+ }
98
+ var buffer = "";
99
+ process.stdin.setEncoding("utf-8");
100
+ process.stdin.on("data", (chunk) => {
101
+ buffer += chunk;
102
+ const lines = buffer.split("\n");
103
+ buffer = lines.pop() ?? "";
104
+ for (const line of lines) {
105
+ if (!line.trim()) continue;
106
+ try {
107
+ const req = JSON.parse(line);
108
+ handleRequest(req).then((res) => {
109
+ process.stdout.write(JSON.stringify(res) + "\n");
110
+ });
111
+ } catch {
112
+ }
113
+ }
114
+ });
115
+ async function handleRequest(req) {
116
+ switch (req.method) {
117
+ case "initialize":
118
+ return {
119
+ jsonrpc: "2.0",
120
+ id: req.id,
121
+ result: {
122
+ protocolVersion: "2024-11-05",
123
+ capabilities: { tools: {} },
124
+ serverInfo: { name: "engram", version: "0.1.0" }
125
+ }
126
+ };
127
+ case "tools/list":
128
+ return { jsonrpc: "2.0", id: req.id, result: { tools: TOOLS } };
129
+ case "tools/call": {
130
+ const params = req.params;
131
+ const text = await handleToolCall(params.name, params.arguments ?? {});
132
+ return { jsonrpc: "2.0", id: req.id, result: { content: [{ type: "text", text }] } };
133
+ }
134
+ default:
135
+ return { jsonrpc: "2.0", id: req.id, error: { code: -32601, message: `Unknown method: ${req.method}` } };
136
+ }
137
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "engramx",
3
+ "version": "0.1.0",
4
+ "description": "AI coding memory that learns from every session — persistent, structural, universal",
5
+ "type": "module",
6
+ "bin": {
7
+ "engram": "dist/cli.js",
8
+ "engram-serve": "dist/serve.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "dev": "tsup --watch",
14
+ "test": "vitest",
15
+ "lint": "tsc --noEmit",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "keywords": [
19
+ "ai",
20
+ "coding",
21
+ "memory",
22
+ "context",
23
+ "tree-sitter",
24
+ "knowledge-graph",
25
+ "mcp",
26
+ "claude",
27
+ "cursor",
28
+ "token-savings"
29
+ ],
30
+ "author": "Nicholas Ashkar",
31
+ "license": "Apache-2.0",
32
+ "engines": {
33
+ "node": ">=20"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "LICENSE",
38
+ "README.md"
39
+ ],
40
+ "dependencies": {
41
+ "chalk": "^5.6.2",
42
+ "commander": "^14.0.3",
43
+ "graphology": "^0.26.0",
44
+ "sql.js": "^1.14.1"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^25.5.2",
48
+ "tsup": "^8.5.1",
49
+ "typescript": "^6.0.2",
50
+ "vitest": "^4.1.4"
51
+ }
52
+ }