engramx 1.0.1 → 2.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 +150 -0
- package/README.md +67 -7
- package/dist/{aider-context-TNGSXMVY.js → aider-context-BC5R2ZTA.js} +1 -1
- package/dist/cache-AK6CF3BC.js +10 -0
- package/dist/chunk-22INHMKB.js +31 -0
- package/dist/chunk-533LR4I7.js +220 -0
- package/dist/{chunk-QOG4K427.js → chunk-C6GBUOAL.js} +1 -1
- package/dist/chunk-CIQQ5Y3S.js +338 -0
- package/dist/chunk-KL6NSPVA.js +59 -0
- package/dist/{chunk-SBHGK5WA.js → chunk-PEH54LYC.js} +85 -3
- package/dist/{chunk-6SFMVYUN.js → chunk-SJT7VS2G.js} +127 -23
- package/dist/cli.js +383 -258
- package/dist/{core-77MHT3QV.js → core-6IY5L6II.js} +2 -2
- package/dist/{cursor-mdc-HWVUZUZH.js → cursor-mdc-GJ7E5LDD.js} +1 -1
- package/dist/{exporter-A3VSLS4U.js → exporter-GWU2GF23.js} +1 -1
- package/dist/grammars/tree-sitter-go.wasm +0 -0
- package/dist/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/grammars/tree-sitter-python.wasm +0 -0
- package/dist/grammars/tree-sitter-rust.wasm +0 -0
- package/dist/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/{importer-LU2YFZDY.js → importer-V62NGZRK.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/{migrate-5ZJWF2HD.js → migrate-UKCO6BUU.js} +3 -1
- package/dist/plugin-loader-STTGYIL5.js +106 -0
- package/dist/serve.js +2 -2
- package/dist/server-6AOI7NQP.js +1370 -0
- package/dist/{tuner-2LVIEE5V.js → tuner-KFNNGKG3.js} +4 -2
- package/dist/windsurf-rules-C7SVDHBL.js +59 -0
- package/package.json +4 -3
- package/dist/chunk-CEAANHHX.js +0 -88
- package/dist/server-I3C74ZLB.js +0 -193
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ESTIMATED_TOKENS_PER_READ_DENY,
|
|
4
|
+
formatHudStatus,
|
|
5
|
+
formatStatsSummary,
|
|
6
|
+
getComponentStatus,
|
|
7
|
+
summarizeHookLog
|
|
8
|
+
} from "./chunk-533LR4I7.js";
|
|
9
|
+
import {
|
|
10
|
+
readConfig
|
|
11
|
+
} from "./chunk-22INHMKB.js";
|
|
2
12
|
import {
|
|
3
13
|
logHookEvent,
|
|
4
|
-
readConfig,
|
|
5
14
|
readHookLog
|
|
6
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-KL6NSPVA.js";
|
|
7
16
|
import {
|
|
8
17
|
autogen,
|
|
9
18
|
install,
|
|
10
19
|
status,
|
|
11
20
|
uninstall
|
|
12
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-C6GBUOAL.js";
|
|
13
22
|
import {
|
|
14
23
|
benchmark,
|
|
15
24
|
computeKeywordIDF,
|
|
@@ -26,24 +35,24 @@ import {
|
|
|
26
35
|
renderFileStructure,
|
|
27
36
|
stats,
|
|
28
37
|
toPosixPath
|
|
29
|
-
} from "./chunk-
|
|
30
|
-
import "./chunk-
|
|
38
|
+
} from "./chunk-SJT7VS2G.js";
|
|
39
|
+
import "./chunk-PEH54LYC.js";
|
|
31
40
|
|
|
32
41
|
// src/cli.ts
|
|
33
42
|
import { Command } from "commander";
|
|
34
43
|
import chalk2 from "chalk";
|
|
35
44
|
import {
|
|
36
|
-
existsSync as
|
|
37
|
-
readFileSync as
|
|
38
|
-
writeFileSync as
|
|
45
|
+
existsSync as existsSync9,
|
|
46
|
+
readFileSync as readFileSync5,
|
|
47
|
+
writeFileSync as writeFileSync2,
|
|
39
48
|
mkdirSync,
|
|
40
49
|
unlinkSync,
|
|
41
50
|
copyFileSync,
|
|
42
51
|
renameSync as renameSync2
|
|
43
52
|
} from "fs";
|
|
44
|
-
import { dirname as dirname4, join as
|
|
53
|
+
import { dirname as dirname4, join as join9, resolve as pathResolve } from "path";
|
|
45
54
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
46
|
-
import { homedir
|
|
55
|
+
import { homedir } from "os";
|
|
47
56
|
|
|
48
57
|
// src/intercept/safety.ts
|
|
49
58
|
import { existsSync } from "fs";
|
|
@@ -408,8 +417,13 @@ function findGrammarWasm(lang) {
|
|
|
408
417
|
const candidates = [];
|
|
409
418
|
try {
|
|
410
419
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
411
|
-
candidates.push(join3(here, "..", "..", "node_modules", pkg, wasmName));
|
|
412
420
|
candidates.push(join3(here, "..", "grammars", wasmName));
|
|
421
|
+
candidates.push(join3(here, "grammars", wasmName));
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
426
|
+
candidates.push(join3(here, "..", "..", "node_modules", pkg, wasmName));
|
|
413
427
|
} catch {
|
|
414
428
|
}
|
|
415
429
|
try {
|
|
@@ -1325,7 +1339,7 @@ var lspProvider = {
|
|
|
1325
1339
|
};
|
|
1326
1340
|
|
|
1327
1341
|
// src/providers/resolver.ts
|
|
1328
|
-
var
|
|
1342
|
+
var BUILTIN_PROVIDERS = [
|
|
1329
1343
|
astProvider,
|
|
1330
1344
|
structureProvider,
|
|
1331
1345
|
mistakesProvider,
|
|
@@ -1335,12 +1349,26 @@ var ALL_PROVIDERS = [
|
|
|
1335
1349
|
obsidianProvider,
|
|
1336
1350
|
lspProvider
|
|
1337
1351
|
];
|
|
1352
|
+
var BUILTIN_NAMES = new Set(BUILTIN_PROVIDERS.map((p) => p.name));
|
|
1353
|
+
async function getAllProviders() {
|
|
1354
|
+
const { getLoadedPlugins } = await import("./plugin-loader-STTGYIL5.js");
|
|
1355
|
+
const { loaded } = await getLoadedPlugins();
|
|
1356
|
+
const safePlugins = loaded.filter((p) => !BUILTIN_NAMES.has(p.name));
|
|
1357
|
+
return [...BUILTIN_PROVIDERS, ...safePlugins];
|
|
1358
|
+
}
|
|
1359
|
+
var ALL_PROVIDERS = BUILTIN_PROVIDERS;
|
|
1338
1360
|
function estimateTokens(text) {
|
|
1339
1361
|
return Math.ceil(text.length / 4);
|
|
1340
1362
|
}
|
|
1341
1363
|
async function resolveRichPacket(filePath, context, enabledProviders) {
|
|
1342
1364
|
const start = Date.now();
|
|
1343
|
-
|
|
1365
|
+
let allProviders;
|
|
1366
|
+
try {
|
|
1367
|
+
allProviders = await getAllProviders();
|
|
1368
|
+
} catch {
|
|
1369
|
+
allProviders = BUILTIN_PROVIDERS;
|
|
1370
|
+
}
|
|
1371
|
+
const providers = allProviders.filter((p) => {
|
|
1344
1372
|
if (enabledProviders && !enabledProviders.includes(p.name)) return false;
|
|
1345
1373
|
return true;
|
|
1346
1374
|
});
|
|
@@ -1372,7 +1400,7 @@ async function resolveRichPacket(filePath, context, enabledProviders) {
|
|
|
1372
1400
|
if (totalTokens + sectionTokens > budget) {
|
|
1373
1401
|
break;
|
|
1374
1402
|
}
|
|
1375
|
-
const provider =
|
|
1403
|
+
const provider = allProviders.find((p) => p.name === result.provider);
|
|
1376
1404
|
const label = provider?.label ?? result.provider.toUpperCase();
|
|
1377
1405
|
const cacheTag = result.cached ? ", cached" : "";
|
|
1378
1406
|
sections.push(`${label} (${result.provider}${cacheTag}):
|
|
@@ -1406,7 +1434,7 @@ async function warmAllProviders(projectRoot, enabledProviders) {
|
|
|
1406
1434
|
try {
|
|
1407
1435
|
const result = await withTimeout2(p.warmup(projectRoot), 5e3);
|
|
1408
1436
|
if (result && result.entries.length > 0) {
|
|
1409
|
-
const { getStore: getStore2 } = await import("./core-
|
|
1437
|
+
const { getStore: getStore2 } = await import("./core-6IY5L6II.js");
|
|
1410
1438
|
const store = await getStore2(projectRoot);
|
|
1411
1439
|
try {
|
|
1412
1440
|
store.warmCache(
|
|
@@ -2314,98 +2342,6 @@ function watchProject(projectRoot, options = {}) {
|
|
|
2314
2342
|
import chalk from "chalk";
|
|
2315
2343
|
import { existsSync as existsSync7, statSync as statSync3 } from "fs";
|
|
2316
2344
|
import { join as join7, resolve as resolve6, basename as basename4 } from "path";
|
|
2317
|
-
|
|
2318
|
-
// src/intercept/stats.ts
|
|
2319
|
-
var ESTIMATED_TOKENS_PER_READ_DENY = 1200;
|
|
2320
|
-
function summarizeHookLog(entries) {
|
|
2321
|
-
const byEvent = {};
|
|
2322
|
-
const byTool = {};
|
|
2323
|
-
const byDecision = {};
|
|
2324
|
-
let readDenyCount = 0;
|
|
2325
|
-
let firstEntryTs = null;
|
|
2326
|
-
let lastEntryTs = null;
|
|
2327
|
-
for (const entry of entries) {
|
|
2328
|
-
const event = entry.event ?? "unknown";
|
|
2329
|
-
byEvent[event] = (byEvent[event] ?? 0) + 1;
|
|
2330
|
-
const tool = entry.tool ?? "unknown";
|
|
2331
|
-
byTool[tool] = (byTool[tool] ?? 0) + 1;
|
|
2332
|
-
if (entry.decision) {
|
|
2333
|
-
byDecision[entry.decision] = (byDecision[entry.decision] ?? 0) + 1;
|
|
2334
|
-
}
|
|
2335
|
-
if (event === "PreToolUse" && tool === "Read" && entry.decision === "deny") {
|
|
2336
|
-
readDenyCount += 1;
|
|
2337
|
-
}
|
|
2338
|
-
const ts = entry.ts;
|
|
2339
|
-
if (typeof ts === "string") {
|
|
2340
|
-
if (firstEntryTs === null || ts < firstEntryTs) firstEntryTs = ts;
|
|
2341
|
-
if (lastEntryTs === null || ts > lastEntryTs) lastEntryTs = ts;
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
return {
|
|
2345
|
-
totalInvocations: entries.length,
|
|
2346
|
-
byEvent: Object.freeze(byEvent),
|
|
2347
|
-
byTool: Object.freeze(byTool),
|
|
2348
|
-
byDecision: Object.freeze(byDecision),
|
|
2349
|
-
readDenyCount,
|
|
2350
|
-
estimatedTokensSaved: readDenyCount * ESTIMATED_TOKENS_PER_READ_DENY,
|
|
2351
|
-
firstEntry: firstEntryTs,
|
|
2352
|
-
lastEntry: lastEntryTs
|
|
2353
|
-
};
|
|
2354
|
-
}
|
|
2355
|
-
function formatStatsSummary(summary) {
|
|
2356
|
-
if (summary.totalInvocations === 0) {
|
|
2357
|
-
return "engram hook stats: no log entries yet.\n\nRun engram install-hook in a project, then use Claude Code to see interceptions.";
|
|
2358
|
-
}
|
|
2359
|
-
const lines = [];
|
|
2360
|
-
lines.push(`engram hook stats (${summary.totalInvocations} invocations)`);
|
|
2361
|
-
lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2362
|
-
if (summary.firstEntry && summary.lastEntry) {
|
|
2363
|
-
lines.push(`Time range: ${summary.firstEntry} \u2192 ${summary.lastEntry}`);
|
|
2364
|
-
lines.push("");
|
|
2365
|
-
}
|
|
2366
|
-
lines.push("By event:");
|
|
2367
|
-
const eventEntries = Object.entries(summary.byEvent).sort(
|
|
2368
|
-
(a, b) => b[1] - a[1]
|
|
2369
|
-
);
|
|
2370
|
-
for (const [event, count] of eventEntries) {
|
|
2371
|
-
const pct = (count / summary.totalInvocations * 100).toFixed(1);
|
|
2372
|
-
lines.push(` ${event.padEnd(18)} ${String(count).padStart(5)} (${pct}%)`);
|
|
2373
|
-
}
|
|
2374
|
-
lines.push("");
|
|
2375
|
-
lines.push("By tool:");
|
|
2376
|
-
const toolEntries = Object.entries(summary.byTool).filter(([k]) => k !== "unknown").sort((a, b) => b[1] - a[1]);
|
|
2377
|
-
for (const [tool, count] of toolEntries) {
|
|
2378
|
-
lines.push(` ${tool.padEnd(18)} ${String(count).padStart(5)}`);
|
|
2379
|
-
}
|
|
2380
|
-
if (toolEntries.length === 0) {
|
|
2381
|
-
lines.push(" (no tool-tagged entries)");
|
|
2382
|
-
}
|
|
2383
|
-
lines.push("");
|
|
2384
|
-
const decisionEntries = Object.entries(summary.byDecision);
|
|
2385
|
-
if (decisionEntries.length > 0) {
|
|
2386
|
-
lines.push("PreToolUse decisions:");
|
|
2387
|
-
for (const [decision, count] of decisionEntries.sort(
|
|
2388
|
-
(a, b) => b[1] - a[1]
|
|
2389
|
-
)) {
|
|
2390
|
-
lines.push(` ${decision.padEnd(18)} ${String(count).padStart(5)}`);
|
|
2391
|
-
}
|
|
2392
|
-
lines.push("");
|
|
2393
|
-
}
|
|
2394
|
-
if (summary.readDenyCount > 0) {
|
|
2395
|
-
lines.push(
|
|
2396
|
-
`Estimated tokens saved: ~${summary.estimatedTokensSaved.toLocaleString()}`
|
|
2397
|
-
);
|
|
2398
|
-
lines.push(
|
|
2399
|
-
` (${summary.readDenyCount} Read denies \xD7 ${ESTIMATED_TOKENS_PER_READ_DENY} tok/deny avg)`
|
|
2400
|
-
);
|
|
2401
|
-
} else {
|
|
2402
|
-
lines.push("Estimated tokens saved: 0");
|
|
2403
|
-
lines.push(" (no PreToolUse:Read denies recorded yet)");
|
|
2404
|
-
}
|
|
2405
|
-
return lines.join("\n");
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
// src/dashboard.ts
|
|
2409
2345
|
var AMBER = chalk.hex("#d97706");
|
|
2410
2346
|
var DIM = chalk.dim;
|
|
2411
2347
|
var GREEN = chalk.green;
|
|
@@ -2736,116 +2672,15 @@ function formatInstallDiff(before, after) {
|
|
|
2736
2672
|
return lines.length > 0 ? lines.join("\n") : "(no changes)";
|
|
2737
2673
|
}
|
|
2738
2674
|
|
|
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
|
-
|
|
2840
2675
|
// src/intercept/memory-md.ts
|
|
2841
2676
|
import {
|
|
2842
|
-
existsSync as
|
|
2843
|
-
readFileSync as
|
|
2844
|
-
writeFileSync
|
|
2677
|
+
existsSync as existsSync8,
|
|
2678
|
+
readFileSync as readFileSync4,
|
|
2679
|
+
writeFileSync,
|
|
2845
2680
|
renameSync,
|
|
2846
2681
|
statSync as statSync4
|
|
2847
2682
|
} from "fs";
|
|
2848
|
-
import { join as
|
|
2683
|
+
import { join as join8 } from "path";
|
|
2849
2684
|
var ENGRAM_MARKER_START = "<!-- engram:structural-facts:start -->";
|
|
2850
2685
|
var ENGRAM_MARKER_END = "<!-- engram:structural-facts:end -->";
|
|
2851
2686
|
var MAX_MEMORY_FILE_BYTES = 1e6;
|
|
@@ -2916,19 +2751,19 @@ function writeEngramSectionToMemoryMd(projectRoot, engramSection) {
|
|
|
2916
2751
|
if (engramSection.length > MAX_ENGRAM_SECTION_BYTES) {
|
|
2917
2752
|
return false;
|
|
2918
2753
|
}
|
|
2919
|
-
const memoryPath =
|
|
2754
|
+
const memoryPath = join8(projectRoot, "MEMORY.md");
|
|
2920
2755
|
try {
|
|
2921
2756
|
let existing = "";
|
|
2922
|
-
if (
|
|
2757
|
+
if (existsSync8(memoryPath)) {
|
|
2923
2758
|
const st = statSync4(memoryPath);
|
|
2924
2759
|
if (st.size > MAX_MEMORY_FILE_BYTES) {
|
|
2925
2760
|
return false;
|
|
2926
2761
|
}
|
|
2927
|
-
existing =
|
|
2762
|
+
existing = readFileSync4(memoryPath, "utf-8");
|
|
2928
2763
|
}
|
|
2929
2764
|
const updated = upsertEngramSection(existing, engramSection);
|
|
2930
2765
|
const tmpPath = memoryPath + ".engram-tmp";
|
|
2931
|
-
|
|
2766
|
+
writeFileSync(tmpPath, updated);
|
|
2932
2767
|
renameSync(tmpPath, memoryPath);
|
|
2933
2768
|
return true;
|
|
2934
2769
|
} catch {
|
|
@@ -2948,10 +2783,14 @@ program.name("engram").description(
|
|
|
2948
2783
|
program.command("init").description("Scan codebase and build knowledge graph (zero LLM cost)").argument("[path]", "Project directory", ".").option(
|
|
2949
2784
|
"--with-skills [dir]",
|
|
2950
2785
|
"Also index Claude Code skills from ~/.claude/skills/ or a given path"
|
|
2951
|
-
).option("--from-ccs", "Import .context/index.md (CCS) into graph after init").
|
|
2952
|
-
|
|
2786
|
+
).option("--from-ccs", "Import .context/index.md (CCS) into graph after init").option(
|
|
2787
|
+
"--incremental",
|
|
2788
|
+
"Skip unchanged files (mtime-based). Dramatically faster on re-index of large repos."
|
|
2789
|
+
).action(async (projectPath, opts) => {
|
|
2790
|
+
console.log(chalk2.dim(opts.incremental ? "\u{1F50D} Scanning changed files..." : "\u{1F50D} Scanning codebase..."));
|
|
2953
2791
|
const result = await init(projectPath, {
|
|
2954
|
-
withSkills: opts.withSkills
|
|
2792
|
+
withSkills: opts.withSkills,
|
|
2793
|
+
incremental: opts.incremental
|
|
2955
2794
|
});
|
|
2956
2795
|
console.log(
|
|
2957
2796
|
chalk2.green("\u{1F333} AST extraction complete") + chalk2.dim(` (${result.timeMs}ms, 0 tokens used)`)
|
|
@@ -2959,6 +2798,9 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2959
2798
|
console.log(
|
|
2960
2799
|
` ${chalk2.bold(String(result.nodes))} nodes, ${chalk2.bold(String(result.edges))} edges from ${chalk2.bold(String(result.fileCount))} files (${result.totalLines.toLocaleString()} lines)`
|
|
2961
2800
|
);
|
|
2801
|
+
if (result.incremental && result.skippedFiles && result.skippedFiles > 0) {
|
|
2802
|
+
console.log(chalk2.dim(` ${result.skippedFiles} unchanged files skipped (incremental mode)`));
|
|
2803
|
+
}
|
|
2962
2804
|
if (result.skillCount && result.skillCount > 0) {
|
|
2963
2805
|
console.log(
|
|
2964
2806
|
chalk2.cyan(` ${chalk2.bold(String(result.skillCount))} skills indexed`)
|
|
@@ -2977,9 +2819,9 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2977
2819
|
console.log(chalk2.green("\n\u2705 Ready. Your AI now has persistent memory."));
|
|
2978
2820
|
console.log(chalk2.dim(" Graph stored in .engram/graph.db"));
|
|
2979
2821
|
const resolvedProject = pathResolve(projectPath);
|
|
2980
|
-
const localSettings =
|
|
2981
|
-
const projectSettings =
|
|
2982
|
-
const hasHooks =
|
|
2822
|
+
const localSettings = join9(resolvedProject, ".claude", "settings.local.json");
|
|
2823
|
+
const projectSettings = join9(resolvedProject, ".claude", "settings.json");
|
|
2824
|
+
const hasHooks = existsSync9(localSettings) && readFileSync5(localSettings, "utf-8").includes("engram intercept") || existsSync9(projectSettings) && readFileSync5(projectSettings, "utf-8").includes("engram intercept");
|
|
2983
2825
|
if (!hasHooks) {
|
|
2984
2826
|
console.log(
|
|
2985
2827
|
chalk2.yellow("\n\u{1F4A1} Next step: ") + chalk2.white("engram install-hook") + chalk2.dim(
|
|
@@ -2993,7 +2835,7 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2993
2835
|
);
|
|
2994
2836
|
}
|
|
2995
2837
|
if (opts.fromCcs) {
|
|
2996
|
-
const { importCcs } = await import("./importer-
|
|
2838
|
+
const { importCcs } = await import("./importer-V62NGZRK.js");
|
|
2997
2839
|
const resolvedProjectPath = pathResolve(projectPath);
|
|
2998
2840
|
const ccsResult = await importCcs(resolvedProjectPath);
|
|
2999
2841
|
if (ccsResult.nodesCreated > 0) {
|
|
@@ -3035,8 +2877,8 @@ program.command("watch").description("Watch project for file changes and re-inde
|
|
|
3035
2877
|
});
|
|
3036
2878
|
program.command("dashboard").alias("hud").description("Live terminal dashboard showing hook activity and token savings").argument("[path]", "Project directory", ".").action(async (projectPath) => {
|
|
3037
2879
|
const resolvedPath = pathResolve(projectPath);
|
|
3038
|
-
const dbPath =
|
|
3039
|
-
if (!
|
|
2880
|
+
const dbPath = join9(resolvedPath, ".engram", "graph.db");
|
|
2881
|
+
if (!existsSync9(dbPath)) {
|
|
3040
2882
|
console.error(
|
|
3041
2883
|
chalk2.red("No engram graph found at ") + chalk2.white(resolvedPath)
|
|
3042
2884
|
);
|
|
@@ -3056,7 +2898,7 @@ program.command("hud-label").description("Output JSON label for Claude HUD --ext
|
|
|
3056
2898
|
let resolvedPath = pathResolve(projectPath);
|
|
3057
2899
|
let found = false;
|
|
3058
2900
|
for (let depth = 0; depth < 20; depth++) {
|
|
3059
|
-
if (
|
|
2901
|
+
if (existsSync9(join9(resolvedPath, ".engram", "graph.db"))) {
|
|
3060
2902
|
found = true;
|
|
3061
2903
|
break;
|
|
3062
2904
|
}
|
|
@@ -3068,8 +2910,8 @@ program.command("hud-label").description("Output JSON label for Claude HUD --ext
|
|
|
3068
2910
|
console.log('{"label":""}');
|
|
3069
2911
|
return;
|
|
3070
2912
|
}
|
|
3071
|
-
const logPath =
|
|
3072
|
-
if (!
|
|
2913
|
+
const logPath = join9(resolvedPath, ".engram", "hook-log.jsonl");
|
|
2914
|
+
if (!existsSync9(logPath)) {
|
|
3073
2915
|
console.log('{"label":"\u26A1engram \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 ready"}');
|
|
3074
2916
|
return;
|
|
3075
2917
|
}
|
|
@@ -3223,7 +3065,7 @@ program.command("gen").description("Generate CLAUDE.md / .cursorrules section fr
|
|
|
3223
3065
|
}
|
|
3224
3066
|
);
|
|
3225
3067
|
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-
|
|
3068
|
+
const { generateCursorMdc } = await import("./cursor-mdc-GJ7E5LDD.js");
|
|
3227
3069
|
const result = await generateCursorMdc(opts.project);
|
|
3228
3070
|
console.log(
|
|
3229
3071
|
chalk2.green(
|
|
@@ -3244,7 +3086,7 @@ program.command("gen-mdc").description("Generate .cursor/rules/engram-context.md
|
|
|
3244
3086
|
}
|
|
3245
3087
|
});
|
|
3246
3088
|
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-
|
|
3089
|
+
const { exportCcs } = await import("./exporter-GWU2GF23.js");
|
|
3248
3090
|
const result = await exportCcs(pathResolve(opts.project));
|
|
3249
3091
|
console.log(
|
|
3250
3092
|
chalk2.green(
|
|
@@ -3253,7 +3095,7 @@ program.command("gen-ccs").description("Export knowledge graph as .context/index
|
|
|
3253
3095
|
);
|
|
3254
3096
|
});
|
|
3255
3097
|
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-
|
|
3098
|
+
const { generateAiderContext } = await import("./aider-context-BC5R2ZTA.js");
|
|
3257
3099
|
const result = await generateAiderContext(pathResolve(opts.project));
|
|
3258
3100
|
console.log(
|
|
3259
3101
|
chalk2.green(
|
|
@@ -3273,15 +3115,36 @@ program.command("gen-aider").description("Generate .aider-context.md from knowle
|
|
|
3273
3115
|
});
|
|
3274
3116
|
}
|
|
3275
3117
|
});
|
|
3118
|
+
program.command("gen-windsurfrules").description("Generate .windsurfrules from knowledge graph (Windsurf IDE)").option("-p, --project <path>", "Project directory", ".").option("--watch", "Regenerate on graph changes").action(async (opts) => {
|
|
3119
|
+
const { generateWindsurfRules } = await import("./windsurf-rules-C7SVDHBL.js");
|
|
3120
|
+
const result = await generateWindsurfRules(pathResolve(opts.project));
|
|
3121
|
+
console.log(
|
|
3122
|
+
chalk2.green(
|
|
3123
|
+
`\u2705 Generated ${result.filePath} (${result.sections} sections, ${result.nodes} nodes)`
|
|
3124
|
+
)
|
|
3125
|
+
);
|
|
3126
|
+
if (opts.watch) {
|
|
3127
|
+
watchProject(pathResolve(opts.project), {
|
|
3128
|
+
onReindex: async () => {
|
|
3129
|
+
const r = await generateWindsurfRules(opts.project);
|
|
3130
|
+
console.log(chalk2.dim(` \u21BB Regenerated .windsurfrules (${r.nodes} nodes)`));
|
|
3131
|
+
},
|
|
3132
|
+
onError: (err) => console.error(chalk2.red(err.message)),
|
|
3133
|
+
onReady: () => console.log(chalk2.dim(" Watching for changes..."))
|
|
3134
|
+
});
|
|
3135
|
+
await new Promise(() => {
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3138
|
+
});
|
|
3276
3139
|
function resolveSettingsPath(scope, projectPath) {
|
|
3277
3140
|
const absProject = pathResolve(projectPath);
|
|
3278
3141
|
switch (scope) {
|
|
3279
3142
|
case "local":
|
|
3280
|
-
return
|
|
3143
|
+
return join9(absProject, ".claude", "settings.local.json");
|
|
3281
3144
|
case "project":
|
|
3282
|
-
return
|
|
3145
|
+
return join9(absProject, ".claude", "settings.json");
|
|
3283
3146
|
case "user":
|
|
3284
|
-
return
|
|
3147
|
+
return join9(homedir(), ".claude", "settings.json");
|
|
3285
3148
|
default:
|
|
3286
3149
|
return null;
|
|
3287
3150
|
}
|
|
@@ -3375,9 +3238,9 @@ program.command("install-hook").description("Install engram hook entries into Cl
|
|
|
3375
3238
|
process.exit(1);
|
|
3376
3239
|
}
|
|
3377
3240
|
let existing = {};
|
|
3378
|
-
if (
|
|
3241
|
+
if (existsSync9(settingsPath)) {
|
|
3379
3242
|
try {
|
|
3380
|
-
const raw =
|
|
3243
|
+
const raw = readFileSync5(settingsPath, "utf-8");
|
|
3381
3244
|
existing = raw.trim() ? JSON.parse(raw) : {};
|
|
3382
3245
|
} catch (err) {
|
|
3383
3246
|
console.error(
|
|
@@ -3423,13 +3286,13 @@ program.command("install-hook").description("Install engram hook entries into Cl
|
|
|
3423
3286
|
}
|
|
3424
3287
|
try {
|
|
3425
3288
|
mkdirSync(dirname4(settingsPath), { recursive: true });
|
|
3426
|
-
if (
|
|
3289
|
+
if (existsSync9(settingsPath)) {
|
|
3427
3290
|
const backupPath = `${settingsPath}.engram-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
3428
3291
|
copyFileSync(settingsPath, backupPath);
|
|
3429
3292
|
console.log(chalk2.dim(` Backup: ${backupPath}`));
|
|
3430
3293
|
}
|
|
3431
3294
|
const tmpPath = settingsPath + ".engram-tmp";
|
|
3432
|
-
|
|
3295
|
+
writeFileSync2(
|
|
3433
3296
|
tmpPath,
|
|
3434
3297
|
JSON.stringify(result.updated, null, 2) + "\n"
|
|
3435
3298
|
);
|
|
@@ -3474,7 +3337,7 @@ program.command("uninstall-hook").description("Remove engram hook entries from C
|
|
|
3474
3337
|
console.error(chalk2.red(`Unknown scope: ${opts.scope}`));
|
|
3475
3338
|
process.exit(1);
|
|
3476
3339
|
}
|
|
3477
|
-
if (!
|
|
3340
|
+
if (!existsSync9(settingsPath)) {
|
|
3478
3341
|
console.log(
|
|
3479
3342
|
chalk2.yellow(`No settings file at ${settingsPath} \u2014 nothing to remove.`)
|
|
3480
3343
|
);
|
|
@@ -3482,7 +3345,7 @@ program.command("uninstall-hook").description("Remove engram hook entries from C
|
|
|
3482
3345
|
}
|
|
3483
3346
|
let existing;
|
|
3484
3347
|
try {
|
|
3485
|
-
const raw =
|
|
3348
|
+
const raw = readFileSync5(settingsPath, "utf-8");
|
|
3486
3349
|
existing = raw.trim() ? JSON.parse(raw) : {};
|
|
3487
3350
|
} catch (err) {
|
|
3488
3351
|
console.error(
|
|
@@ -3502,7 +3365,7 @@ program.command("uninstall-hook").description("Remove engram hook entries from C
|
|
|
3502
3365
|
const backupPath = `${settingsPath}.engram-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
3503
3366
|
copyFileSync(settingsPath, backupPath);
|
|
3504
3367
|
const tmpPath = settingsPath + ".engram-tmp";
|
|
3505
|
-
|
|
3368
|
+
writeFileSync2(tmpPath, JSON.stringify(result.updated, null, 2) + "\n");
|
|
3506
3369
|
renameSync2(tmpPath, settingsPath);
|
|
3507
3370
|
if (result.removed.length > 0) {
|
|
3508
3371
|
console.log(
|
|
@@ -3597,9 +3460,9 @@ program.command("hook-disable").description("Disable engram hooks via kill switc
|
|
|
3597
3460
|
console.error(chalk2.dim("Run 'engram init' first."));
|
|
3598
3461
|
process.exit(1);
|
|
3599
3462
|
}
|
|
3600
|
-
const flagPath =
|
|
3463
|
+
const flagPath = join9(projectRoot, ".engram", "hook-disabled");
|
|
3601
3464
|
try {
|
|
3602
|
-
|
|
3465
|
+
writeFileSync2(flagPath, (/* @__PURE__ */ new Date()).toISOString());
|
|
3603
3466
|
console.log(
|
|
3604
3467
|
chalk2.green(`\u2705 engram hooks disabled for ${projectRoot}`)
|
|
3605
3468
|
);
|
|
@@ -3621,8 +3484,8 @@ program.command("hook-enable").description("Re-enable engram hooks (remove kill
|
|
|
3621
3484
|
console.error(chalk2.red(`Not an engram project: ${absProject}`));
|
|
3622
3485
|
process.exit(1);
|
|
3623
3486
|
}
|
|
3624
|
-
const flagPath =
|
|
3625
|
-
if (!
|
|
3487
|
+
const flagPath = join9(projectRoot, ".engram", "hook-disabled");
|
|
3488
|
+
if (!existsSync9(flagPath)) {
|
|
3626
3489
|
console.log(
|
|
3627
3490
|
chalk2.yellow(`engram hooks already enabled for ${projectRoot}`)
|
|
3628
3491
|
);
|
|
@@ -3664,9 +3527,9 @@ program.command("memory-sync").description(
|
|
|
3664
3527
|
}
|
|
3665
3528
|
let branch = null;
|
|
3666
3529
|
try {
|
|
3667
|
-
const headPath =
|
|
3668
|
-
if (
|
|
3669
|
-
const content =
|
|
3530
|
+
const headPath = join9(projectRoot, ".git", "HEAD");
|
|
3531
|
+
if (existsSync9(headPath)) {
|
|
3532
|
+
const content = readFileSync5(headPath, "utf-8").trim();
|
|
3670
3533
|
const m = content.match(/^ref:\s+refs\/heads\/(.+)$/);
|
|
3671
3534
|
if (m) branch = m[1];
|
|
3672
3535
|
}
|
|
@@ -3692,7 +3555,7 @@ program.command("memory-sync").description(
|
|
|
3692
3555
|
\u{1F4DD} engram memory-sync`)
|
|
3693
3556
|
);
|
|
3694
3557
|
console.log(
|
|
3695
|
-
chalk2.dim(` Target: ${
|
|
3558
|
+
chalk2.dim(` Target: ${join9(projectRoot, "MEMORY.md")}`)
|
|
3696
3559
|
);
|
|
3697
3560
|
if (opts.dryRun) {
|
|
3698
3561
|
console.log(chalk2.cyan("\n Section to write (dry-run):\n"));
|
|
@@ -3736,29 +3599,69 @@ program.command("stress-test").description("Run stress tests: memory, concurrenc
|
|
|
3736
3599
|
if (opts.replay) args.push("--replay", opts.replay);
|
|
3737
3600
|
if (opts.limit) args.push("--limit", String(opts.limit));
|
|
3738
3601
|
try {
|
|
3739
|
-
execFileSync2("npx", ["tsx", ...args], { stdio: "inherit", shell: true, cwd:
|
|
3602
|
+
execFileSync2("npx", ["tsx", ...args], { stdio: "inherit", shell: true, cwd: join9(dirname4(fileURLToPath2(import.meta.url)), "..") });
|
|
3740
3603
|
} catch {
|
|
3741
3604
|
process.exit(1);
|
|
3742
3605
|
}
|
|
3743
3606
|
});
|
|
3744
3607
|
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-
|
|
3608
|
+
const { startHttpServer } = await import("./server-6AOI7NQP.js");
|
|
3746
3609
|
await startHttpServer(pathResolve(opts.project), parseInt(opts.port, 10));
|
|
3747
3610
|
});
|
|
3611
|
+
program.command("ui").description("Open the web dashboard (auto-starts HTTP server if needed)").option("--port <port>", "HTTP port", "7337").option("-p, --project <path>", "Project directory", ".").option("--no-open", "Don't launch browser, just print the URL").action(async (opts) => {
|
|
3612
|
+
const port = parseInt(opts.port, 10);
|
|
3613
|
+
const url = `http://127.0.0.1:${port}/ui`;
|
|
3614
|
+
const projectRoot = pathResolve(opts.project);
|
|
3615
|
+
const { existsSync: existsSync10, readFileSync: readFileSync6 } = await import("fs");
|
|
3616
|
+
const pidPath = join9(projectRoot, ".engram", "http-server.pid");
|
|
3617
|
+
let alreadyRunning = false;
|
|
3618
|
+
if (existsSync10(pidPath)) {
|
|
3619
|
+
try {
|
|
3620
|
+
const pid = parseInt(readFileSync6(pidPath, "utf-8"), 10);
|
|
3621
|
+
process.kill(pid, 0);
|
|
3622
|
+
alreadyRunning = true;
|
|
3623
|
+
} catch {
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
if (alreadyRunning) {
|
|
3627
|
+
console.log(chalk2.dim(`engram server already running \u2014 opening ${url}`));
|
|
3628
|
+
} else {
|
|
3629
|
+
console.log(chalk2.dim(`Starting engram server on ${url}...`));
|
|
3630
|
+
const { spawn } = await import("child_process");
|
|
3631
|
+
const child = spawn(
|
|
3632
|
+
process.argv[0],
|
|
3633
|
+
[process.argv[1], "server", "--port", String(port), "-p", projectRoot],
|
|
3634
|
+
{ detached: true, stdio: "ignore" }
|
|
3635
|
+
);
|
|
3636
|
+
child.unref();
|
|
3637
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
3638
|
+
}
|
|
3639
|
+
console.log(chalk2.green(`\u2713 Dashboard: ${url}`));
|
|
3640
|
+
if (opts.open !== false) {
|
|
3641
|
+
const { platform } = process;
|
|
3642
|
+
const opener = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
3643
|
+
try {
|
|
3644
|
+
const { execFile: execFile4 } = await import("child_process");
|
|
3645
|
+
execFile4(opener, [url], { shell: platform === "win32" }, () => {
|
|
3646
|
+
});
|
|
3647
|
+
} catch {
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
});
|
|
3748
3651
|
program.command("context-server").description("Start Zed-compatible context server (JSON-RPC over stdio)").action(async () => {
|
|
3749
3652
|
const { execFileSync: execFileSync2 } = await import("child_process");
|
|
3750
3653
|
try {
|
|
3751
3654
|
execFileSync2("npx", ["tsx", "adapters/zed/index.ts"], {
|
|
3752
3655
|
stdio: "inherit",
|
|
3753
3656
|
shell: true,
|
|
3754
|
-
cwd:
|
|
3657
|
+
cwd: join9(dirname4(fileURLToPath2(import.meta.url)), "..")
|
|
3755
3658
|
});
|
|
3756
3659
|
} catch {
|
|
3757
3660
|
process.exit(1);
|
|
3758
3661
|
}
|
|
3759
3662
|
});
|
|
3760
3663
|
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-
|
|
3664
|
+
const { analyzeTuning, applyTuning } = await import("./tuner-KFNNGKG3.js");
|
|
3762
3665
|
const proposal = analyzeTuning(pathResolve(opts.project));
|
|
3763
3666
|
if (proposal.changes.length === 0) {
|
|
3764
3667
|
console.log(
|
|
@@ -3789,8 +3692,8 @@ program.command("tune").description("Analyze hook-log and propose provider confi
|
|
|
3789
3692
|
});
|
|
3790
3693
|
var dbCmd = program.command("db").description("Database management");
|
|
3791
3694
|
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-
|
|
3793
|
-
const { CURRENT_SCHEMA_VERSION, getSchemaVersion } = await import("./migrate-
|
|
3695
|
+
const { getStore: getStore2 } = await import("./core-6IY5L6II.js");
|
|
3696
|
+
const { CURRENT_SCHEMA_VERSION, getSchemaVersion } = await import("./migrate-UKCO6BUU.js");
|
|
3794
3697
|
const store = await getStore2(pathResolve(opts.project));
|
|
3795
3698
|
try {
|
|
3796
3699
|
const version = getSchemaVersion(store.db);
|
|
@@ -3806,11 +3709,11 @@ dbCmd.command("status").description("Show schema version and migration status").
|
|
|
3806
3709
|
}
|
|
3807
3710
|
});
|
|
3808
3711
|
dbCmd.command("migrate").description("Run pending schema migrations").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3809
|
-
const { getStore: getStore2 } = await import("./core-
|
|
3810
|
-
const { runMigrations } = await import("./migrate-
|
|
3712
|
+
const { getStore: getStore2 } = await import("./core-6IY5L6II.js");
|
|
3713
|
+
const { runMigrations } = await import("./migrate-UKCO6BUU.js");
|
|
3811
3714
|
const store = await getStore2(pathResolve(opts.project));
|
|
3812
3715
|
try {
|
|
3813
|
-
const dbPath =
|
|
3716
|
+
const dbPath = join9(pathResolve(opts.project), ".engram", "graph.db");
|
|
3814
3717
|
const result = runMigrations(
|
|
3815
3718
|
store.db,
|
|
3816
3719
|
dbPath
|
|
@@ -3830,4 +3733,226 @@ dbCmd.command("migrate").description("Run pending schema migrations").option("-p
|
|
|
3830
3733
|
store.close();
|
|
3831
3734
|
}
|
|
3832
3735
|
});
|
|
3736
|
+
dbCmd.command("rollback").description("Roll back to an earlier schema version (DESTRUCTIVE \u2014 always backs up first)").option("-p, --project <path>", "Project directory", ".").option("--to <version>", "Target schema version (0 drops all tables)").option("--yes", "Skip confirmation prompt").action(async (opts) => {
|
|
3737
|
+
if (opts.to === void 0) {
|
|
3738
|
+
console.error(chalk2.red("Required: --to <version>"));
|
|
3739
|
+
console.log(chalk2.dim("Run 'engram db status' to see current version."));
|
|
3740
|
+
process.exit(1);
|
|
3741
|
+
}
|
|
3742
|
+
const target = parseInt(opts.to, 10);
|
|
3743
|
+
if (isNaN(target)) {
|
|
3744
|
+
console.error(chalk2.red(`Invalid version: ${opts.to}`));
|
|
3745
|
+
process.exit(1);
|
|
3746
|
+
}
|
|
3747
|
+
const { getStore: getStore2 } = await import("./core-6IY5L6II.js");
|
|
3748
|
+
const { rollback, getSchemaVersion } = await import("./migrate-UKCO6BUU.js");
|
|
3749
|
+
const store = await getStore2(pathResolve(opts.project));
|
|
3750
|
+
try {
|
|
3751
|
+
const dbPath = join9(pathResolve(opts.project), ".engram", "graph.db");
|
|
3752
|
+
const current = getSchemaVersion(
|
|
3753
|
+
store.db
|
|
3754
|
+
);
|
|
3755
|
+
if (target === current) {
|
|
3756
|
+
console.log(chalk2.green(`Already at v${target}.`));
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
if (target > current) {
|
|
3760
|
+
console.error(
|
|
3761
|
+
chalk2.red(`Cannot roll back to v${target}: current is v${current}.`)
|
|
3762
|
+
);
|
|
3763
|
+
console.log(chalk2.dim("Use 'engram db migrate' to move forward."));
|
|
3764
|
+
process.exit(1);
|
|
3765
|
+
}
|
|
3766
|
+
if (!opts.yes) {
|
|
3767
|
+
console.log(
|
|
3768
|
+
chalk2.yellow(
|
|
3769
|
+
`\u26A0 This will roll back from v${current} \u2192 v${target}.`
|
|
3770
|
+
)
|
|
3771
|
+
);
|
|
3772
|
+
console.log(
|
|
3773
|
+
chalk2.yellow(
|
|
3774
|
+
` Tables created after v${target} will be DROPPED (data loss).`
|
|
3775
|
+
)
|
|
3776
|
+
);
|
|
3777
|
+
console.log(chalk2.dim(` A backup will be saved to ${dbPath}.bak-v${current}`));
|
|
3778
|
+
console.log(chalk2.dim(` Re-run with --yes to confirm.`));
|
|
3779
|
+
process.exit(1);
|
|
3780
|
+
}
|
|
3781
|
+
const result = rollback(
|
|
3782
|
+
store.db,
|
|
3783
|
+
dbPath,
|
|
3784
|
+
target
|
|
3785
|
+
);
|
|
3786
|
+
store.save();
|
|
3787
|
+
console.log(
|
|
3788
|
+
chalk2.green(
|
|
3789
|
+
`\u2713 Rolled back v${result.fromVersion} \u2192 v${result.toVersion} (${result.migrationsReverted} migrations reverted)`
|
|
3790
|
+
)
|
|
3791
|
+
);
|
|
3792
|
+
if (result.backedUp) {
|
|
3793
|
+
console.log(chalk2.dim(` Backup: ${dbPath}.bak-v${result.fromVersion}`));
|
|
3794
|
+
}
|
|
3795
|
+
} finally {
|
|
3796
|
+
store.close();
|
|
3797
|
+
}
|
|
3798
|
+
});
|
|
3799
|
+
var pluginCmd = program.command("plugin").description("Manage context provider plugins");
|
|
3800
|
+
pluginCmd.command("list").description("List installed provider plugins").action(async () => {
|
|
3801
|
+
const { loadPlugins, getPluginsDir, ensurePluginsDir } = await import("./plugin-loader-STTGYIL5.js");
|
|
3802
|
+
const dir = getPluginsDir();
|
|
3803
|
+
ensurePluginsDir(dir);
|
|
3804
|
+
const { loaded, failed } = await loadPlugins(dir);
|
|
3805
|
+
if (loaded.length === 0 && failed.length === 0) {
|
|
3806
|
+
console.log(chalk2.dim(`No plugins installed.`));
|
|
3807
|
+
console.log(chalk2.dim(`Install with: engram plugin install <file.mjs>`));
|
|
3808
|
+
console.log(chalk2.dim(`Plugins directory: ${dir}`));
|
|
3809
|
+
return;
|
|
3810
|
+
}
|
|
3811
|
+
if (loaded.length > 0) {
|
|
3812
|
+
console.log(chalk2.bold(`Installed plugins (${loaded.length})`));
|
|
3813
|
+
console.log(chalk2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3814
|
+
for (const p of loaded) {
|
|
3815
|
+
const tierLabel = p.tier === 1 ? "internal" : "external";
|
|
3816
|
+
const descr = p.description ? ` \u2014 ${p.description}` : "";
|
|
3817
|
+
console.log(
|
|
3818
|
+
` ${chalk2.green("\u25CF")} ${chalk2.bold(p.name)} ${chalk2.dim(`v${p.version}`)} ` + chalk2.dim(`[${tierLabel}, ${p.tokenBudget}tok budget]`)
|
|
3819
|
+
);
|
|
3820
|
+
if (descr) console.log(` ${chalk2.dim(descr.trim())}`);
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
if (failed.length > 0) {
|
|
3824
|
+
console.log();
|
|
3825
|
+
console.log(chalk2.yellow(`Failed to load (${failed.length}):`));
|
|
3826
|
+
for (const f of failed) {
|
|
3827
|
+
console.log(` ${chalk2.red("\u2717")} ${f.file} ${chalk2.dim(`\u2014 ${f.reason}`)}`);
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
});
|
|
3831
|
+
pluginCmd.command("install").description("Install a plugin by copying its .mjs file into ~/.engram/plugins/").argument("<file>", "Path to plugin .mjs file").action(async (file) => {
|
|
3832
|
+
const { copyFileSync: copyFileSync2, statSync: statSync5 } = await import("fs");
|
|
3833
|
+
const { basename: basename6 } = await import("path");
|
|
3834
|
+
const { getPluginsDir, ensurePluginsDir, validatePlugin } = await import("./plugin-loader-STTGYIL5.js");
|
|
3835
|
+
const { pathToFileURL } = await import("url");
|
|
3836
|
+
const absPath = pathResolve(file);
|
|
3837
|
+
if (!existsSync9(absPath)) {
|
|
3838
|
+
console.error(chalk2.red(`File not found: ${absPath}`));
|
|
3839
|
+
process.exit(1);
|
|
3840
|
+
}
|
|
3841
|
+
if (!statSync5(absPath).isFile()) {
|
|
3842
|
+
console.error(chalk2.red(`Not a file: ${absPath}`));
|
|
3843
|
+
process.exit(1);
|
|
3844
|
+
}
|
|
3845
|
+
if (!absPath.endsWith(".mjs") && !absPath.endsWith(".js")) {
|
|
3846
|
+
console.error(chalk2.red(`Plugin must be .mjs or .js (got ${absPath})`));
|
|
3847
|
+
process.exit(1);
|
|
3848
|
+
}
|
|
3849
|
+
try {
|
|
3850
|
+
const mod = await import(pathToFileURL(absPath).href);
|
|
3851
|
+
const { plugin, reason } = validatePlugin(mod);
|
|
3852
|
+
if (!plugin) {
|
|
3853
|
+
console.error(chalk2.red(`Invalid plugin: ${reason}`));
|
|
3854
|
+
process.exit(1);
|
|
3855
|
+
}
|
|
3856
|
+
console.log(
|
|
3857
|
+
chalk2.dim(`Validated ${plugin.name} v${plugin.version} (tier ${plugin.tier})`)
|
|
3858
|
+
);
|
|
3859
|
+
} catch (e) {
|
|
3860
|
+
console.error(chalk2.red(`Failed to load plugin: ${e.message}`));
|
|
3861
|
+
process.exit(1);
|
|
3862
|
+
}
|
|
3863
|
+
const pluginsDir = getPluginsDir();
|
|
3864
|
+
ensurePluginsDir(pluginsDir);
|
|
3865
|
+
const destName = basename6(absPath);
|
|
3866
|
+
const destPath = join9(pluginsDir, destName);
|
|
3867
|
+
copyFileSync2(absPath, destPath);
|
|
3868
|
+
console.log(chalk2.green(`\u2713 Installed: ${destPath}`));
|
|
3869
|
+
});
|
|
3870
|
+
pluginCmd.command("remove").description("Remove an installed plugin by filename").argument("<filename>", "Plugin filename (e.g., my-provider.mjs)").action(async (filename) => {
|
|
3871
|
+
const { getPluginsDir } = await import("./plugin-loader-STTGYIL5.js");
|
|
3872
|
+
const pluginsDir = getPluginsDir();
|
|
3873
|
+
const target = join9(pluginsDir, filename);
|
|
3874
|
+
if (!existsSync9(target)) {
|
|
3875
|
+
console.error(chalk2.red(`No such plugin: ${filename}`));
|
|
3876
|
+
console.log(chalk2.dim(`Plugins directory: ${pluginsDir}`));
|
|
3877
|
+
process.exit(1);
|
|
3878
|
+
}
|
|
3879
|
+
unlinkSync(target);
|
|
3880
|
+
console.log(chalk2.green(`\u2713 Removed: ${filename}`));
|
|
3881
|
+
});
|
|
3882
|
+
var cacheCmd = program.command("cache").description("Inspect and manage the context cache");
|
|
3883
|
+
cacheCmd.command("stats").description("Show cache hit rate, entries, and LRU sizes").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3884
|
+
const { getStore: getStore2 } = await import("./core-6IY5L6II.js");
|
|
3885
|
+
const { getContextCache, ContextCache } = await import("./cache-AK6CF3BC.js");
|
|
3886
|
+
const store = await getStore2(pathResolve(opts.project));
|
|
3887
|
+
try {
|
|
3888
|
+
ContextCache.ensureTables(store);
|
|
3889
|
+
const cache = getContextCache();
|
|
3890
|
+
const s = cache.getStats(store);
|
|
3891
|
+
const hitRatePct = (s.hitRate * 100).toFixed(1);
|
|
3892
|
+
const totalOps = s.totalHits + s.totalMisses;
|
|
3893
|
+
console.log(chalk2.bold("Cache stats"));
|
|
3894
|
+
console.log(chalk2.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
3895
|
+
console.log(
|
|
3896
|
+
` ${chalk2.dim("Hit rate")} ${chalk2.green(chalk2.bold(hitRatePct + "%"))} ${chalk2.dim(
|
|
3897
|
+
`(${s.totalHits} hits / ${totalOps} ops)`
|
|
3898
|
+
)}`
|
|
3899
|
+
);
|
|
3900
|
+
console.log(
|
|
3901
|
+
` ${chalk2.dim("Query cache")} ${chalk2.bold(String(s.queryEntries))} entries, ${chalk2.green(
|
|
3902
|
+
String(s.queryHits)
|
|
3903
|
+
)} hits, ${chalk2.dim(String(s.queryMisses) + " miss")}`
|
|
3904
|
+
);
|
|
3905
|
+
console.log(
|
|
3906
|
+
` ${chalk2.dim("Pattern cache")} ${chalk2.bold(String(s.patternEntries))} entries, ${chalk2.green(
|
|
3907
|
+
String(s.patternHits)
|
|
3908
|
+
)} hits, ${chalk2.dim(String(s.patternMisses) + " miss")}`
|
|
3909
|
+
);
|
|
3910
|
+
console.log(
|
|
3911
|
+
` ${chalk2.dim("Hot files")} ${chalk2.bold(String(s.hotFileCount))} warmed`
|
|
3912
|
+
);
|
|
3913
|
+
} finally {
|
|
3914
|
+
store.close();
|
|
3915
|
+
}
|
|
3916
|
+
});
|
|
3917
|
+
cacheCmd.command("clear").description("Flush all cache layers (query, pattern, hot files)").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3918
|
+
const { getStore: getStore2 } = await import("./core-6IY5L6II.js");
|
|
3919
|
+
const { getContextCache, ContextCache } = await import("./cache-AK6CF3BC.js");
|
|
3920
|
+
const store = await getStore2(pathResolve(opts.project));
|
|
3921
|
+
try {
|
|
3922
|
+
ContextCache.ensureTables(store);
|
|
3923
|
+
const cache = getContextCache();
|
|
3924
|
+
const before = cache.getStats(store);
|
|
3925
|
+
cache.clearAll(store);
|
|
3926
|
+
store.save();
|
|
3927
|
+
console.log(
|
|
3928
|
+
chalk2.green(
|
|
3929
|
+
`\u2713 Cleared ${before.queryEntries} query entries, ${before.patternEntries} pattern entries`
|
|
3930
|
+
)
|
|
3931
|
+
);
|
|
3932
|
+
} finally {
|
|
3933
|
+
store.close();
|
|
3934
|
+
}
|
|
3935
|
+
});
|
|
3936
|
+
cacheCmd.command("warm").description("Pre-warm hot file cache from access frequency (top-N)").option("-p, --project <path>", "Project directory", ".").option("-n, --limit <n>", "Number of files to warm", "20").action(async (opts) => {
|
|
3937
|
+
const { getStore: getStore2 } = await import("./core-6IY5L6II.js");
|
|
3938
|
+
const { getContextCache, ContextCache } = await import("./cache-AK6CF3BC.js");
|
|
3939
|
+
const store = await getStore2(pathResolve(opts.project));
|
|
3940
|
+
try {
|
|
3941
|
+
ContextCache.ensureTables(store);
|
|
3942
|
+
const cache = getContextCache();
|
|
3943
|
+
const topN = parseInt(opts.limit, 10) || 20;
|
|
3944
|
+
const count = cache.warmHotFiles(store, pathResolve(opts.project), topN);
|
|
3945
|
+
if (count === 0) {
|
|
3946
|
+
console.log(
|
|
3947
|
+
chalk2.dim(
|
|
3948
|
+
"No files to warm. Cache is empty \u2014 run a few Read-intercepted sessions first."
|
|
3949
|
+
)
|
|
3950
|
+
);
|
|
3951
|
+
} else {
|
|
3952
|
+
console.log(chalk2.green(`\u2713 Warmed ${count} hot file${count === 1 ? "" : "s"} into LRU`));
|
|
3953
|
+
}
|
|
3954
|
+
} finally {
|
|
3955
|
+
store.close();
|
|
3956
|
+
}
|
|
3957
|
+
});
|
|
3833
3958
|
program.parse();
|