engramx 1.0.0 → 2.0.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.
- package/CHANGELOG.md +118 -0
- package/README.md +67 -7
- package/dist/{aider-context-TNGSXMVY.js → aider-context-BC5R2ZTA.js} +1 -1
- package/dist/cache-AK6CF3BC.js +10 -0
- package/dist/chunk-22INHMKB.js +31 -0
- package/dist/chunk-533LR4I7.js +220 -0
- package/dist/{chunk-QOG4K427.js → chunk-C6GBUOAL.js} +1 -1
- package/dist/chunk-CIQQ5Y3S.js +338 -0
- package/dist/chunk-KL6NSPVA.js +59 -0
- package/dist/{chunk-SBHGK5WA.js → chunk-PEH54LYC.js} +85 -3
- package/dist/{chunk-6SFMVYUN.js → chunk-SJT7VS2G.js} +127 -23
- package/dist/cli.js +381 -264
- package/dist/{core-77MHT3QV.js → core-6IY5L6II.js} +2 -2
- package/dist/{cursor-mdc-HWVUZUZH.js → cursor-mdc-GJ7E5LDD.js} +1 -1
- package/dist/{exporter-A3VSLS4U.js → exporter-GWU2GF23.js} +1 -1
- package/dist/grammars/tree-sitter-go.wasm +0 -0
- package/dist/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/grammars/tree-sitter-python.wasm +0 -0
- package/dist/grammars/tree-sitter-rust.wasm +0 -0
- package/dist/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/{importer-MCNFMV5O.js → importer-V62NGZRK.js} +4 -3
- package/dist/index.js +3 -3
- package/dist/{migrate-5ZJWF2HD.js → migrate-UKCO6BUU.js} +3 -1
- package/dist/plugin-loader-FCOMVOX7.js +100 -0
- package/dist/serve.js +2 -2
- package/dist/server-VBRTTECZ.js +1363 -0
- package/dist/{tuner-2LVIEE5V.js → tuner-KFNNGKG3.js} +4 -2
- package/dist/windsurf-rules-C7SVDHBL.js +59 -0
- package/package.json +4 -3
- package/dist/chunk-CEAANHHX.js +0 -88
- package/dist/server-I3C74ZLB.js +0 -193
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/generators/windsurf-rules.ts
|
|
2
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
5
|
+
function buildSection(heading, lines) {
|
|
6
|
+
if (lines.length === 0) return "";
|
|
7
|
+
return [`## ${heading}`, "", ...lines, ""].join("\n");
|
|
8
|
+
}
|
|
9
|
+
async function generateWindsurfRules(projectRoot) {
|
|
10
|
+
const { getStore } = await import("./core-6IY5L6II.js");
|
|
11
|
+
const store = await getStore(projectRoot);
|
|
12
|
+
try {
|
|
13
|
+
const allNodes = store.getAllNodes();
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
const cutoff = now - THIRTY_DAYS_MS;
|
|
16
|
+
const godNodes = store.getGodNodes(10);
|
|
17
|
+
const architectureLines = godNodes.map(
|
|
18
|
+
({ node, degree }) => `- \`${node.label}\` (${node.kind}, ${degree} connections) \u2014 ${node.sourceFile}`
|
|
19
|
+
);
|
|
20
|
+
const hotFileLines = allNodes.filter(
|
|
21
|
+
(n) => n.kind === "pattern" && n.metadata.type === "hot_file"
|
|
22
|
+
).slice(0, 10).map((n) => `- ${n.label}`);
|
|
23
|
+
const mistakeLines = allNodes.filter((n) => n.kind === "mistake").sort((a, b) => b.queryCount - a.queryCount).slice(0, 5).map((n) => `- ${n.label}`);
|
|
24
|
+
const decisionLines = allNodes.filter((n) => n.kind === "decision" && n.lastVerified >= cutoff).sort((a, b) => b.lastVerified - a.lastVerified).map((n) => `- ${n.label}`);
|
|
25
|
+
const patternLines = allNodes.filter(
|
|
26
|
+
(n) => n.kind === "pattern" && n.confidenceScore >= 0.8 && n.metadata.type !== "hot_file"
|
|
27
|
+
).map((n) => `- ${n.label}`);
|
|
28
|
+
const sections = [
|
|
29
|
+
buildSection("Architecture", architectureLines),
|
|
30
|
+
buildSection("Known Landmines", mistakeLines),
|
|
31
|
+
buildSection("Active Decisions", decisionLines),
|
|
32
|
+
buildSection("Key Patterns", patternLines),
|
|
33
|
+
buildSection("Hot Files", hotFileLines)
|
|
34
|
+
].filter((s) => s.length > 0);
|
|
35
|
+
const header = [
|
|
36
|
+
"# engram context",
|
|
37
|
+
"",
|
|
38
|
+
"> Auto-generated by engram. Do not edit manually.",
|
|
39
|
+
"> Regenerate: `engram gen-windsurfrules -p .`",
|
|
40
|
+
'> Prefer `engram query "..."` for structural queries \u2014 this file is a snapshot.',
|
|
41
|
+
""
|
|
42
|
+
].join("\n");
|
|
43
|
+
const content = header + sections.join("\n");
|
|
44
|
+
const outPath = join(projectRoot, ".windsurfrules");
|
|
45
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
46
|
+
writeFileSync(outPath, content, "utf-8");
|
|
47
|
+
const nodeCount = architectureLines.length + hotFileLines.length + mistakeLines.length + decisionLines.length + patternLines.length;
|
|
48
|
+
return {
|
|
49
|
+
filePath: outPath,
|
|
50
|
+
sections: sections.length,
|
|
51
|
+
nodes: nodeCount
|
|
52
|
+
};
|
|
53
|
+
} finally {
|
|
54
|
+
store.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
generateWindsurfRules
|
|
59
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "engramx",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "The context spine for AI coding agents.
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "The context spine for AI coding agents. 8 providers + pluggable context sources, 3-layer memory cache, web dashboard, multi-IDE support (Claude Code, Cursor, Continue, Zed, Aider, Windsurf, Neovim, Emacs). 88.1% measured session-level token savings. Local SQLite, zero native deps, zero cloud.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/NickCirv/engram.git"
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
},
|
|
19
19
|
"main": "./dist/index.js",
|
|
20
20
|
"scripts": {
|
|
21
|
-
"build": "tsup",
|
|
21
|
+
"build": "tsup && node scripts/bundle-grammars.mjs",
|
|
22
|
+
"build:nogrammars": "tsup",
|
|
22
23
|
"dev": "tsup --watch",
|
|
23
24
|
"test": "vitest",
|
|
24
25
|
"lint": "tsc --noEmit",
|
package/dist/chunk-CEAANHHX.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// src/tuner/config.ts
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
var DEFAULTS = {
|
|
5
|
-
confidenceThreshold: 0.7,
|
|
6
|
-
totalTokenBudget: 600,
|
|
7
|
-
providers: {}
|
|
8
|
-
};
|
|
9
|
-
function readConfig(projectRoot) {
|
|
10
|
-
const configPath = join(projectRoot, ".engram", "config.json");
|
|
11
|
-
if (!existsSync(configPath)) return DEFAULTS;
|
|
12
|
-
try {
|
|
13
|
-
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
14
|
-
return {
|
|
15
|
-
...DEFAULTS,
|
|
16
|
-
...raw,
|
|
17
|
-
providers: { ...DEFAULTS.providers, ...raw.providers ?? {} }
|
|
18
|
-
};
|
|
19
|
-
} catch {
|
|
20
|
-
return DEFAULTS;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
function writeConfig(projectRoot, config) {
|
|
24
|
-
const configPath = join(projectRoot, ".engram", "config.json");
|
|
25
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// src/intelligence/hook-log.ts
|
|
29
|
-
import {
|
|
30
|
-
appendFileSync,
|
|
31
|
-
existsSync as existsSync2,
|
|
32
|
-
renameSync,
|
|
33
|
-
statSync,
|
|
34
|
-
readFileSync as readFileSync2
|
|
35
|
-
} from "fs";
|
|
36
|
-
import { join as join2 } from "path";
|
|
37
|
-
var HOOK_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
38
|
-
var LOG_FILENAME = "hook-log.jsonl";
|
|
39
|
-
var LOG_ROTATED_FILENAME = "hook-log.jsonl.1";
|
|
40
|
-
function logHookEvent(projectRoot, entry) {
|
|
41
|
-
if (!projectRoot) return;
|
|
42
|
-
try {
|
|
43
|
-
const logPath = join2(projectRoot, ".engram", LOG_FILENAME);
|
|
44
|
-
rotateIfNeeded(projectRoot);
|
|
45
|
-
const line = JSON.stringify({
|
|
46
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47
|
-
...entry
|
|
48
|
-
}) + "\n";
|
|
49
|
-
appendFileSync(logPath, line);
|
|
50
|
-
} catch {
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function rotateIfNeeded(projectRoot) {
|
|
54
|
-
try {
|
|
55
|
-
const logPath = join2(projectRoot, ".engram", LOG_FILENAME);
|
|
56
|
-
if (!existsSync2(logPath)) return;
|
|
57
|
-
const size = statSync(logPath).size;
|
|
58
|
-
if (size < HOOK_LOG_MAX_BYTES) return;
|
|
59
|
-
const rotatedPath = join2(projectRoot, ".engram", LOG_ROTATED_FILENAME);
|
|
60
|
-
renameSync(logPath, rotatedPath);
|
|
61
|
-
} catch {
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function readHookLog(projectRoot) {
|
|
65
|
-
try {
|
|
66
|
-
const logPath = join2(projectRoot, ".engram", LOG_FILENAME);
|
|
67
|
-
if (!existsSync2(logPath)) return [];
|
|
68
|
-
const raw = readFileSync2(logPath, "utf-8");
|
|
69
|
-
const entries = [];
|
|
70
|
-
for (const line of raw.split("\n")) {
|
|
71
|
-
if (!line.trim()) continue;
|
|
72
|
-
try {
|
|
73
|
-
entries.push(JSON.parse(line));
|
|
74
|
-
} catch {
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return entries;
|
|
78
|
-
} catch {
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export {
|
|
84
|
-
readConfig,
|
|
85
|
-
writeConfig,
|
|
86
|
-
logHookEvent,
|
|
87
|
-
readHookLog
|
|
88
|
-
};
|
package/dist/server-I3C74ZLB.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
learn,
|
|
3
|
-
query,
|
|
4
|
-
stats
|
|
5
|
-
} from "./chunk-6SFMVYUN.js";
|
|
6
|
-
import "./chunk-SBHGK5WA.js";
|
|
7
|
-
|
|
8
|
-
// src/server/http.ts
|
|
9
|
-
import { createServer } from "http";
|
|
10
|
-
import { writeFileSync, unlinkSync, mkdirSync, existsSync } from "fs";
|
|
11
|
-
import { join } from "path";
|
|
12
|
-
import { createRequire } from "module";
|
|
13
|
-
var require2 = createRequire(import.meta.url);
|
|
14
|
-
var PKG_VERSION = (() => {
|
|
15
|
-
for (const p of ["../package.json", "../../package.json"]) {
|
|
16
|
-
try {
|
|
17
|
-
return require2(p).version;
|
|
18
|
-
} catch {
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return "0.0.0";
|
|
22
|
-
})();
|
|
23
|
-
var PROVIDERS = [
|
|
24
|
-
"structure",
|
|
25
|
-
"mistakes",
|
|
26
|
-
"git",
|
|
27
|
-
"mempalace",
|
|
28
|
-
"context7",
|
|
29
|
-
"obsidian"
|
|
30
|
-
];
|
|
31
|
-
function parseUrl(req) {
|
|
32
|
-
return new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
33
|
-
}
|
|
34
|
-
async function readBody(req) {
|
|
35
|
-
const chunks = [];
|
|
36
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
37
|
-
return Buffer.concat(chunks).toString("utf-8");
|
|
38
|
-
}
|
|
39
|
-
function json(res, status, data) {
|
|
40
|
-
res.writeHead(status, {
|
|
41
|
-
"Content-Type": "application/json",
|
|
42
|
-
"Access-Control-Allow-Origin": "*",
|
|
43
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
44
|
-
"Access-Control-Allow-Headers": "Authorization, Content-Type"
|
|
45
|
-
});
|
|
46
|
-
res.end(JSON.stringify(data));
|
|
47
|
-
}
|
|
48
|
-
function checkAuth(req, res) {
|
|
49
|
-
const token = process.env.ENGRAM_API_TOKEN;
|
|
50
|
-
if (!token) return true;
|
|
51
|
-
const header = req.headers.authorization ?? "";
|
|
52
|
-
if (header === `Bearer ${token}`) return true;
|
|
53
|
-
json(res, 401, { error: "Unauthorized" });
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
function handleHealth(_req, res, startedAt) {
|
|
57
|
-
json(res, 200, {
|
|
58
|
-
ok: true,
|
|
59
|
-
version: PKG_VERSION,
|
|
60
|
-
uptime: Math.floor((Date.now() - startedAt) / 1e3)
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
async function handleQuery(req, res, projectRoot) {
|
|
64
|
-
const url = parseUrl(req);
|
|
65
|
-
const q = url.searchParams.get("q");
|
|
66
|
-
if (!q) {
|
|
67
|
-
json(res, 400, { error: "Missing query parameter 'q'" });
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const budget = parseInt(url.searchParams.get("budget") ?? "2000", 10);
|
|
71
|
-
try {
|
|
72
|
-
const result = await query(projectRoot, q, { tokenBudget: isNaN(budget) ? 2e3 : budget });
|
|
73
|
-
json(res, 200, {
|
|
74
|
-
text: result.text,
|
|
75
|
-
estimatedTokens: result.estimatedTokens,
|
|
76
|
-
providers: [...PROVIDERS]
|
|
77
|
-
});
|
|
78
|
-
} catch (err) {
|
|
79
|
-
json(res, 500, { error: "Query failed", detail: String(err) });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
async function handleStats(_req, res, projectRoot) {
|
|
83
|
-
try {
|
|
84
|
-
const result = await stats(projectRoot);
|
|
85
|
-
json(res, 200, result);
|
|
86
|
-
} catch (err) {
|
|
87
|
-
json(res, 500, { error: "Stats failed", detail: String(err) });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function handleProviders(_req, res) {
|
|
91
|
-
const list = PROVIDERS.map((name) => ({ name, available: true }));
|
|
92
|
-
json(res, 200, list);
|
|
93
|
-
}
|
|
94
|
-
async function handleLearn(req, res, projectRoot) {
|
|
95
|
-
let body;
|
|
96
|
-
try {
|
|
97
|
-
body = await readBody(req);
|
|
98
|
-
} catch {
|
|
99
|
-
json(res, 400, { error: "Failed to read request body" });
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
let parsed;
|
|
103
|
-
try {
|
|
104
|
-
parsed = JSON.parse(body);
|
|
105
|
-
} catch {
|
|
106
|
-
json(res, 400, { error: "Invalid JSON body" });
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (!parsed.content || typeof parsed.content !== "string") {
|
|
110
|
-
json(res, 400, { error: "Missing 'content' in request body" });
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
await learn(projectRoot, parsed.content, parsed.file ?? "http-api");
|
|
115
|
-
json(res, 201, { ok: true });
|
|
116
|
-
} catch (err) {
|
|
117
|
-
json(res, 500, { error: "Learn failed", detail: String(err) });
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function writePid(projectRoot) {
|
|
121
|
-
const dir = join(projectRoot, ".engram");
|
|
122
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
123
|
-
writeFileSync(join(dir, "http-server.pid"), String(process.pid), "utf-8");
|
|
124
|
-
}
|
|
125
|
-
function removePid(projectRoot) {
|
|
126
|
-
try {
|
|
127
|
-
unlinkSync(join(projectRoot, ".engram", "http-server.pid"));
|
|
128
|
-
} catch {
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function createHttpServer(projectRoot, port) {
|
|
132
|
-
return new Promise((resolve, reject) => {
|
|
133
|
-
const startedAt = Date.now();
|
|
134
|
-
const server = createServer(async (req, res) => {
|
|
135
|
-
if (req.method === "OPTIONS") {
|
|
136
|
-
res.writeHead(204, {
|
|
137
|
-
"Access-Control-Allow-Origin": "*",
|
|
138
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
139
|
-
"Access-Control-Allow-Headers": "Authorization, Content-Type"
|
|
140
|
-
});
|
|
141
|
-
res.end();
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (!checkAuth(req, res)) return;
|
|
145
|
-
const url = parseUrl(req);
|
|
146
|
-
const path = url.pathname;
|
|
147
|
-
try {
|
|
148
|
-
if (req.method === "GET" && path === "/health") {
|
|
149
|
-
handleHealth(req, res, startedAt);
|
|
150
|
-
} else if (req.method === "GET" && path === "/query") {
|
|
151
|
-
await handleQuery(req, res, projectRoot);
|
|
152
|
-
} else if (req.method === "GET" && path === "/stats") {
|
|
153
|
-
await handleStats(req, res, projectRoot);
|
|
154
|
-
} else if (req.method === "GET" && path === "/providers") {
|
|
155
|
-
handleProviders(req, res);
|
|
156
|
-
} else if (req.method === "POST" && path === "/learn") {
|
|
157
|
-
await handleLearn(req, res, projectRoot);
|
|
158
|
-
} else {
|
|
159
|
-
json(res, 404, { error: "Not found" });
|
|
160
|
-
}
|
|
161
|
-
} catch (err) {
|
|
162
|
-
json(res, 500, { error: "Internal server error", detail: String(err) });
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
server.on("error", (err) => {
|
|
166
|
-
removePid(projectRoot);
|
|
167
|
-
reject(err);
|
|
168
|
-
});
|
|
169
|
-
server.listen(port, "127.0.0.1", () => {
|
|
170
|
-
writePid(projectRoot);
|
|
171
|
-
const cleanup = () => {
|
|
172
|
-
removePid(projectRoot);
|
|
173
|
-
server.close(() => process.exit(0));
|
|
174
|
-
};
|
|
175
|
-
process.on("SIGINT", cleanup);
|
|
176
|
-
process.on("SIGTERM", cleanup);
|
|
177
|
-
resolve();
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// src/server/index.ts
|
|
183
|
-
var DEFAULT_PORT = 7337;
|
|
184
|
-
async function startHttpServer(projectRoot, port = DEFAULT_PORT) {
|
|
185
|
-
await createHttpServer(projectRoot, port);
|
|
186
|
-
process.stdout.write(
|
|
187
|
-
`engram HTTP server listening on http://127.0.0.1:${port}
|
|
188
|
-
`
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
export {
|
|
192
|
-
startHttpServer
|
|
193
|
-
};
|