engramx 0.5.2 → 1.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 +71 -0
- package/README.md +264 -291
- package/dist/aider-context-TNGSXMVY.js +54 -0
- package/dist/{chunk-V5VQQ3SF.js → chunk-6SFMVYUN.js} +7 -2
- package/dist/chunk-CEAANHHX.js +88 -0
- package/dist/{chunk-LH2ZID5Z.js → chunk-QOG4K427.js} +1 -1
- package/dist/chunk-SBHGK5WA.js +104 -0
- package/dist/cli.js +747 -130
- package/dist/{core-VUVXLXZN.js → core-77MHT3QV.js} +2 -1
- package/dist/cursor-mdc-HWVUZUZH.js +75 -0
- package/dist/exporter-A3VSLS4U.js +47 -0
- package/dist/importer-MCNFMV5O.js +66 -0
- package/dist/index.js +3 -2
- package/dist/migrate-5ZJWF2HD.js +10 -0
- package/dist/serve.js +2 -1
- package/dist/server-I3C74ZLB.js +193 -0
- package/dist/tuner-2LVIEE5V.js +113 -0
- package/package.json +11 -3
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
logHookEvent,
|
|
4
|
+
readConfig,
|
|
5
|
+
readHookLog
|
|
6
|
+
} from "./chunk-CEAANHHX.js";
|
|
2
7
|
import {
|
|
3
8
|
autogen,
|
|
4
9
|
install,
|
|
5
10
|
status,
|
|
6
11
|
uninstall
|
|
7
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-QOG4K427.js";
|
|
8
13
|
import {
|
|
9
14
|
benchmark,
|
|
10
15
|
computeKeywordIDF,
|
|
@@ -21,21 +26,22 @@ import {
|
|
|
21
26
|
renderFileStructure,
|
|
22
27
|
stats,
|
|
23
28
|
toPosixPath
|
|
24
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-6SFMVYUN.js";
|
|
30
|
+
import "./chunk-SBHGK5WA.js";
|
|
25
31
|
|
|
26
32
|
// src/cli.ts
|
|
27
33
|
import { Command } from "commander";
|
|
28
34
|
import chalk2 from "chalk";
|
|
29
35
|
import {
|
|
30
|
-
existsSync as
|
|
31
|
-
readFileSync as
|
|
32
|
-
writeFileSync as
|
|
36
|
+
existsSync as existsSync10,
|
|
37
|
+
readFileSync as readFileSync6,
|
|
38
|
+
writeFileSync as writeFileSync3,
|
|
33
39
|
mkdirSync,
|
|
34
40
|
unlinkSync,
|
|
35
41
|
copyFileSync,
|
|
36
|
-
renameSync as
|
|
42
|
+
renameSync as renameSync2
|
|
37
43
|
} from "fs";
|
|
38
|
-
import { dirname as
|
|
44
|
+
import { dirname as dirname4, join as join10, resolve as pathResolve } from "path";
|
|
39
45
|
import { homedir } from "os";
|
|
40
46
|
|
|
41
47
|
// src/intercept/safety.ts
|
|
@@ -342,15 +348,253 @@ function buildSessionContextResponse(eventName, additionalContext) {
|
|
|
342
348
|
|
|
343
349
|
// src/providers/types.ts
|
|
344
350
|
var PROVIDER_PRIORITY = [
|
|
351
|
+
"engram:ast",
|
|
345
352
|
"engram:structure",
|
|
346
353
|
"engram:mistakes",
|
|
347
354
|
"mempalace",
|
|
348
355
|
"context7",
|
|
349
356
|
"engram:git",
|
|
350
|
-
"obsidian"
|
|
357
|
+
"obsidian",
|
|
358
|
+
"engram:lsp"
|
|
351
359
|
];
|
|
352
360
|
var DEFAULT_CACHE_TTL_SEC = 3600;
|
|
353
361
|
|
|
362
|
+
// src/providers/ast.ts
|
|
363
|
+
import { readFileSync } from "fs";
|
|
364
|
+
|
|
365
|
+
// src/providers/grammar-loader.ts
|
|
366
|
+
import { existsSync as existsSync3 } from "fs";
|
|
367
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
368
|
+
import { createRequire } from "module";
|
|
369
|
+
import { fileURLToPath } from "url";
|
|
370
|
+
var require2 = createRequire(import.meta.url);
|
|
371
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
372
|
+
var tsParserInit = false;
|
|
373
|
+
var EXT_TO_LANG = {
|
|
374
|
+
ts: "typescript",
|
|
375
|
+
tsx: "tsx",
|
|
376
|
+
js: "javascript",
|
|
377
|
+
jsx: "javascript",
|
|
378
|
+
mjs: "javascript",
|
|
379
|
+
cjs: "javascript",
|
|
380
|
+
py: "python",
|
|
381
|
+
go: "go",
|
|
382
|
+
rs: "rust",
|
|
383
|
+
rb: "ruby",
|
|
384
|
+
java: "java",
|
|
385
|
+
c: "c",
|
|
386
|
+
cpp: "cpp",
|
|
387
|
+
h: "c",
|
|
388
|
+
hpp: "cpp",
|
|
389
|
+
php: "php"
|
|
390
|
+
};
|
|
391
|
+
var LANG_TO_PKG = {
|
|
392
|
+
typescript: "tree-sitter-typescript",
|
|
393
|
+
tsx: "tree-sitter-typescript",
|
|
394
|
+
javascript: "tree-sitter-javascript",
|
|
395
|
+
python: "tree-sitter-python",
|
|
396
|
+
go: "tree-sitter-go",
|
|
397
|
+
rust: "tree-sitter-rust"
|
|
398
|
+
};
|
|
399
|
+
function getSupportedLang(filePath) {
|
|
400
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
401
|
+
return ext ? EXT_TO_LANG[ext] ?? null : null;
|
|
402
|
+
}
|
|
403
|
+
function findGrammarWasm(lang) {
|
|
404
|
+
const pkg = LANG_TO_PKG[lang];
|
|
405
|
+
if (!pkg) return null;
|
|
406
|
+
const wasmName = lang === "tsx" ? "tree-sitter-tsx.wasm" : `tree-sitter-${lang}.wasm`;
|
|
407
|
+
const candidates = [];
|
|
408
|
+
try {
|
|
409
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
410
|
+
candidates.push(join3(here, "..", "..", "node_modules", pkg, wasmName));
|
|
411
|
+
candidates.push(join3(here, "..", "grammars", wasmName));
|
|
412
|
+
} catch {
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
const pkgMain = require2.resolve(`${pkg}/package.json`);
|
|
416
|
+
const pkgDir = dirname2(pkgMain);
|
|
417
|
+
candidates.push(join3(pkgDir, wasmName));
|
|
418
|
+
} catch {
|
|
419
|
+
}
|
|
420
|
+
return candidates.find((c) => existsSync3(c)) ?? null;
|
|
421
|
+
}
|
|
422
|
+
async function getParser(lang) {
|
|
423
|
+
const cached = parserCache.get(lang);
|
|
424
|
+
if (cached) return cached;
|
|
425
|
+
try {
|
|
426
|
+
const { Parser, Language } = await import("web-tree-sitter");
|
|
427
|
+
if (!tsParserInit) {
|
|
428
|
+
await Parser.init();
|
|
429
|
+
tsParserInit = true;
|
|
430
|
+
}
|
|
431
|
+
const wasmPath = findGrammarWasm(lang);
|
|
432
|
+
if (!wasmPath) return null;
|
|
433
|
+
const language = await Language.load(wasmPath);
|
|
434
|
+
const parser = new Parser();
|
|
435
|
+
parser.setLanguage(language);
|
|
436
|
+
parserCache.set(lang, parser);
|
|
437
|
+
return parser;
|
|
438
|
+
} catch {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/providers/ast.ts
|
|
444
|
+
function extractParams(node) {
|
|
445
|
+
const paramsNode = node.childForFieldName("parameters") ?? node.childForFieldName("formal_parameters");
|
|
446
|
+
if (!paramsNode) return "";
|
|
447
|
+
return paramsNode.text.replace(/\n/g, " ").replace(/\s+/g, " ").slice(0, 80).trim();
|
|
448
|
+
}
|
|
449
|
+
function extractSymbols(rootNode) {
|
|
450
|
+
const symbols = [];
|
|
451
|
+
function visit(node) {
|
|
452
|
+
switch (node.type) {
|
|
453
|
+
// ── Functions ───────────────────────────────────────────────
|
|
454
|
+
case "function_declaration":
|
|
455
|
+
case "function_definition": {
|
|
456
|
+
const nameNode = node.childForFieldName("name");
|
|
457
|
+
if (nameNode) {
|
|
458
|
+
symbols.push({
|
|
459
|
+
name: nameNode.text,
|
|
460
|
+
kind: "function",
|
|
461
|
+
line: node.startPosition.row + 1,
|
|
462
|
+
params: extractParams(node)
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
// ── Classes ─────────────────────────────────────────────────
|
|
468
|
+
case "class_declaration":
|
|
469
|
+
case "class_definition": {
|
|
470
|
+
const nameNode = node.childForFieldName("name");
|
|
471
|
+
if (nameNode) {
|
|
472
|
+
symbols.push({
|
|
473
|
+
name: nameNode.text,
|
|
474
|
+
kind: "class",
|
|
475
|
+
line: node.startPosition.row + 1
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
// ── Methods ─────────────────────────────────────────────────
|
|
481
|
+
case "method_definition":
|
|
482
|
+
case "method_declaration": {
|
|
483
|
+
const nameNode = node.childForFieldName("name");
|
|
484
|
+
if (nameNode) {
|
|
485
|
+
symbols.push({
|
|
486
|
+
name: nameNode.text,
|
|
487
|
+
kind: "method",
|
|
488
|
+
line: node.startPosition.row + 1,
|
|
489
|
+
params: extractParams(node)
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
// ── TypeScript interfaces ────────────────────────────────────
|
|
495
|
+
case "interface_declaration": {
|
|
496
|
+
const nameNode = node.childForFieldName("name");
|
|
497
|
+
if (nameNode) {
|
|
498
|
+
symbols.push({
|
|
499
|
+
name: nameNode.text,
|
|
500
|
+
kind: "interface",
|
|
501
|
+
line: node.startPosition.row + 1
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
// ── TypeScript type aliases ──────────────────────────────────
|
|
507
|
+
case "type_alias_declaration": {
|
|
508
|
+
const nameNode = node.childForFieldName("name");
|
|
509
|
+
if (nameNode) {
|
|
510
|
+
symbols.push({
|
|
511
|
+
name: nameNode.text,
|
|
512
|
+
kind: "type",
|
|
513
|
+
line: node.startPosition.row + 1
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
// ── Exported variable declarations (incl. arrow functions) ──
|
|
519
|
+
case "lexical_declaration":
|
|
520
|
+
case "variable_declaration": {
|
|
521
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
522
|
+
const child = node.child(i);
|
|
523
|
+
if (!child || child.type !== "variable_declarator") continue;
|
|
524
|
+
const nameNode = child.childForFieldName("name");
|
|
525
|
+
const valueNode = child.childForFieldName("value");
|
|
526
|
+
if (!nameNode) continue;
|
|
527
|
+
const isArrow = valueNode?.type === "arrow_function" || valueNode?.type === "function";
|
|
528
|
+
symbols.push({
|
|
529
|
+
name: nameNode.text,
|
|
530
|
+
kind: isArrow ? "function" : "variable",
|
|
531
|
+
line: node.startPosition.row + 1,
|
|
532
|
+
params: isArrow && valueNode ? extractParams(valueNode) : void 0
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
default:
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
541
|
+
const child = node.child(i);
|
|
542
|
+
if (child) visit(child);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
visit(rootNode);
|
|
546
|
+
return symbols;
|
|
547
|
+
}
|
|
548
|
+
function formatSymbols(symbols, tokenBudget) {
|
|
549
|
+
const lines = symbols.map((s) => {
|
|
550
|
+
const params = s.params !== void 0 ? `(${s.params})` : "";
|
|
551
|
+
return `${s.kind.toUpperCase()} ${s.name}${params} L${s.line}`;
|
|
552
|
+
});
|
|
553
|
+
const charBudget = tokenBudget * 4;
|
|
554
|
+
let text = lines.join("\n");
|
|
555
|
+
if (text.length > charBudget) {
|
|
556
|
+
text = text.slice(0, charBudget).trimEnd() + "\n... (truncated)";
|
|
557
|
+
}
|
|
558
|
+
return text;
|
|
559
|
+
}
|
|
560
|
+
var astProvider = {
|
|
561
|
+
name: "engram:ast",
|
|
562
|
+
label: "AST STRUCTURE",
|
|
563
|
+
tier: 1,
|
|
564
|
+
tokenBudget: 300,
|
|
565
|
+
timeoutMs: 200,
|
|
566
|
+
async resolve(filePath, _context) {
|
|
567
|
+
const lang = getSupportedLang(filePath);
|
|
568
|
+
if (!lang) return null;
|
|
569
|
+
const parser = await getParser(lang);
|
|
570
|
+
if (!parser) return null;
|
|
571
|
+
try {
|
|
572
|
+
const source = readFileSync(filePath, "utf-8");
|
|
573
|
+
const tree = parser.parse(source);
|
|
574
|
+
if (!tree) return null;
|
|
575
|
+
const symbols = extractSymbols(tree.rootNode);
|
|
576
|
+
if (symbols.length === 0) return null;
|
|
577
|
+
return {
|
|
578
|
+
provider: "engram:ast",
|
|
579
|
+
content: formatSymbols(symbols, this.tokenBudget),
|
|
580
|
+
confidence: 1,
|
|
581
|
+
cached: false
|
|
582
|
+
};
|
|
583
|
+
} catch {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
async isAvailable() {
|
|
588
|
+
try {
|
|
589
|
+
const { Parser } = await import("web-tree-sitter");
|
|
590
|
+
await Parser.init();
|
|
591
|
+
return true;
|
|
592
|
+
} catch {
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
354
598
|
// src/providers/engram-structure.ts
|
|
355
599
|
var structureProvider = {
|
|
356
600
|
name: "engram:structure",
|
|
@@ -935,16 +1179,161 @@ async function fetchWithTimeout(url, timeoutMs) {
|
|
|
935
1179
|
}
|
|
936
1180
|
}
|
|
937
1181
|
|
|
1182
|
+
// src/providers/lsp-connection.ts
|
|
1183
|
+
import { connect } from "net";
|
|
1184
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1185
|
+
import { tmpdir } from "os";
|
|
1186
|
+
import { join as join4 } from "path";
|
|
1187
|
+
function candidateSockets() {
|
|
1188
|
+
const uid = process.getuid?.() ?? 0;
|
|
1189
|
+
const tmp = tmpdir();
|
|
1190
|
+
return [
|
|
1191
|
+
// TypeScript language server (used by VS Code)
|
|
1192
|
+
join4(tmp, `tsserver-${uid}.sock`),
|
|
1193
|
+
// Generic LSP socket (some editors, e.g. Helix)
|
|
1194
|
+
join4(tmp, "lsp-server.sock"),
|
|
1195
|
+
// TypeScript language server alternate path
|
|
1196
|
+
join4(tmp, "typescript-language-server.sock"),
|
|
1197
|
+
// Pyright (Python)
|
|
1198
|
+
join4(tmp, `pyright-${uid}.sock`),
|
|
1199
|
+
// rust-analyzer
|
|
1200
|
+
join4(tmp, "rust-analyzer.sock")
|
|
1201
|
+
];
|
|
1202
|
+
}
|
|
1203
|
+
var LspConnection = class _LspConnection {
|
|
1204
|
+
socket = null;
|
|
1205
|
+
_requestId = 0;
|
|
1206
|
+
/**
|
|
1207
|
+
* Attempt to connect to any currently-running LSP server socket.
|
|
1208
|
+
* Returns null — not throws — if no socket is found or connection fails.
|
|
1209
|
+
* Timeout per candidate: 500ms.
|
|
1210
|
+
*/
|
|
1211
|
+
static async tryConnect() {
|
|
1212
|
+
const candidates = candidateSockets().filter((p) => existsSync4(p));
|
|
1213
|
+
if (candidates.length === 0) return null;
|
|
1214
|
+
for (const path2 of candidates) {
|
|
1215
|
+
try {
|
|
1216
|
+
const conn = new _LspConnection();
|
|
1217
|
+
await conn._connect(path2);
|
|
1218
|
+
return conn;
|
|
1219
|
+
} catch {
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1225
|
+
/** Internal: open a socket to the given path with a 500ms timeout. */
|
|
1226
|
+
_connect(socketPath) {
|
|
1227
|
+
return new Promise((resolve7, reject) => {
|
|
1228
|
+
const socket = connect(socketPath);
|
|
1229
|
+
const timeout = setTimeout(() => {
|
|
1230
|
+
socket.destroy();
|
|
1231
|
+
reject(new Error("LSP connect timeout"));
|
|
1232
|
+
}, 500);
|
|
1233
|
+
socket.on("connect", () => {
|
|
1234
|
+
clearTimeout(timeout);
|
|
1235
|
+
this.socket = socket;
|
|
1236
|
+
resolve7();
|
|
1237
|
+
});
|
|
1238
|
+
socket.on("error", (err) => {
|
|
1239
|
+
clearTimeout(timeout);
|
|
1240
|
+
reject(err);
|
|
1241
|
+
});
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Request hover info for a position.
|
|
1246
|
+
*
|
|
1247
|
+
* Stub: returns null. A full implementation would send a JSON-RPC
|
|
1248
|
+
* textDocument/hover request and parse the response. Left as a stub
|
|
1249
|
+
* because the response requires a request/response correlation loop
|
|
1250
|
+
* over a streaming socket — non-trivial, and out of scope for v0.5.x.
|
|
1251
|
+
* The provider benefits from the availability check alone.
|
|
1252
|
+
*/
|
|
1253
|
+
async hover(_filePath, _line, _character) {
|
|
1254
|
+
if (!this.socket) return null;
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Fetch diagnostics for a file.
|
|
1259
|
+
*
|
|
1260
|
+
* Stub: returns []. A full implementation would use the
|
|
1261
|
+
* textDocument/diagnostic pull request (LSP 3.17+) or subscribe to
|
|
1262
|
+
* publishDiagnostics push notifications. Deferred to a future sprint.
|
|
1263
|
+
*/
|
|
1264
|
+
async getDiagnostics(_filePath) {
|
|
1265
|
+
if (!this.socket) return [];
|
|
1266
|
+
return [];
|
|
1267
|
+
}
|
|
1268
|
+
/** Whether this connection has a live socket. */
|
|
1269
|
+
get connected() {
|
|
1270
|
+
return this.socket !== null && !this.socket.destroyed;
|
|
1271
|
+
}
|
|
1272
|
+
/** Close and destroy the socket. Safe to call multiple times. */
|
|
1273
|
+
close() {
|
|
1274
|
+
this.socket?.destroy();
|
|
1275
|
+
this.socket = null;
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
// src/providers/lsp.ts
|
|
1280
|
+
var cachedConnection = void 0;
|
|
1281
|
+
async function getConnection() {
|
|
1282
|
+
if (cachedConnection instanceof LspConnection) {
|
|
1283
|
+
if (cachedConnection.connected) return cachedConnection;
|
|
1284
|
+
cachedConnection.close();
|
|
1285
|
+
cachedConnection = void 0;
|
|
1286
|
+
}
|
|
1287
|
+
if (cachedConnection === null) return null;
|
|
1288
|
+
cachedConnection = await LspConnection.tryConnect();
|
|
1289
|
+
return cachedConnection;
|
|
1290
|
+
}
|
|
1291
|
+
var lspProvider = {
|
|
1292
|
+
name: "engram:lsp",
|
|
1293
|
+
label: "LSP CONTEXT",
|
|
1294
|
+
tier: 1,
|
|
1295
|
+
tokenBudget: 100,
|
|
1296
|
+
timeoutMs: 100,
|
|
1297
|
+
async resolve(filePath, _context) {
|
|
1298
|
+
try {
|
|
1299
|
+
const conn = await getConnection();
|
|
1300
|
+
if (!conn) return null;
|
|
1301
|
+
const hover = await conn.hover(filePath, 0, 0);
|
|
1302
|
+
if (!hover?.contents) return null;
|
|
1303
|
+
const content = typeof hover.contents === "string" ? hover.contents : JSON.stringify(hover.contents);
|
|
1304
|
+
const charBudget = this.tokenBudget * 4;
|
|
1305
|
+
const truncated = content.length > charBudget ? content.slice(0, charBudget) + "..." : content;
|
|
1306
|
+
return {
|
|
1307
|
+
provider: "engram:lsp",
|
|
1308
|
+
content: truncated,
|
|
1309
|
+
confidence: 0.95,
|
|
1310
|
+
cached: false
|
|
1311
|
+
};
|
|
1312
|
+
} catch {
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
async isAvailable() {
|
|
1317
|
+
try {
|
|
1318
|
+
const conn = await getConnection();
|
|
1319
|
+
return conn !== null;
|
|
1320
|
+
} catch {
|
|
1321
|
+
return false;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
|
|
938
1326
|
// src/providers/resolver.ts
|
|
939
1327
|
var ALL_PROVIDERS = [
|
|
1328
|
+
astProvider,
|
|
940
1329
|
structureProvider,
|
|
941
1330
|
mistakesProvider,
|
|
942
1331
|
gitProvider,
|
|
943
1332
|
mempalaceProvider,
|
|
944
1333
|
context7Provider,
|
|
945
|
-
obsidianProvider
|
|
1334
|
+
obsidianProvider,
|
|
1335
|
+
lspProvider
|
|
946
1336
|
];
|
|
947
|
-
var TOTAL_TOKEN_BUDGET = 600;
|
|
948
1337
|
function estimateTokens(text) {
|
|
949
1338
|
return Math.ceil(text.length / 4);
|
|
950
1339
|
}
|
|
@@ -966,16 +1355,20 @@ async function resolveRichPacket(filePath, context, enabledProviders) {
|
|
|
966
1355
|
}
|
|
967
1356
|
}
|
|
968
1357
|
if (results.length === 0) return null;
|
|
969
|
-
const
|
|
1358
|
+
const hasAst = results.some((r) => r.provider === "engram:ast");
|
|
1359
|
+
const deduped = hasAst ? results.filter((r) => r.provider !== "engram:structure") : results;
|
|
1360
|
+
const sorted = deduped.sort((a, b) => {
|
|
970
1361
|
const aIdx = PROVIDER_PRIORITY.indexOf(a.provider);
|
|
971
1362
|
const bIdx = PROVIDER_PRIORITY.indexOf(b.provider);
|
|
972
1363
|
return (aIdx === -1 ? 99 : aIdx) - (bIdx === -1 ? 99 : bIdx);
|
|
973
1364
|
});
|
|
1365
|
+
const config = readConfig(context.projectRoot);
|
|
1366
|
+
const budget = config.totalTokenBudget;
|
|
974
1367
|
const sections = [];
|
|
975
1368
|
let totalTokens = 0;
|
|
976
1369
|
for (const result of sorted) {
|
|
977
1370
|
const sectionTokens = estimateTokens(result.content);
|
|
978
|
-
if (totalTokens + sectionTokens >
|
|
1371
|
+
if (totalTokens + sectionTokens > budget) {
|
|
979
1372
|
break;
|
|
980
1373
|
}
|
|
981
1374
|
const provider = ALL_PROVIDERS.find((p) => p.name === result.provider);
|
|
@@ -1012,7 +1405,7 @@ async function warmAllProviders(projectRoot, enabledProviders) {
|
|
|
1012
1405
|
try {
|
|
1013
1406
|
const result = await withTimeout2(p.warmup(projectRoot), 5e3);
|
|
1014
1407
|
if (result && result.entries.length > 0) {
|
|
1015
|
-
const { getStore: getStore2 } = await import("./core-
|
|
1408
|
+
const { getStore: getStore2 } = await import("./core-77MHT3QV.js");
|
|
1016
1409
|
const store = await getStore2(projectRoot);
|
|
1017
1410
|
try {
|
|
1018
1411
|
store.warmCache(
|
|
@@ -1249,10 +1642,10 @@ async function handleBash(payload) {
|
|
|
1249
1642
|
}
|
|
1250
1643
|
|
|
1251
1644
|
// src/intercept/handlers/session-start.ts
|
|
1252
|
-
import { existsSync as
|
|
1645
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
|
|
1253
1646
|
import { execFile as execFile3 } from "child_process";
|
|
1254
1647
|
import { promisify } from "util";
|
|
1255
|
-
import { basename, dirname as
|
|
1648
|
+
import { basename, dirname as dirname3, join as join5, resolve as resolve2 } from "path";
|
|
1256
1649
|
var execFileAsync = promisify(execFile3);
|
|
1257
1650
|
var MAX_GOD_NODES = 10;
|
|
1258
1651
|
var MAX_LANDMINES_IN_BRIEF = 3;
|
|
@@ -1260,15 +1653,15 @@ function readGitBranch(projectRoot) {
|
|
|
1260
1653
|
try {
|
|
1261
1654
|
let current = resolve2(projectRoot);
|
|
1262
1655
|
for (let depth = 0; depth < 10; depth++) {
|
|
1263
|
-
const headPath =
|
|
1264
|
-
if (
|
|
1265
|
-
const content =
|
|
1656
|
+
const headPath = join5(current, ".git", "HEAD");
|
|
1657
|
+
if (existsSync5(headPath)) {
|
|
1658
|
+
const content = readFileSync2(headPath, "utf-8").trim();
|
|
1266
1659
|
const refMatch = content.match(/^ref:\s+refs\/heads\/(.+)$/);
|
|
1267
1660
|
if (refMatch) return refMatch[1];
|
|
1268
1661
|
if (/^[0-9a-f]{7,40}$/i.test(content)) return "detached";
|
|
1269
1662
|
return null;
|
|
1270
1663
|
}
|
|
1271
|
-
const parent =
|
|
1664
|
+
const parent = dirname3(current);
|
|
1272
1665
|
if (parent === current) return null;
|
|
1273
1666
|
current = parent;
|
|
1274
1667
|
}
|
|
@@ -1547,61 +1940,6 @@ ${result.text}`;
|
|
|
1547
1940
|
return buildSessionContextResponse("UserPromptSubmit", text);
|
|
1548
1941
|
}
|
|
1549
1942
|
|
|
1550
|
-
// src/intelligence/hook-log.ts
|
|
1551
|
-
import {
|
|
1552
|
-
appendFileSync,
|
|
1553
|
-
existsSync as existsSync4,
|
|
1554
|
-
renameSync,
|
|
1555
|
-
statSync as statSync2,
|
|
1556
|
-
readFileSync as readFileSync2
|
|
1557
|
-
} from "fs";
|
|
1558
|
-
import { join as join4 } from "path";
|
|
1559
|
-
var HOOK_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
1560
|
-
var LOG_FILENAME = "hook-log.jsonl";
|
|
1561
|
-
var LOG_ROTATED_FILENAME = "hook-log.jsonl.1";
|
|
1562
|
-
function logHookEvent(projectRoot, entry) {
|
|
1563
|
-
if (!projectRoot) return;
|
|
1564
|
-
try {
|
|
1565
|
-
const logPath = join4(projectRoot, ".engram", LOG_FILENAME);
|
|
1566
|
-
rotateIfNeeded(projectRoot);
|
|
1567
|
-
const line = JSON.stringify({
|
|
1568
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1569
|
-
...entry
|
|
1570
|
-
}) + "\n";
|
|
1571
|
-
appendFileSync(logPath, line);
|
|
1572
|
-
} catch {
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
function rotateIfNeeded(projectRoot) {
|
|
1576
|
-
try {
|
|
1577
|
-
const logPath = join4(projectRoot, ".engram", LOG_FILENAME);
|
|
1578
|
-
if (!existsSync4(logPath)) return;
|
|
1579
|
-
const size = statSync2(logPath).size;
|
|
1580
|
-
if (size < HOOK_LOG_MAX_BYTES) return;
|
|
1581
|
-
const rotatedPath = join4(projectRoot, ".engram", LOG_ROTATED_FILENAME);
|
|
1582
|
-
renameSync(logPath, rotatedPath);
|
|
1583
|
-
} catch {
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
function readHookLog(projectRoot) {
|
|
1587
|
-
try {
|
|
1588
|
-
const logPath = join4(projectRoot, ".engram", LOG_FILENAME);
|
|
1589
|
-
if (!existsSync4(logPath)) return [];
|
|
1590
|
-
const raw = readFileSync2(logPath, "utf-8");
|
|
1591
|
-
const entries = [];
|
|
1592
|
-
for (const line of raw.split("\n")) {
|
|
1593
|
-
if (!line.trim()) continue;
|
|
1594
|
-
try {
|
|
1595
|
-
entries.push(JSON.parse(line));
|
|
1596
|
-
} catch {
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
return entries;
|
|
1600
|
-
} catch {
|
|
1601
|
-
return [];
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
1943
|
// src/intercept/handlers/post-tool.ts
|
|
1606
1944
|
function extractFilePath(toolName, toolInput) {
|
|
1607
1945
|
if (!toolInput) return void 0;
|
|
@@ -1872,7 +2210,7 @@ function extractPreToolDecision(result) {
|
|
|
1872
2210
|
}
|
|
1873
2211
|
|
|
1874
2212
|
// src/watcher.ts
|
|
1875
|
-
import { watch, existsSync as
|
|
2213
|
+
import { watch, existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
1876
2214
|
import { resolve as resolve5, relative as relative3, extname } from "path";
|
|
1877
2215
|
var WATCHABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1878
2216
|
".ts",
|
|
@@ -1908,9 +2246,9 @@ function shouldIgnore(relPath) {
|
|
|
1908
2246
|
async function reindexFile(absPath, projectRoot) {
|
|
1909
2247
|
const ext = extname(absPath).toLowerCase();
|
|
1910
2248
|
if (!WATCHABLE_EXTENSIONS.has(ext)) return 0;
|
|
1911
|
-
if (!
|
|
2249
|
+
if (!existsSync6(absPath)) return 0;
|
|
1912
2250
|
try {
|
|
1913
|
-
if (
|
|
2251
|
+
if (statSync2(absPath).isDirectory()) return 0;
|
|
1914
2252
|
} catch {
|
|
1915
2253
|
return 0;
|
|
1916
2254
|
}
|
|
@@ -1931,7 +2269,7 @@ async function reindexFile(absPath, projectRoot) {
|
|
|
1931
2269
|
function watchProject(projectRoot, options = {}) {
|
|
1932
2270
|
const root = resolve5(projectRoot);
|
|
1933
2271
|
const controller = new AbortController();
|
|
1934
|
-
if (!
|
|
2272
|
+
if (!existsSync6(getDbPath(root))) {
|
|
1935
2273
|
throw new Error(
|
|
1936
2274
|
`engram: no graph found at ${root}. Run 'engram init' first.`
|
|
1937
2275
|
);
|
|
@@ -1973,8 +2311,8 @@ function watchProject(projectRoot, options = {}) {
|
|
|
1973
2311
|
|
|
1974
2312
|
// src/dashboard.ts
|
|
1975
2313
|
import chalk from "chalk";
|
|
1976
|
-
import { existsSync as
|
|
1977
|
-
import { join as
|
|
2314
|
+
import { existsSync as existsSync7, statSync as statSync3 } from "fs";
|
|
2315
|
+
import { join as join7, resolve as resolve6, basename as basename4 } from "path";
|
|
1978
2316
|
|
|
1979
2317
|
// src/intercept/stats.ts
|
|
1980
2318
|
var ESTIMATED_TOKENS_PER_READ_DENY = 1200;
|
|
@@ -2188,9 +2526,9 @@ function startDashboard(projectRoot, options = {}) {
|
|
|
2188
2526
|
const tick = () => {
|
|
2189
2527
|
if (controller.signal.aborted) return;
|
|
2190
2528
|
try {
|
|
2191
|
-
const logPath =
|
|
2192
|
-
if (
|
|
2193
|
-
const currentSize =
|
|
2529
|
+
const logPath = join7(root, ".engram", "hook-log.jsonl");
|
|
2530
|
+
if (existsSync7(logPath)) {
|
|
2531
|
+
const currentSize = statSync3(logPath).size;
|
|
2194
2532
|
if (currentSize !== lastSize) {
|
|
2195
2533
|
cachedEntries = readHookLog(root);
|
|
2196
2534
|
lastSize = currentSize;
|
|
@@ -2397,15 +2735,123 @@ function formatInstallDiff(before, after) {
|
|
|
2397
2735
|
return lines.length > 0 ? lines.join("\n") : "(no changes)";
|
|
2398
2736
|
}
|
|
2399
2737
|
|
|
2738
|
+
// src/intercept/component-status.ts
|
|
2739
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
2740
|
+
import { join as join8 } from "path";
|
|
2741
|
+
function statusPath(projectRoot) {
|
|
2742
|
+
return join8(projectRoot, ".engram", "component-status.json");
|
|
2743
|
+
}
|
|
2744
|
+
function readCachedStatus(projectRoot) {
|
|
2745
|
+
const path2 = statusPath(projectRoot);
|
|
2746
|
+
if (!existsSync8(path2)) return null;
|
|
2747
|
+
try {
|
|
2748
|
+
const raw = JSON.parse(readFileSync4(path2, "utf-8"));
|
|
2749
|
+
if (Date.now() - raw.generatedAt > 3e4) return null;
|
|
2750
|
+
return raw;
|
|
2751
|
+
} catch {
|
|
2752
|
+
return null;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
function checkHttp(projectRoot) {
|
|
2756
|
+
return existsSync8(join8(projectRoot, ".engram", "http-server.pid"));
|
|
2757
|
+
}
|
|
2758
|
+
function checkLsp(projectRoot) {
|
|
2759
|
+
if (existsSync8(join8(projectRoot, ".engram", "lsp-available"))) return true;
|
|
2760
|
+
const candidates = [
|
|
2761
|
+
"/tmp/tsserver.sock",
|
|
2762
|
+
"/tmp/typescript-language-server.sock"
|
|
2763
|
+
];
|
|
2764
|
+
return candidates.some((c) => existsSync8(c));
|
|
2765
|
+
}
|
|
2766
|
+
function checkAst(projectRoot) {
|
|
2767
|
+
const grammarsDir = join8(projectRoot, "node_modules", "web-tree-sitter");
|
|
2768
|
+
return existsSync8(grammarsDir);
|
|
2769
|
+
}
|
|
2770
|
+
function countIdeAdapters(projectRoot) {
|
|
2771
|
+
let count = 0;
|
|
2772
|
+
if (existsSync8(join8(projectRoot, ".cursor", "rules", "engram-context.mdc"))) {
|
|
2773
|
+
count += 1;
|
|
2774
|
+
}
|
|
2775
|
+
const continueConfig = join8(
|
|
2776
|
+
process.env.HOME ?? "",
|
|
2777
|
+
".continue",
|
|
2778
|
+
"config.json"
|
|
2779
|
+
);
|
|
2780
|
+
if (existsSync8(continueConfig)) {
|
|
2781
|
+
try {
|
|
2782
|
+
const cfg = readFileSync4(continueConfig, "utf-8");
|
|
2783
|
+
if (cfg.includes("engram")) count += 1;
|
|
2784
|
+
} catch {
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
const zedSettings = join8(
|
|
2788
|
+
process.env.HOME ?? "",
|
|
2789
|
+
".config",
|
|
2790
|
+
"zed",
|
|
2791
|
+
"settings.json"
|
|
2792
|
+
);
|
|
2793
|
+
if (existsSync8(zedSettings)) {
|
|
2794
|
+
try {
|
|
2795
|
+
const cfg = readFileSync4(zedSettings, "utf-8");
|
|
2796
|
+
if (cfg.includes("engram")) count += 1;
|
|
2797
|
+
} catch {
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
const claudeSettings = join8(projectRoot, ".claude", "settings.local.json");
|
|
2801
|
+
if (existsSync8(claudeSettings)) {
|
|
2802
|
+
try {
|
|
2803
|
+
const cfg = readFileSync4(claudeSettings, "utf-8");
|
|
2804
|
+
if (cfg.includes("engram")) count += 1;
|
|
2805
|
+
} catch {
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
return count;
|
|
2809
|
+
}
|
|
2810
|
+
function refreshComponentStatus(projectRoot) {
|
|
2811
|
+
const now = Date.now();
|
|
2812
|
+
const components = [
|
|
2813
|
+
{ name: "http", available: checkHttp(projectRoot), checkedAt: now },
|
|
2814
|
+
{ name: "lsp", available: checkLsp(projectRoot), checkedAt: now },
|
|
2815
|
+
{ name: "ast", available: checkAst(projectRoot), checkedAt: now }
|
|
2816
|
+
];
|
|
2817
|
+
const ideCount = countIdeAdapters(projectRoot);
|
|
2818
|
+
const report = {
|
|
2819
|
+
components,
|
|
2820
|
+
ideCount,
|
|
2821
|
+
generatedAt: now
|
|
2822
|
+
};
|
|
2823
|
+
try {
|
|
2824
|
+
writeFileSync(statusPath(projectRoot), JSON.stringify(report), "utf-8");
|
|
2825
|
+
} catch {
|
|
2826
|
+
}
|
|
2827
|
+
return report;
|
|
2828
|
+
}
|
|
2829
|
+
function getComponentStatus(projectRoot) {
|
|
2830
|
+
const cached = readCachedStatus(projectRoot);
|
|
2831
|
+
if (cached) return cached;
|
|
2832
|
+
return refreshComponentStatus(projectRoot);
|
|
2833
|
+
}
|
|
2834
|
+
function formatHudStatus(report) {
|
|
2835
|
+
const parts = [];
|
|
2836
|
+
for (const c of report.components) {
|
|
2837
|
+
const icon = c.available ? "\u2713" : "\u2717";
|
|
2838
|
+
parts.push(`${c.name.toUpperCase()} ${icon}`);
|
|
2839
|
+
}
|
|
2840
|
+
if (report.ideCount > 0) {
|
|
2841
|
+
parts.push(`${report.ideCount} IDE${report.ideCount > 1 ? "s" : ""}`);
|
|
2842
|
+
}
|
|
2843
|
+
return parts.join(" | ");
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2400
2846
|
// src/intercept/memory-md.ts
|
|
2401
2847
|
import {
|
|
2402
|
-
existsSync as
|
|
2403
|
-
readFileSync as
|
|
2404
|
-
writeFileSync,
|
|
2405
|
-
renameSync
|
|
2406
|
-
statSync as
|
|
2848
|
+
existsSync as existsSync9,
|
|
2849
|
+
readFileSync as readFileSync5,
|
|
2850
|
+
writeFileSync as writeFileSync2,
|
|
2851
|
+
renameSync,
|
|
2852
|
+
statSync as statSync4
|
|
2407
2853
|
} from "fs";
|
|
2408
|
-
import { join as
|
|
2854
|
+
import { join as join9 } from "path";
|
|
2409
2855
|
var ENGRAM_MARKER_START = "<!-- engram:structural-facts:start -->";
|
|
2410
2856
|
var ENGRAM_MARKER_END = "<!-- engram:structural-facts:end -->";
|
|
2411
2857
|
var MAX_MEMORY_FILE_BYTES = 1e6;
|
|
@@ -2476,20 +2922,20 @@ function writeEngramSectionToMemoryMd(projectRoot, engramSection) {
|
|
|
2476
2922
|
if (engramSection.length > MAX_ENGRAM_SECTION_BYTES) {
|
|
2477
2923
|
return false;
|
|
2478
2924
|
}
|
|
2479
|
-
const memoryPath =
|
|
2925
|
+
const memoryPath = join9(projectRoot, "MEMORY.md");
|
|
2480
2926
|
try {
|
|
2481
2927
|
let existing = "";
|
|
2482
|
-
if (
|
|
2483
|
-
const st =
|
|
2928
|
+
if (existsSync9(memoryPath)) {
|
|
2929
|
+
const st = statSync4(memoryPath);
|
|
2484
2930
|
if (st.size > MAX_MEMORY_FILE_BYTES) {
|
|
2485
2931
|
return false;
|
|
2486
2932
|
}
|
|
2487
|
-
existing =
|
|
2933
|
+
existing = readFileSync5(memoryPath, "utf-8");
|
|
2488
2934
|
}
|
|
2489
2935
|
const updated = upsertEngramSection(existing, engramSection);
|
|
2490
2936
|
const tmpPath = memoryPath + ".engram-tmp";
|
|
2491
|
-
|
|
2492
|
-
|
|
2937
|
+
writeFileSync2(tmpPath, updated);
|
|
2938
|
+
renameSync(tmpPath, memoryPath);
|
|
2493
2939
|
return true;
|
|
2494
2940
|
} catch {
|
|
2495
2941
|
return false;
|
|
@@ -2498,9 +2944,9 @@ function writeEngramSectionToMemoryMd(projectRoot, engramSection) {
|
|
|
2498
2944
|
|
|
2499
2945
|
// src/cli.ts
|
|
2500
2946
|
import { basename as basename5 } from "path";
|
|
2501
|
-
import { createRequire } from "module";
|
|
2502
|
-
var
|
|
2503
|
-
var { version: PKG_VERSION } =
|
|
2947
|
+
import { createRequire as createRequire2 } from "module";
|
|
2948
|
+
var require3 = createRequire2(import.meta.url);
|
|
2949
|
+
var { version: PKG_VERSION } = require3("../package.json");
|
|
2504
2950
|
var program = new Command();
|
|
2505
2951
|
program.name("engram").description(
|
|
2506
2952
|
"Context as infra for AI coding tools \u2014 hook-based Read/Edit interception + structural graph summaries"
|
|
@@ -2508,7 +2954,7 @@ program.name("engram").description(
|
|
|
2508
2954
|
program.command("init").description("Scan codebase and build knowledge graph (zero LLM cost)").argument("[path]", "Project directory", ".").option(
|
|
2509
2955
|
"--with-skills [dir]",
|
|
2510
2956
|
"Also index Claude Code skills from ~/.claude/skills/ or a given path"
|
|
2511
|
-
).action(async (projectPath, opts) => {
|
|
2957
|
+
).option("--from-ccs", "Import .context/index.md (CCS) into graph after init").action(async (projectPath, opts) => {
|
|
2512
2958
|
console.log(chalk2.dim("\u{1F50D} Scanning codebase..."));
|
|
2513
2959
|
const result = await init(projectPath, {
|
|
2514
2960
|
withSkills: opts.withSkills
|
|
@@ -2537,9 +2983,9 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2537
2983
|
console.log(chalk2.green("\n\u2705 Ready. Your AI now has persistent memory."));
|
|
2538
2984
|
console.log(chalk2.dim(" Graph stored in .engram/graph.db"));
|
|
2539
2985
|
const resolvedProject = pathResolve(projectPath);
|
|
2540
|
-
const localSettings =
|
|
2541
|
-
const projectSettings =
|
|
2542
|
-
const hasHooks =
|
|
2986
|
+
const localSettings = join10(resolvedProject, ".claude", "settings.local.json");
|
|
2987
|
+
const projectSettings = join10(resolvedProject, ".claude", "settings.json");
|
|
2988
|
+
const hasHooks = existsSync10(localSettings) && readFileSync6(localSettings, "utf-8").includes("engram intercept") || existsSync10(projectSettings) && readFileSync6(projectSettings, "utf-8").includes("engram intercept");
|
|
2543
2989
|
if (!hasHooks) {
|
|
2544
2990
|
console.log(
|
|
2545
2991
|
chalk2.yellow("\n\u{1F4A1} Next step: ") + chalk2.white("engram install-hook") + chalk2.dim(
|
|
@@ -2552,6 +2998,20 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2552
2998
|
)
|
|
2553
2999
|
);
|
|
2554
3000
|
}
|
|
3001
|
+
if (opts.fromCcs) {
|
|
3002
|
+
const { importCcs } = await import("./importer-MCNFMV5O.js");
|
|
3003
|
+
const resolvedProjectPath = pathResolve(projectPath);
|
|
3004
|
+
const ccsResult = await importCcs(resolvedProjectPath);
|
|
3005
|
+
if (ccsResult.nodesCreated > 0) {
|
|
3006
|
+
console.log(
|
|
3007
|
+
chalk2.cyan(
|
|
3008
|
+
` ${ccsResult.nodesCreated} nodes imported from .context/index.md`
|
|
3009
|
+
)
|
|
3010
|
+
);
|
|
3011
|
+
} else {
|
|
3012
|
+
console.log(chalk2.dim(" --from-ccs: no .context/index.md found, skipping"));
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
2555
3015
|
});
|
|
2556
3016
|
program.command("watch").description("Watch project for file changes and re-index incrementally").argument("[path]", "Project directory", ".").action(async (projectPath) => {
|
|
2557
3017
|
const resolvedPath = pathResolve(projectPath);
|
|
@@ -2581,8 +3041,8 @@ program.command("watch").description("Watch project for file changes and re-inde
|
|
|
2581
3041
|
});
|
|
2582
3042
|
program.command("dashboard").alias("hud").description("Live terminal dashboard showing hook activity and token savings").argument("[path]", "Project directory", ".").action(async (projectPath) => {
|
|
2583
3043
|
const resolvedPath = pathResolve(projectPath);
|
|
2584
|
-
const dbPath =
|
|
2585
|
-
if (!
|
|
3044
|
+
const dbPath = join10(resolvedPath, ".engram", "graph.db");
|
|
3045
|
+
if (!existsSync10(dbPath)) {
|
|
2586
3046
|
console.error(
|
|
2587
3047
|
chalk2.red("No engram graph found at ") + chalk2.white(resolvedPath)
|
|
2588
3048
|
);
|
|
@@ -2602,11 +3062,11 @@ program.command("hud-label").description("Output JSON label for Claude HUD --ext
|
|
|
2602
3062
|
let resolvedPath = pathResolve(projectPath);
|
|
2603
3063
|
let found = false;
|
|
2604
3064
|
for (let depth = 0; depth < 20; depth++) {
|
|
2605
|
-
if (
|
|
3065
|
+
if (existsSync10(join10(resolvedPath, ".engram", "graph.db"))) {
|
|
2606
3066
|
found = true;
|
|
2607
3067
|
break;
|
|
2608
3068
|
}
|
|
2609
|
-
const parent =
|
|
3069
|
+
const parent = dirname4(resolvedPath);
|
|
2610
3070
|
if (parent === resolvedPath) break;
|
|
2611
3071
|
resolvedPath = parent;
|
|
2612
3072
|
}
|
|
@@ -2614,8 +3074,8 @@ program.command("hud-label").description("Output JSON label for Claude HUD --ext
|
|
|
2614
3074
|
console.log('{"label":""}');
|
|
2615
3075
|
return;
|
|
2616
3076
|
}
|
|
2617
|
-
const logPath =
|
|
2618
|
-
if (!
|
|
3077
|
+
const logPath = join10(resolvedPath, ".engram", "hook-log.jsonl");
|
|
3078
|
+
if (!existsSync10(logPath)) {
|
|
2619
3079
|
console.log('{"label":"\u26A1engram \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 ready"}');
|
|
2620
3080
|
return;
|
|
2621
3081
|
}
|
|
@@ -2639,7 +3099,10 @@ program.command("hud-label").description("Output JSON label for Claude HUD --ext
|
|
|
2639
3099
|
if (filled > barWidth) filled = barWidth;
|
|
2640
3100
|
if (denied > 0 && filled === 0) filled = 1;
|
|
2641
3101
|
const bar2 = "\u25B0".repeat(filled) + "\u25B1".repeat(barWidth - filled);
|
|
2642
|
-
|
|
3102
|
+
const status2 = getComponentStatus(resolvedPath);
|
|
3103
|
+
const statusSuffix = formatHudStatus(status2);
|
|
3104
|
+
const label = statusSuffix ? `\u26A1engram ${formatted} saved ${bar2} ${hitRate}% | ${statusSuffix}` : `\u26A1engram ${formatted} saved ${bar2} ${hitRate}%`;
|
|
3105
|
+
console.log(JSON.stringify({ label }));
|
|
2643
3106
|
} catch {
|
|
2644
3107
|
console.log('{"label":"\u26A1engram"}');
|
|
2645
3108
|
}
|
|
@@ -2765,15 +3228,66 @@ program.command("gen").description("Generate CLAUDE.md / .cursorrules section fr
|
|
|
2765
3228
|
);
|
|
2766
3229
|
}
|
|
2767
3230
|
);
|
|
3231
|
+
program.command("gen-mdc").description("Generate .cursor/rules/engram-context.mdc from knowledge graph").option("-p, --project <path>", "Project directory", ".").option("--watch", "Regenerate on graph changes").action(async (opts) => {
|
|
3232
|
+
const { generateCursorMdc } = await import("./cursor-mdc-HWVUZUZH.js");
|
|
3233
|
+
const result = await generateCursorMdc(opts.project);
|
|
3234
|
+
console.log(
|
|
3235
|
+
chalk2.green(
|
|
3236
|
+
`\u2705 Generated ${result.filePath} (${result.sections} sections, ${result.nodes} nodes)`
|
|
3237
|
+
)
|
|
3238
|
+
);
|
|
3239
|
+
if (opts.watch) {
|
|
3240
|
+
watchProject(pathResolve(opts.project), {
|
|
3241
|
+
onReindex: async () => {
|
|
3242
|
+
const r = await generateCursorMdc(opts.project);
|
|
3243
|
+
console.log(chalk2.dim(` \u21BB Regenerated MDC (${r.nodes} nodes)`));
|
|
3244
|
+
},
|
|
3245
|
+
onError: (err) => console.error(chalk2.red(err.message)),
|
|
3246
|
+
onReady: () => console.log(chalk2.dim(" Watching for changes..."))
|
|
3247
|
+
});
|
|
3248
|
+
await new Promise(() => {
|
|
3249
|
+
});
|
|
3250
|
+
}
|
|
3251
|
+
});
|
|
3252
|
+
program.command("gen-ccs").description("Export knowledge graph as .context/index.md (CCS format)").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3253
|
+
const { exportCcs } = await import("./exporter-A3VSLS4U.js");
|
|
3254
|
+
const result = await exportCcs(pathResolve(opts.project));
|
|
3255
|
+
console.log(
|
|
3256
|
+
chalk2.green(
|
|
3257
|
+
`\u2705 Generated ${result.filePath} (${result.sectionsWritten} sections, ${result.nodesExported} nodes)`
|
|
3258
|
+
)
|
|
3259
|
+
);
|
|
3260
|
+
});
|
|
3261
|
+
program.command("gen-aider").description("Generate .aider-context.md from knowledge graph").option("-p, --project <path>", "Project directory", ".").option("--watch", "Regenerate on graph changes").action(async (opts) => {
|
|
3262
|
+
const { generateAiderContext } = await import("./aider-context-TNGSXMVY.js");
|
|
3263
|
+
const result = await generateAiderContext(pathResolve(opts.project));
|
|
3264
|
+
console.log(
|
|
3265
|
+
chalk2.green(
|
|
3266
|
+
`\u2705 Generated ${result.filePath} (${result.sections} sections, ${result.nodes} nodes)`
|
|
3267
|
+
)
|
|
3268
|
+
);
|
|
3269
|
+
if (opts.watch) {
|
|
3270
|
+
watchProject(pathResolve(opts.project), {
|
|
3271
|
+
onReindex: async () => {
|
|
3272
|
+
const r = await generateAiderContext(opts.project);
|
|
3273
|
+
console.log(chalk2.dim(` \u21BB Regenerated .aider-context.md (${r.nodes} nodes)`));
|
|
3274
|
+
},
|
|
3275
|
+
onError: (err) => console.error(chalk2.red(err.message)),
|
|
3276
|
+
onReady: () => console.log(chalk2.dim(" Watching for changes..."))
|
|
3277
|
+
});
|
|
3278
|
+
await new Promise(() => {
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
});
|
|
2768
3282
|
function resolveSettingsPath(scope, projectPath) {
|
|
2769
3283
|
const absProject = pathResolve(projectPath);
|
|
2770
3284
|
switch (scope) {
|
|
2771
3285
|
case "local":
|
|
2772
|
-
return
|
|
3286
|
+
return join10(absProject, ".claude", "settings.local.json");
|
|
2773
3287
|
case "project":
|
|
2774
|
-
return
|
|
3288
|
+
return join10(absProject, ".claude", "settings.json");
|
|
2775
3289
|
case "user":
|
|
2776
|
-
return
|
|
3290
|
+
return join10(homedir(), ".claude", "settings.json");
|
|
2777
3291
|
default:
|
|
2778
3292
|
return null;
|
|
2779
3293
|
}
|
|
@@ -2867,9 +3381,9 @@ program.command("install-hook").description("Install engram hook entries into Cl
|
|
|
2867
3381
|
process.exit(1);
|
|
2868
3382
|
}
|
|
2869
3383
|
let existing = {};
|
|
2870
|
-
if (
|
|
3384
|
+
if (existsSync10(settingsPath)) {
|
|
2871
3385
|
try {
|
|
2872
|
-
const raw =
|
|
3386
|
+
const raw = readFileSync6(settingsPath, "utf-8");
|
|
2873
3387
|
existing = raw.trim() ? JSON.parse(raw) : {};
|
|
2874
3388
|
} catch (err) {
|
|
2875
3389
|
console.error(
|
|
@@ -2914,18 +3428,18 @@ program.command("install-hook").description("Install engram hook entries into Cl
|
|
|
2914
3428
|
return;
|
|
2915
3429
|
}
|
|
2916
3430
|
try {
|
|
2917
|
-
mkdirSync(
|
|
2918
|
-
if (
|
|
3431
|
+
mkdirSync(dirname4(settingsPath), { recursive: true });
|
|
3432
|
+
if (existsSync10(settingsPath)) {
|
|
2919
3433
|
const backupPath = `${settingsPath}.engram-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
2920
3434
|
copyFileSync(settingsPath, backupPath);
|
|
2921
3435
|
console.log(chalk2.dim(` Backup: ${backupPath}`));
|
|
2922
3436
|
}
|
|
2923
3437
|
const tmpPath = settingsPath + ".engram-tmp";
|
|
2924
|
-
|
|
3438
|
+
writeFileSync3(
|
|
2925
3439
|
tmpPath,
|
|
2926
3440
|
JSON.stringify(result.updated, null, 2) + "\n"
|
|
2927
3441
|
);
|
|
2928
|
-
|
|
3442
|
+
renameSync2(tmpPath, settingsPath);
|
|
2929
3443
|
} catch (err) {
|
|
2930
3444
|
console.error(
|
|
2931
3445
|
chalk2.red(`
|
|
@@ -2966,7 +3480,7 @@ program.command("uninstall-hook").description("Remove engram hook entries from C
|
|
|
2966
3480
|
console.error(chalk2.red(`Unknown scope: ${opts.scope}`));
|
|
2967
3481
|
process.exit(1);
|
|
2968
3482
|
}
|
|
2969
|
-
if (!
|
|
3483
|
+
if (!existsSync10(settingsPath)) {
|
|
2970
3484
|
console.log(
|
|
2971
3485
|
chalk2.yellow(`No settings file at ${settingsPath} \u2014 nothing to remove.`)
|
|
2972
3486
|
);
|
|
@@ -2974,7 +3488,7 @@ program.command("uninstall-hook").description("Remove engram hook entries from C
|
|
|
2974
3488
|
}
|
|
2975
3489
|
let existing;
|
|
2976
3490
|
try {
|
|
2977
|
-
const raw =
|
|
3491
|
+
const raw = readFileSync6(settingsPath, "utf-8");
|
|
2978
3492
|
existing = raw.trim() ? JSON.parse(raw) : {};
|
|
2979
3493
|
} catch (err) {
|
|
2980
3494
|
console.error(
|
|
@@ -2994,8 +3508,8 @@ program.command("uninstall-hook").description("Remove engram hook entries from C
|
|
|
2994
3508
|
const backupPath = `${settingsPath}.engram-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
2995
3509
|
copyFileSync(settingsPath, backupPath);
|
|
2996
3510
|
const tmpPath = settingsPath + ".engram-tmp";
|
|
2997
|
-
|
|
2998
|
-
|
|
3511
|
+
writeFileSync3(tmpPath, JSON.stringify(result.updated, null, 2) + "\n");
|
|
3512
|
+
renameSync2(tmpPath, settingsPath);
|
|
2999
3513
|
if (result.removed.length > 0) {
|
|
3000
3514
|
console.log(
|
|
3001
3515
|
chalk2.green(
|
|
@@ -3089,9 +3603,9 @@ program.command("hook-disable").description("Disable engram hooks via kill switc
|
|
|
3089
3603
|
console.error(chalk2.dim("Run 'engram init' first."));
|
|
3090
3604
|
process.exit(1);
|
|
3091
3605
|
}
|
|
3092
|
-
const flagPath =
|
|
3606
|
+
const flagPath = join10(projectRoot, ".engram", "hook-disabled");
|
|
3093
3607
|
try {
|
|
3094
|
-
|
|
3608
|
+
writeFileSync3(flagPath, (/* @__PURE__ */ new Date()).toISOString());
|
|
3095
3609
|
console.log(
|
|
3096
3610
|
chalk2.green(`\u2705 engram hooks disabled for ${projectRoot}`)
|
|
3097
3611
|
);
|
|
@@ -3113,8 +3627,8 @@ program.command("hook-enable").description("Re-enable engram hooks (remove kill
|
|
|
3113
3627
|
console.error(chalk2.red(`Not an engram project: ${absProject}`));
|
|
3114
3628
|
process.exit(1);
|
|
3115
3629
|
}
|
|
3116
|
-
const flagPath =
|
|
3117
|
-
if (!
|
|
3630
|
+
const flagPath = join10(projectRoot, ".engram", "hook-disabled");
|
|
3631
|
+
if (!existsSync10(flagPath)) {
|
|
3118
3632
|
console.log(
|
|
3119
3633
|
chalk2.yellow(`engram hooks already enabled for ${projectRoot}`)
|
|
3120
3634
|
);
|
|
@@ -3156,9 +3670,9 @@ program.command("memory-sync").description(
|
|
|
3156
3670
|
}
|
|
3157
3671
|
let branch = null;
|
|
3158
3672
|
try {
|
|
3159
|
-
const headPath =
|
|
3160
|
-
if (
|
|
3161
|
-
const content =
|
|
3673
|
+
const headPath = join10(projectRoot, ".git", "HEAD");
|
|
3674
|
+
if (existsSync10(headPath)) {
|
|
3675
|
+
const content = readFileSync6(headPath, "utf-8").trim();
|
|
3162
3676
|
const m = content.match(/^ref:\s+refs\/heads\/(.+)$/);
|
|
3163
3677
|
if (m) branch = m[1];
|
|
3164
3678
|
}
|
|
@@ -3184,7 +3698,7 @@ program.command("memory-sync").description(
|
|
|
3184
3698
|
\u{1F4DD} engram memory-sync`)
|
|
3185
3699
|
);
|
|
3186
3700
|
console.log(
|
|
3187
|
-
chalk2.dim(` Target: ${
|
|
3701
|
+
chalk2.dim(` Target: ${join10(projectRoot, "MEMORY.md")}`)
|
|
3188
3702
|
);
|
|
3189
3703
|
if (opts.dryRun) {
|
|
3190
3704
|
console.log(chalk2.cyan("\n Section to write (dry-run):\n"));
|
|
@@ -3218,4 +3732,107 @@ program.command("memory-sync").description(
|
|
|
3218
3732
|
);
|
|
3219
3733
|
}
|
|
3220
3734
|
);
|
|
3735
|
+
program.command("stress-test").description("Run stress tests: memory, concurrency, large-graph, hook-log replay").option("--reads <n>", "Rapid-reads test: call resolveRichPacket N times", parseInt).option("--providers", "Concurrency test: 50 parallel resolveRichPacket calls").option("--large-graph", "Large-graph test: insert N synthetic nodes and query").option("--nodes <n>", "Node count for --large-graph (default 1000)", parseInt).option("--replay <path>", "Hook-log replay: path to hook-log.jsonl").option("--limit <n>", "Entry limit for --replay (default 500)", parseInt).action(async (opts) => {
|
|
3736
|
+
const { execFileSync: execFileSync2 } = await import("child_process");
|
|
3737
|
+
const args = ["bench/stress-test.ts"];
|
|
3738
|
+
if (opts.reads) args.push("--reads", String(opts.reads));
|
|
3739
|
+
if (opts.providers) args.push("--providers");
|
|
3740
|
+
if (opts.largeGraph) args.push("--large-graph");
|
|
3741
|
+
if (opts.nodes) args.push("--nodes", String(opts.nodes));
|
|
3742
|
+
if (opts.replay) args.push("--replay", opts.replay);
|
|
3743
|
+
if (opts.limit) args.push("--limit", String(opts.limit));
|
|
3744
|
+
try {
|
|
3745
|
+
execFileSync2("npx", ["tsx", ...args], { stdio: "inherit", cwd: join10(dirname4(import.meta.url.replace("file://", "")), "..") });
|
|
3746
|
+
} catch {
|
|
3747
|
+
process.exit(1);
|
|
3748
|
+
}
|
|
3749
|
+
});
|
|
3750
|
+
program.command("server").description("Start engram HTTP REST server (binds to 127.0.0.1 only)").option("--http", "Enable HTTP server (default)").option("--port <port>", "HTTP port", "7337").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3751
|
+
const { startHttpServer } = await import("./server-I3C74ZLB.js");
|
|
3752
|
+
await startHttpServer(pathResolve(opts.project), parseInt(opts.port, 10));
|
|
3753
|
+
});
|
|
3754
|
+
program.command("context-server").description("Start Zed-compatible context server (JSON-RPC over stdio)").action(async () => {
|
|
3755
|
+
const { execFileSync: execFileSync2 } = await import("child_process");
|
|
3756
|
+
try {
|
|
3757
|
+
execFileSync2("npx", ["tsx", "adapters/zed/index.ts"], {
|
|
3758
|
+
stdio: "inherit",
|
|
3759
|
+
cwd: join10(dirname4(import.meta.url.replace("file://", "")), "..")
|
|
3760
|
+
});
|
|
3761
|
+
} catch {
|
|
3762
|
+
process.exit(1);
|
|
3763
|
+
}
|
|
3764
|
+
});
|
|
3765
|
+
program.command("tune").description("Analyze hook-log and propose provider config changes").option("-p, --project <path>", "Project directory", ".").option("--dry-run", "Show proposed changes without applying (default)").option("--apply", "Apply proposed changes to .engram/config.json").action(async (opts) => {
|
|
3766
|
+
const { analyzeTuning, applyTuning } = await import("./tuner-2LVIEE5V.js");
|
|
3767
|
+
const proposal = analyzeTuning(pathResolve(opts.project));
|
|
3768
|
+
if (proposal.changes.length === 0) {
|
|
3769
|
+
console.log(
|
|
3770
|
+
chalk2.dim(
|
|
3771
|
+
`Analyzed ${proposal.entriesAnalyzed} entries (${proposal.daysSpanned} days) \u2014 no changes suggested.`
|
|
3772
|
+
)
|
|
3773
|
+
);
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
console.log(
|
|
3777
|
+
chalk2.bold(
|
|
3778
|
+
`Analyzing ${proposal.entriesAnalyzed} hook-log entries from last ${proposal.daysSpanned} days...
|
|
3779
|
+
`
|
|
3780
|
+
)
|
|
3781
|
+
);
|
|
3782
|
+
console.log(chalk2.bold("Proposed changes:"));
|
|
3783
|
+
for (const c of proposal.changes) {
|
|
3784
|
+
console.log(
|
|
3785
|
+
` ${c.field}: ${chalk2.red(String(c.current))} \u2192 ${chalk2.green(String(c.proposed))} \u2014 ${chalk2.dim(c.reason)}`
|
|
3786
|
+
);
|
|
3787
|
+
}
|
|
3788
|
+
if (opts.apply) {
|
|
3789
|
+
applyTuning(pathResolve(opts.project), proposal);
|
|
3790
|
+
console.log(chalk2.green("\n\u2705 Changes applied to .engram/config.json"));
|
|
3791
|
+
} else {
|
|
3792
|
+
console.log(chalk2.dim("\nRun with --apply to write these changes."));
|
|
3793
|
+
}
|
|
3794
|
+
});
|
|
3795
|
+
var dbCmd = program.command("db").description("Database management");
|
|
3796
|
+
dbCmd.command("status").description("Show schema version and migration status").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3797
|
+
const { getStore: getStore2 } = await import("./core-77MHT3QV.js");
|
|
3798
|
+
const { CURRENT_SCHEMA_VERSION, getSchemaVersion } = await import("./migrate-5ZJWF2HD.js");
|
|
3799
|
+
const store = await getStore2(pathResolve(opts.project));
|
|
3800
|
+
try {
|
|
3801
|
+
const version = getSchemaVersion(store.db);
|
|
3802
|
+
const pending = CURRENT_SCHEMA_VERSION - version;
|
|
3803
|
+
console.log(`Schema version: ${version} (current: ${CURRENT_SCHEMA_VERSION})`);
|
|
3804
|
+
if (pending > 0) {
|
|
3805
|
+
console.log(chalk2.yellow(`${pending} pending migration(s). Run 'engram db migrate' to update.`));
|
|
3806
|
+
} else {
|
|
3807
|
+
console.log(chalk2.green("Up to date."));
|
|
3808
|
+
}
|
|
3809
|
+
} finally {
|
|
3810
|
+
store.close();
|
|
3811
|
+
}
|
|
3812
|
+
});
|
|
3813
|
+
dbCmd.command("migrate").description("Run pending schema migrations").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3814
|
+
const { getStore: getStore2 } = await import("./core-77MHT3QV.js");
|
|
3815
|
+
const { runMigrations } = await import("./migrate-5ZJWF2HD.js");
|
|
3816
|
+
const store = await getStore2(pathResolve(opts.project));
|
|
3817
|
+
try {
|
|
3818
|
+
const dbPath = join10(pathResolve(opts.project), ".engram", "graph.db");
|
|
3819
|
+
const result = runMigrations(
|
|
3820
|
+
store.db,
|
|
3821
|
+
dbPath
|
|
3822
|
+
);
|
|
3823
|
+
store.save();
|
|
3824
|
+
if (result.migrationsRun === 0) {
|
|
3825
|
+
console.log(chalk2.green("Already up to date."));
|
|
3826
|
+
} else {
|
|
3827
|
+
console.log(
|
|
3828
|
+
chalk2.green(`Migrated v${result.fromVersion} \u2192 v${result.toVersion} (${result.migrationsRun} migrations)`)
|
|
3829
|
+
);
|
|
3830
|
+
if (result.backedUp) {
|
|
3831
|
+
console.log(chalk2.dim("Backup created."));
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
} finally {
|
|
3835
|
+
store.close();
|
|
3836
|
+
}
|
|
3837
|
+
});
|
|
3221
3838
|
program.parse();
|