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