engramx 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +201 -0
- package/README.md +55 -0
- package/dist/{aider-context-BC5R2ZTA.js → aider-context-J557IHIP.js} +1 -1
- package/dist/check-2Z3MPZEJ.js +12 -0
- package/dist/{chunk-C6GBUOAL.js → chunk-4XA6ENNL.js} +1 -1
- package/dist/{chunk-533LR4I7.js → chunk-G4U3QOOW.js} +13 -97
- package/dist/chunk-RM2TBOVW.js +121 -0
- package/dist/chunk-SMU4WR3D.js +187 -0
- package/dist/chunk-XFE6ZANP.js +99 -0
- package/dist/chunk-XVYE4OX2.js +232 -0
- package/dist/{chunk-SJT7VS2G.js → chunk-ZVWRIVWQ.js} +17 -0
- package/dist/cli.js +584 -317
- package/dist/{core-6IY5L6II.js → core-TSXA5XZH.js} +1 -1
- package/dist/{cursor-mdc-GJ7E5LDD.js → cursor-mdc-VEOFFDVO.js} +1 -1
- package/dist/{exporter-GWU2GF23.js → exporter-AWXS34AS.js} +1 -1
- package/dist/{importer-V62NGZRK.js → importer-3Q5M6QBL.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/install-YVMVCFQW.js +121 -0
- package/dist/notify-5POGKMRX.js +36 -0
- package/dist/report-C3GTM3HY.js +12 -0
- package/dist/serve.js +4 -3
- package/dist/{server-KUG7U6SG.js → server-A6MUVKQK.js} +5 -3
- package/dist/{windsurf-rules-C7SVDHBL.js → windsurf-rules-RWPKBHRD.js} +1 -1
- package/dist/wizard-AOXWMSXW.js +274 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
formatInstallDiff,
|
|
4
|
+
installEngramHooks,
|
|
5
|
+
uninstallEngramHooks
|
|
6
|
+
} from "./chunk-SMU4WR3D.js";
|
|
2
7
|
import {
|
|
3
8
|
ESTIMATED_TOKENS_PER_READ_DENY,
|
|
4
|
-
formatHudStatus,
|
|
5
9
|
formatStatsSummary,
|
|
6
|
-
getComponentStatus,
|
|
7
10
|
summarizeHookLog
|
|
8
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-XFE6ZANP.js";
|
|
12
|
+
import {
|
|
13
|
+
formatHudStatus,
|
|
14
|
+
getComponentStatus
|
|
15
|
+
} from "./chunk-G4U3QOOW.js";
|
|
9
16
|
import {
|
|
10
17
|
readConfig
|
|
11
18
|
} from "./chunk-22INHMKB.js";
|
|
@@ -18,11 +25,12 @@ import {
|
|
|
18
25
|
install,
|
|
19
26
|
status,
|
|
20
27
|
uninstall
|
|
21
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-4XA6ENNL.js";
|
|
22
29
|
import {
|
|
23
30
|
benchmark,
|
|
24
31
|
computeKeywordIDF,
|
|
25
32
|
extractFile,
|
|
33
|
+
formatThousands,
|
|
26
34
|
getDbPath,
|
|
27
35
|
getFileContext,
|
|
28
36
|
getStore,
|
|
@@ -35,7 +43,7 @@ import {
|
|
|
35
43
|
renderFileStructure,
|
|
36
44
|
stats,
|
|
37
45
|
toPosixPath
|
|
38
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-ZVWRIVWQ.js";
|
|
39
47
|
import "./chunk-PEH54LYC.js";
|
|
40
48
|
|
|
41
49
|
// src/cli.ts
|
|
@@ -50,7 +58,7 @@ import {
|
|
|
50
58
|
copyFileSync,
|
|
51
59
|
renameSync as renameSync2
|
|
52
60
|
} from "fs";
|
|
53
|
-
import { dirname as dirname4, join as join9, resolve as
|
|
61
|
+
import { dirname as dirname4, join as join9, resolve as pathResolve2 } from "path";
|
|
54
62
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
55
63
|
import { homedir } from "os";
|
|
56
64
|
|
|
@@ -1434,7 +1442,7 @@ async function warmAllProviders(projectRoot, enabledProviders) {
|
|
|
1434
1442
|
try {
|
|
1435
1443
|
const result = await withTimeout2(p.warmup(projectRoot), 5e3);
|
|
1436
1444
|
if (result && result.entries.length > 0) {
|
|
1437
|
-
const { getStore: getStore2 } = await import("./core-
|
|
1445
|
+
const { getStore: getStore2 } = await import("./core-TSXA5XZH.js");
|
|
1438
1446
|
const store = await getStore2(projectRoot);
|
|
1439
1447
|
try {
|
|
1440
1448
|
store.warmCache(
|
|
@@ -1969,6 +1977,219 @@ ${result.text}`;
|
|
|
1969
1977
|
return buildSessionContextResponse("UserPromptSubmit", text);
|
|
1970
1978
|
}
|
|
1971
1979
|
|
|
1980
|
+
// src/intercept/handlers/bash-postool.ts
|
|
1981
|
+
import { isAbsolute as isAbsolute2, resolve as pathResolve } from "path";
|
|
1982
|
+
var MAX_COMMAND_LEN = 500;
|
|
1983
|
+
var BASIC_UNSAFE = /[|&;()$`*?[\]{}"']/;
|
|
1984
|
+
var SUBSHELL = /\$\(|`|<\(|>\(/;
|
|
1985
|
+
function parseFileOps(command, cwd) {
|
|
1986
|
+
if (!command || typeof command !== "string") return [];
|
|
1987
|
+
if (command.length > MAX_COMMAND_LEN) return [];
|
|
1988
|
+
if (SUBSHELL.test(command)) return [];
|
|
1989
|
+
const trimmed = command.trim();
|
|
1990
|
+
if (!trimmed) return [];
|
|
1991
|
+
const redirectMatch = /\s+(>>?)\s+(\S+)\s*$/.exec(trimmed);
|
|
1992
|
+
if (redirectMatch) {
|
|
1993
|
+
const head = trimmed.slice(0, redirectMatch.index);
|
|
1994
|
+
const dest = redirectMatch[2];
|
|
1995
|
+
if (BASIC_UNSAFE.test(head)) return [];
|
|
1996
|
+
if (dest.startsWith("-") || dest.length === 0) return [];
|
|
1997
|
+
return [{ action: "reindex", path: absolutize(dest, cwd) }];
|
|
1998
|
+
}
|
|
1999
|
+
if (BASIC_UNSAFE.test(trimmed)) return [];
|
|
2000
|
+
const tokens = trimmed.split(/\s+/);
|
|
2001
|
+
if (tokens.length === 0) return [];
|
|
2002
|
+
const first = tokens[0];
|
|
2003
|
+
if (first === "git" && tokens.length >= 3) {
|
|
2004
|
+
const sub = tokens[1];
|
|
2005
|
+
if (sub === "rm") return parseRm(tokens.slice(2), cwd);
|
|
2006
|
+
if (sub === "mv") return parseMv(tokens.slice(2), cwd);
|
|
2007
|
+
return [];
|
|
2008
|
+
}
|
|
2009
|
+
if (first === "rm") return parseRm(tokens.slice(1), cwd);
|
|
2010
|
+
if (first === "mv") return parseMv(tokens.slice(1), cwd);
|
|
2011
|
+
if (first === "cp") return parseCp(tokens.slice(1), cwd);
|
|
2012
|
+
return [];
|
|
2013
|
+
}
|
|
2014
|
+
function absolutize(path2, cwd) {
|
|
2015
|
+
if (isAbsolute2(path2)) return path2;
|
|
2016
|
+
return pathResolve(cwd, path2);
|
|
2017
|
+
}
|
|
2018
|
+
function isFlagLike(tok) {
|
|
2019
|
+
return tok.startsWith("-");
|
|
2020
|
+
}
|
|
2021
|
+
function parseRm(args, cwd) {
|
|
2022
|
+
const paths = args.filter((t) => !isFlagLike(t));
|
|
2023
|
+
if (paths.length === 0) return [];
|
|
2024
|
+
return paths.map((p) => ({ action: "prune", path: absolutize(p, cwd) }));
|
|
2025
|
+
}
|
|
2026
|
+
function parseMv(args, cwd) {
|
|
2027
|
+
const paths = args.filter((t) => !isFlagLike(t));
|
|
2028
|
+
if (paths.length !== 2) return [];
|
|
2029
|
+
const [src, dst] = paths;
|
|
2030
|
+
return [
|
|
2031
|
+
{ action: "prune", path: absolutize(src, cwd) },
|
|
2032
|
+
{ action: "reindex", path: absolutize(dst, cwd) }
|
|
2033
|
+
];
|
|
2034
|
+
}
|
|
2035
|
+
function parseCp(args, cwd) {
|
|
2036
|
+
const paths = args.filter((t) => !isFlagLike(t));
|
|
2037
|
+
if (paths.length !== 2) return [];
|
|
2038
|
+
const [, dst] = paths;
|
|
2039
|
+
return [{ action: "reindex", path: absolutize(dst, cwd) }];
|
|
2040
|
+
}
|
|
2041
|
+
function handleBashPostTool(payload) {
|
|
2042
|
+
if (payload.tool_name !== "Bash") return { ops: [] };
|
|
2043
|
+
const cmd = payload.tool_input?.command;
|
|
2044
|
+
if (!cmd || typeof cmd !== "string") return { ops: [] };
|
|
2045
|
+
try {
|
|
2046
|
+
const ops = parseFileOps(cmd, payload.cwd);
|
|
2047
|
+
return { ops };
|
|
2048
|
+
} catch {
|
|
2049
|
+
return { ops: [] };
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
// src/watcher.ts
|
|
2054
|
+
import { watch, existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
2055
|
+
import { resolve as resolve3, relative as relative3, extname } from "path";
|
|
2056
|
+
var WATCHABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2057
|
+
".ts",
|
|
2058
|
+
".tsx",
|
|
2059
|
+
".js",
|
|
2060
|
+
".jsx",
|
|
2061
|
+
".py",
|
|
2062
|
+
".go",
|
|
2063
|
+
".rs",
|
|
2064
|
+
".java",
|
|
2065
|
+
".c",
|
|
2066
|
+
".cpp",
|
|
2067
|
+
".cs",
|
|
2068
|
+
".rb"
|
|
2069
|
+
]);
|
|
2070
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2071
|
+
".engram",
|
|
2072
|
+
"node_modules",
|
|
2073
|
+
".git",
|
|
2074
|
+
"dist",
|
|
2075
|
+
"build",
|
|
2076
|
+
".next",
|
|
2077
|
+
"__pycache__",
|
|
2078
|
+
".venv",
|
|
2079
|
+
"target",
|
|
2080
|
+
"vendor"
|
|
2081
|
+
]);
|
|
2082
|
+
var DEBOUNCE_MS = 300;
|
|
2083
|
+
function shouldIgnore(relPath) {
|
|
2084
|
+
const parts = relPath.split(/[/\\]/);
|
|
2085
|
+
return parts.some((p) => IGNORED_DIRS.has(p));
|
|
2086
|
+
}
|
|
2087
|
+
async function syncFile(absPath, projectRoot) {
|
|
2088
|
+
const ext = extname(absPath).toLowerCase();
|
|
2089
|
+
if (!WATCHABLE_EXTENSIONS.has(ext)) return { action: "skipped", count: 0 };
|
|
2090
|
+
const relPath = toPosixPath(relative3(projectRoot, absPath));
|
|
2091
|
+
if (shouldIgnore(relPath)) return { action: "skipped", count: 0 };
|
|
2092
|
+
if (!existsSync6(absPath)) {
|
|
2093
|
+
const store2 = await getStore(projectRoot);
|
|
2094
|
+
try {
|
|
2095
|
+
const prior = store2.countBySourceFile(relPath);
|
|
2096
|
+
if (prior === 0) return { action: "skipped", count: 0 };
|
|
2097
|
+
store2.deleteBySourceFile(relPath);
|
|
2098
|
+
return { action: "pruned", count: prior };
|
|
2099
|
+
} finally {
|
|
2100
|
+
store2.close();
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
try {
|
|
2104
|
+
if (statSync2(absPath).isDirectory()) return { action: "skipped", count: 0 };
|
|
2105
|
+
} catch {
|
|
2106
|
+
return { action: "skipped", count: 0 };
|
|
2107
|
+
}
|
|
2108
|
+
const store = await getStore(projectRoot);
|
|
2109
|
+
try {
|
|
2110
|
+
store.deleteBySourceFile(relPath);
|
|
2111
|
+
const { nodes, edges } = extractFile(absPath, projectRoot);
|
|
2112
|
+
if (nodes.length > 0 || edges.length > 0) {
|
|
2113
|
+
store.bulkUpsert(nodes, edges);
|
|
2114
|
+
}
|
|
2115
|
+
return { action: "indexed", count: nodes.length };
|
|
2116
|
+
} finally {
|
|
2117
|
+
store.close();
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
function formatReindexLine(result, displayPath) {
|
|
2121
|
+
if (result.action === "indexed") {
|
|
2122
|
+
return `engram: reindexed ${displayPath} (${formatThousands(result.count)} nodes)`;
|
|
2123
|
+
}
|
|
2124
|
+
if (result.action === "pruned") {
|
|
2125
|
+
return `engram: pruned ${displayPath} (${formatThousands(result.count)} nodes)`;
|
|
2126
|
+
}
|
|
2127
|
+
return null;
|
|
2128
|
+
}
|
|
2129
|
+
async function runReindexHook(payload) {
|
|
2130
|
+
try {
|
|
2131
|
+
if (payload === null || typeof payload !== "object") return;
|
|
2132
|
+
const p = payload;
|
|
2133
|
+
const cwd = p.cwd;
|
|
2134
|
+
if (typeof cwd !== "string" || !isValidCwd(cwd)) return;
|
|
2135
|
+
const toolInput = p.tool_input;
|
|
2136
|
+
if (toolInput === null || typeof toolInput !== "object") return;
|
|
2137
|
+
const filePath = toolInput.file_path;
|
|
2138
|
+
if (typeof filePath !== "string" || filePath.length === 0) return;
|
|
2139
|
+
const absPath = resolve3(cwd, filePath);
|
|
2140
|
+
const projectRoot = findProjectRoot(absPath);
|
|
2141
|
+
if (projectRoot === null) return;
|
|
2142
|
+
await syncFile(absPath, projectRoot);
|
|
2143
|
+
} catch {
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
function watchProject(projectRoot, options = {}) {
|
|
2147
|
+
const root = resolve3(projectRoot);
|
|
2148
|
+
const controller = new AbortController();
|
|
2149
|
+
if (!existsSync6(getDbPath(root))) {
|
|
2150
|
+
throw new Error(
|
|
2151
|
+
`engram: no graph found at ${root}. Run 'engram init' first.`
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
const debounceTimers = /* @__PURE__ */ new Map();
|
|
2155
|
+
const watcher = watch(root, { recursive: true, signal: controller.signal });
|
|
2156
|
+
const handleEvent = (_eventType, filename) => {
|
|
2157
|
+
if (typeof filename !== "string") return;
|
|
2158
|
+
const absPath = resolve3(root, filename);
|
|
2159
|
+
const relPath = toPosixPath(relative3(root, absPath));
|
|
2160
|
+
if (shouldIgnore(relPath)) return;
|
|
2161
|
+
const ext = extname(filename).toLowerCase();
|
|
2162
|
+
if (!WATCHABLE_EXTENSIONS.has(ext)) return;
|
|
2163
|
+
const existing = debounceTimers.get(absPath);
|
|
2164
|
+
if (existing) clearTimeout(existing);
|
|
2165
|
+
debounceTimers.set(
|
|
2166
|
+
absPath,
|
|
2167
|
+
setTimeout(async () => {
|
|
2168
|
+
debounceTimers.delete(absPath);
|
|
2169
|
+
try {
|
|
2170
|
+
const result = await syncFile(absPath, root);
|
|
2171
|
+
if (result.action === "indexed" && result.count > 0) {
|
|
2172
|
+
options.onReindex?.(relPath, result.count);
|
|
2173
|
+
} else if (result.action === "pruned") {
|
|
2174
|
+
options.onDelete?.(relPath, result.count);
|
|
2175
|
+
}
|
|
2176
|
+
} catch (err) {
|
|
2177
|
+
options.onError?.(
|
|
2178
|
+
err instanceof Error ? err : new Error(String(err))
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
}, DEBOUNCE_MS)
|
|
2182
|
+
);
|
|
2183
|
+
};
|
|
2184
|
+
watcher.on("change", handleEvent);
|
|
2185
|
+
watcher.on("rename", handleEvent);
|
|
2186
|
+
watcher.on("error", (err) => {
|
|
2187
|
+
options.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
2188
|
+
});
|
|
2189
|
+
options.onReady?.();
|
|
2190
|
+
return controller;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
1972
2193
|
// src/intercept/handlers/post-tool.ts
|
|
1973
2194
|
function extractFilePath(toolName, toolInput) {
|
|
1974
2195
|
if (!toolInput) return void 0;
|
|
@@ -2019,13 +2240,34 @@ async function handlePostTool(payload) {
|
|
|
2019
2240
|
outputSize,
|
|
2020
2241
|
success: !hasError
|
|
2021
2242
|
});
|
|
2243
|
+
if (toolName === "Bash" && !hasError && process.env.ENGRAM_AUTO_REINDEX === "1") {
|
|
2244
|
+
void reindexBashOps(payload, projectRoot).catch(() => {
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2022
2247
|
} catch {
|
|
2023
2248
|
}
|
|
2024
2249
|
return PASSTHROUGH;
|
|
2025
2250
|
}
|
|
2251
|
+
async function reindexBashOps(payload, projectRoot) {
|
|
2252
|
+
const result = handleBashPostTool({
|
|
2253
|
+
tool_name: payload.tool_name ?? "",
|
|
2254
|
+
tool_input: payload.tool_input ?? {},
|
|
2255
|
+
cwd: payload.cwd
|
|
2256
|
+
});
|
|
2257
|
+
if (result.ops.length === 0) return;
|
|
2258
|
+
for (const op of result.ops) {
|
|
2259
|
+
await runOp(op, projectRoot);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
async function runOp(op, projectRoot) {
|
|
2263
|
+
try {
|
|
2264
|
+
await syncFile(op.path, projectRoot);
|
|
2265
|
+
} catch {
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2026
2268
|
|
|
2027
2269
|
// src/intercept/handlers/pre-compact.ts
|
|
2028
|
-
import { basename as basename2, resolve as
|
|
2270
|
+
import { basename as basename2, resolve as resolve4 } from "path";
|
|
2029
2271
|
var MAX_GOD_NODES_COMPACT = 5;
|
|
2030
2272
|
var MAX_LANDMINES_COMPACT = 3;
|
|
2031
2273
|
function formatCompactBrief(args) {
|
|
@@ -2075,7 +2317,7 @@ async function handlePreCompact(payload) {
|
|
|
2075
2317
|
}))
|
|
2076
2318
|
]);
|
|
2077
2319
|
if (graphStats.nodes === 0 && gods.length === 0) return PASSTHROUGH;
|
|
2078
|
-
const projectName = basename2(
|
|
2320
|
+
const projectName = basename2(resolve4(projectRoot));
|
|
2079
2321
|
const text = formatCompactBrief({
|
|
2080
2322
|
projectName,
|
|
2081
2323
|
nodeCount: graphStats.nodes,
|
|
@@ -2097,7 +2339,7 @@ async function handlePreCompact(payload) {
|
|
|
2097
2339
|
}
|
|
2098
2340
|
|
|
2099
2341
|
// src/intercept/handlers/cwd-changed.ts
|
|
2100
|
-
import { basename as basename3, resolve as
|
|
2342
|
+
import { basename as basename3, resolve as resolve5 } from "path";
|
|
2101
2343
|
var MAX_GOD_NODES_SWITCH = 5;
|
|
2102
2344
|
async function handleCwdChanged(payload) {
|
|
2103
2345
|
if (payload.hook_event_name !== "CwdChanged") return PASSTHROUGH;
|
|
@@ -2121,7 +2363,7 @@ async function handleCwdChanged(payload) {
|
|
|
2121
2363
|
}))
|
|
2122
2364
|
]);
|
|
2123
2365
|
if (graphStats.nodes === 0) return PASSTHROUGH;
|
|
2124
|
-
const projectName = basename3(
|
|
2366
|
+
const projectName = basename3(resolve5(projectRoot));
|
|
2125
2367
|
const lines = [];
|
|
2126
2368
|
lines.push(
|
|
2127
2369
|
`[engram] Project switched to ${projectName} (${graphStats.nodes} nodes, ${graphStats.edges} edges)`
|
|
@@ -2238,106 +2480,6 @@ function extractPreToolDecision(result) {
|
|
|
2238
2480
|
return "passthrough";
|
|
2239
2481
|
}
|
|
2240
2482
|
|
|
2241
|
-
// src/watcher.ts
|
|
2242
|
-
import { watch, existsSync as existsSync6, statSync as statSync2 } from "fs";
|
|
2243
|
-
import { resolve as resolve5, relative as relative3, extname } from "path";
|
|
2244
|
-
var WATCHABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2245
|
-
".ts",
|
|
2246
|
-
".tsx",
|
|
2247
|
-
".js",
|
|
2248
|
-
".jsx",
|
|
2249
|
-
".py",
|
|
2250
|
-
".go",
|
|
2251
|
-
".rs",
|
|
2252
|
-
".java",
|
|
2253
|
-
".c",
|
|
2254
|
-
".cpp",
|
|
2255
|
-
".cs",
|
|
2256
|
-
".rb"
|
|
2257
|
-
]);
|
|
2258
|
-
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
2259
|
-
".engram",
|
|
2260
|
-
"node_modules",
|
|
2261
|
-
".git",
|
|
2262
|
-
"dist",
|
|
2263
|
-
"build",
|
|
2264
|
-
".next",
|
|
2265
|
-
"__pycache__",
|
|
2266
|
-
".venv",
|
|
2267
|
-
"target",
|
|
2268
|
-
"vendor"
|
|
2269
|
-
]);
|
|
2270
|
-
var DEBOUNCE_MS = 300;
|
|
2271
|
-
function shouldIgnore(relPath) {
|
|
2272
|
-
const parts = relPath.split(/[/\\]/);
|
|
2273
|
-
return parts.some((p) => IGNORED_DIRS.has(p));
|
|
2274
|
-
}
|
|
2275
|
-
async function reindexFile(absPath, projectRoot) {
|
|
2276
|
-
const ext = extname(absPath).toLowerCase();
|
|
2277
|
-
if (!WATCHABLE_EXTENSIONS.has(ext)) return 0;
|
|
2278
|
-
if (!existsSync6(absPath)) return 0;
|
|
2279
|
-
try {
|
|
2280
|
-
if (statSync2(absPath).isDirectory()) return 0;
|
|
2281
|
-
} catch {
|
|
2282
|
-
return 0;
|
|
2283
|
-
}
|
|
2284
|
-
const relPath = toPosixPath(relative3(projectRoot, absPath));
|
|
2285
|
-
if (shouldIgnore(relPath)) return 0;
|
|
2286
|
-
const store = await getStore(projectRoot);
|
|
2287
|
-
try {
|
|
2288
|
-
store.deleteBySourceFile(relPath);
|
|
2289
|
-
const { nodes, edges } = extractFile(absPath, projectRoot);
|
|
2290
|
-
if (nodes.length > 0 || edges.length > 0) {
|
|
2291
|
-
store.bulkUpsert(nodes, edges);
|
|
2292
|
-
}
|
|
2293
|
-
return nodes.length;
|
|
2294
|
-
} finally {
|
|
2295
|
-
store.close();
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
function watchProject(projectRoot, options = {}) {
|
|
2299
|
-
const root = resolve5(projectRoot);
|
|
2300
|
-
const controller = new AbortController();
|
|
2301
|
-
if (!existsSync6(getDbPath(root))) {
|
|
2302
|
-
throw new Error(
|
|
2303
|
-
`engram: no graph found at ${root}. Run 'engram init' first.`
|
|
2304
|
-
);
|
|
2305
|
-
}
|
|
2306
|
-
const debounceTimers = /* @__PURE__ */ new Map();
|
|
2307
|
-
const watcher = watch(root, { recursive: true, signal: controller.signal });
|
|
2308
|
-
watcher.on("change", (_eventType, filename) => {
|
|
2309
|
-
if (typeof filename !== "string") return;
|
|
2310
|
-
const absPath = resolve5(root, filename);
|
|
2311
|
-
const relPath = toPosixPath(relative3(root, absPath));
|
|
2312
|
-
if (shouldIgnore(relPath)) return;
|
|
2313
|
-
const ext = extname(filename).toLowerCase();
|
|
2314
|
-
if (!WATCHABLE_EXTENSIONS.has(ext)) return;
|
|
2315
|
-
const existing = debounceTimers.get(absPath);
|
|
2316
|
-
if (existing) clearTimeout(existing);
|
|
2317
|
-
debounceTimers.set(
|
|
2318
|
-
absPath,
|
|
2319
|
-
setTimeout(async () => {
|
|
2320
|
-
debounceTimers.delete(absPath);
|
|
2321
|
-
try {
|
|
2322
|
-
const count = await reindexFile(absPath, root);
|
|
2323
|
-
if (count > 0) {
|
|
2324
|
-
options.onReindex?.(relPath, count);
|
|
2325
|
-
}
|
|
2326
|
-
} catch (err) {
|
|
2327
|
-
options.onError?.(
|
|
2328
|
-
err instanceof Error ? err : new Error(String(err))
|
|
2329
|
-
);
|
|
2330
|
-
}
|
|
2331
|
-
}, DEBOUNCE_MS)
|
|
2332
|
-
);
|
|
2333
|
-
});
|
|
2334
|
-
watcher.on("error", (err) => {
|
|
2335
|
-
options.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
2336
|
-
});
|
|
2337
|
-
options.onReady?.();
|
|
2338
|
-
return controller;
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
2483
|
// src/dashboard.ts
|
|
2342
2484
|
import chalk from "chalk";
|
|
2343
2485
|
import { existsSync as existsSync7, statSync as statSync3 } from "fs";
|
|
@@ -2353,9 +2495,7 @@ function bar(pct, width = 20) {
|
|
|
2353
2495
|
const empty = width - filled;
|
|
2354
2496
|
return AMBER("\u2588".repeat(filled)) + DIM("\u2591".repeat(empty));
|
|
2355
2497
|
}
|
|
2356
|
-
|
|
2357
|
-
return n.toLocaleString();
|
|
2358
|
-
}
|
|
2498
|
+
var fmt = formatThousands;
|
|
2359
2499
|
function topFiles(entries, n) {
|
|
2360
2500
|
const counts = /* @__PURE__ */ new Map();
|
|
2361
2501
|
for (const e of entries) {
|
|
@@ -2522,156 +2662,6 @@ async function handleCursorBeforeReadFile(payload) {
|
|
|
2522
2662
|
}
|
|
2523
2663
|
}
|
|
2524
2664
|
|
|
2525
|
-
// src/intercept/installer.ts
|
|
2526
|
-
var ENGRAM_HOOK_EVENTS = [
|
|
2527
|
-
"PreToolUse",
|
|
2528
|
-
"PostToolUse",
|
|
2529
|
-
"SessionStart",
|
|
2530
|
-
"UserPromptSubmit",
|
|
2531
|
-
"PreCompact",
|
|
2532
|
-
"CwdChanged"
|
|
2533
|
-
];
|
|
2534
|
-
var ENGRAM_PRETOOL_MATCHER = "Read|Edit|Write|Bash";
|
|
2535
|
-
var DEFAULT_ENGRAM_COMMAND = "engram intercept";
|
|
2536
|
-
var DEFAULT_HOOK_TIMEOUT_SEC = 5;
|
|
2537
|
-
var DEFAULT_STATUSLINE_COMMAND = "engram hud-label";
|
|
2538
|
-
function buildEngramHookEntries(command = DEFAULT_ENGRAM_COMMAND, timeout = DEFAULT_HOOK_TIMEOUT_SEC) {
|
|
2539
|
-
const baseCmd = {
|
|
2540
|
-
type: "command",
|
|
2541
|
-
command,
|
|
2542
|
-
timeout
|
|
2543
|
-
};
|
|
2544
|
-
return {
|
|
2545
|
-
PreToolUse: {
|
|
2546
|
-
matcher: ENGRAM_PRETOOL_MATCHER,
|
|
2547
|
-
hooks: [baseCmd]
|
|
2548
|
-
},
|
|
2549
|
-
PostToolUse: {
|
|
2550
|
-
// Match all tools — PostToolUse is an observer for any completion.
|
|
2551
|
-
matcher: ".*",
|
|
2552
|
-
hooks: [baseCmd]
|
|
2553
|
-
},
|
|
2554
|
-
SessionStart: {
|
|
2555
|
-
// No matcher — SessionStart has no tool name.
|
|
2556
|
-
hooks: [baseCmd]
|
|
2557
|
-
},
|
|
2558
|
-
UserPromptSubmit: {
|
|
2559
|
-
// No matcher — UserPromptSubmit has no tool name.
|
|
2560
|
-
hooks: [baseCmd]
|
|
2561
|
-
},
|
|
2562
|
-
PreCompact: {
|
|
2563
|
-
// No matcher — PreCompact has no tool name.
|
|
2564
|
-
hooks: [baseCmd]
|
|
2565
|
-
},
|
|
2566
|
-
CwdChanged: {
|
|
2567
|
-
// No matcher — CwdChanged has no tool name.
|
|
2568
|
-
hooks: [baseCmd]
|
|
2569
|
-
}
|
|
2570
|
-
};
|
|
2571
|
-
}
|
|
2572
|
-
function isEngramHookEntry(entry) {
|
|
2573
|
-
if (entry === null || typeof entry !== "object") return false;
|
|
2574
|
-
const e = entry;
|
|
2575
|
-
if (!Array.isArray(e.hooks)) return false;
|
|
2576
|
-
for (const h of e.hooks) {
|
|
2577
|
-
if (h === null || typeof h !== "object") continue;
|
|
2578
|
-
const cmd = h.command;
|
|
2579
|
-
if (typeof cmd === "string" && cmd.includes("engram intercept")) {
|
|
2580
|
-
return true;
|
|
2581
|
-
}
|
|
2582
|
-
}
|
|
2583
|
-
return false;
|
|
2584
|
-
}
|
|
2585
|
-
function installEngramHooks(settings, command = DEFAULT_ENGRAM_COMMAND) {
|
|
2586
|
-
const entries = buildEngramHookEntries(command);
|
|
2587
|
-
const added = [];
|
|
2588
|
-
const alreadyPresent = [];
|
|
2589
|
-
const hooksClone = {};
|
|
2590
|
-
const existingHooks = settings.hooks ?? {};
|
|
2591
|
-
for (const [key, value] of Object.entries(existingHooks)) {
|
|
2592
|
-
if (Array.isArray(value)) {
|
|
2593
|
-
hooksClone[key] = value.map((entry) => ({ ...entry }));
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
for (const event of ENGRAM_HOOK_EVENTS) {
|
|
2597
|
-
const eventArr = hooksClone[event] ?? [];
|
|
2598
|
-
const hasEngram = eventArr.some((e) => isEngramHookEntry(e));
|
|
2599
|
-
if (hasEngram) {
|
|
2600
|
-
alreadyPresent.push(event);
|
|
2601
|
-
hooksClone[event] = eventArr;
|
|
2602
|
-
continue;
|
|
2603
|
-
}
|
|
2604
|
-
hooksClone[event] = [...eventArr, entries[event]];
|
|
2605
|
-
added.push(event);
|
|
2606
|
-
}
|
|
2607
|
-
const hasStatusLine = settings.statusLine && typeof settings.statusLine === "object" && typeof settings.statusLine.command === "string" && settings.statusLine.command.length > 0;
|
|
2608
|
-
const statusLineAdded = !hasStatusLine;
|
|
2609
|
-
const statusLine = hasStatusLine ? settings.statusLine : { type: "command", command: DEFAULT_STATUSLINE_COMMAND };
|
|
2610
|
-
return {
|
|
2611
|
-
updated: { ...settings, hooks: hooksClone, statusLine },
|
|
2612
|
-
added,
|
|
2613
|
-
alreadyPresent,
|
|
2614
|
-
statusLineAdded
|
|
2615
|
-
};
|
|
2616
|
-
}
|
|
2617
|
-
function uninstallEngramHooks(settings) {
|
|
2618
|
-
const removed = [];
|
|
2619
|
-
const existingHooks = settings.hooks ?? {};
|
|
2620
|
-
const hooksClone = {};
|
|
2621
|
-
for (const [event, arr] of Object.entries(existingHooks)) {
|
|
2622
|
-
if (!Array.isArray(arr)) continue;
|
|
2623
|
-
const filtered = arr.filter((entry) => !isEngramHookEntry(entry));
|
|
2624
|
-
if (filtered.length !== arr.length && isKnownEngramEvent(event)) {
|
|
2625
|
-
removed.push(event);
|
|
2626
|
-
}
|
|
2627
|
-
if (filtered.length > 0) {
|
|
2628
|
-
hooksClone[event] = filtered;
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
const updatedSettings = { ...settings };
|
|
2632
|
-
if (Object.keys(hooksClone).length === 0) {
|
|
2633
|
-
delete updatedSettings.hooks;
|
|
2634
|
-
} else {
|
|
2635
|
-
updatedSettings.hooks = hooksClone;
|
|
2636
|
-
}
|
|
2637
|
-
const statusLineRemoved = typeof updatedSettings.statusLine?.command === "string" && updatedSettings.statusLine.command.includes("engram hud-label");
|
|
2638
|
-
if (statusLineRemoved) {
|
|
2639
|
-
delete updatedSettings.statusLine;
|
|
2640
|
-
}
|
|
2641
|
-
return { updated: updatedSettings, removed, statusLineRemoved };
|
|
2642
|
-
}
|
|
2643
|
-
function isKnownEngramEvent(event) {
|
|
2644
|
-
return ENGRAM_HOOK_EVENTS.includes(event);
|
|
2645
|
-
}
|
|
2646
|
-
function formatInstallDiff(before, after) {
|
|
2647
|
-
const lines = [];
|
|
2648
|
-
const beforeHooks = before.hooks ?? {};
|
|
2649
|
-
const afterHooks = after.hooks ?? {};
|
|
2650
|
-
for (const event of ENGRAM_HOOK_EVENTS) {
|
|
2651
|
-
const beforeArr = beforeHooks[event] ?? [];
|
|
2652
|
-
const afterArr = afterHooks[event] ?? [];
|
|
2653
|
-
if (beforeArr.length === afterArr.length) continue;
|
|
2654
|
-
lines.push(`+ ${event}: ${beforeArr.length} \u2192 ${afterArr.length} entries`);
|
|
2655
|
-
const added = afterArr.filter((entry) => isEngramHookEntry(entry));
|
|
2656
|
-
const beforeHasEngram = beforeArr.some((entry) => isEngramHookEntry(entry));
|
|
2657
|
-
if (!beforeHasEngram && added.length > 0) {
|
|
2658
|
-
for (const entry of added) {
|
|
2659
|
-
const matcher = entry.matcher ? ` matcher=${JSON.stringify(entry.matcher)}` : "";
|
|
2660
|
-
const cmds = entry.hooks.map((h) => h.command).join(", ");
|
|
2661
|
-
lines.push(` + {${matcher} command="${cmds}"}`);
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
}
|
|
2665
|
-
const hadStatusLine = before.statusLine?.command;
|
|
2666
|
-
const hasStatusLineNow = after.statusLine?.command;
|
|
2667
|
-
if (!hadStatusLine && hasStatusLineNow?.includes("engram hud-label")) {
|
|
2668
|
-
lines.push(`+ statusLine: engram hud-label (HUD enabled)`);
|
|
2669
|
-
} else if (hadStatusLine?.includes("engram hud-label") && !hasStatusLineNow) {
|
|
2670
|
-
lines.push(`- statusLine: engram hud-label (HUD removed)`);
|
|
2671
|
-
}
|
|
2672
|
-
return lines.length > 0 ? lines.join("\n") : "(no changes)";
|
|
2673
|
-
}
|
|
2674
|
-
|
|
2675
2665
|
// src/intercept/memory-md.ts
|
|
2676
2666
|
import {
|
|
2677
2667
|
existsSync as existsSync8,
|
|
@@ -2786,6 +2776,9 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2786
2776
|
).option("--from-ccs", "Import .context/index.md (CCS) into graph after init").option(
|
|
2787
2777
|
"--incremental",
|
|
2788
2778
|
"Skip unchanged files (mtime-based). Dramatically faster on re-index of large repos."
|
|
2779
|
+
).option(
|
|
2780
|
+
"--with-hook",
|
|
2781
|
+
"Also install the Sentinel hook into Claude Code settings.local.json (idempotent)"
|
|
2789
2782
|
).action(async (projectPath, opts) => {
|
|
2790
2783
|
console.log(chalk2.dim(opts.incremental ? "\u{1F50D} Scanning changed files..." : "\u{1F50D} Scanning codebase..."));
|
|
2791
2784
|
const result = await init(projectPath, {
|
|
@@ -2796,7 +2789,7 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2796
2789
|
chalk2.green("\u{1F333} AST extraction complete") + chalk2.dim(` (${result.timeMs}ms, 0 tokens used)`)
|
|
2797
2790
|
);
|
|
2798
2791
|
console.log(
|
|
2799
|
-
` ${chalk2.bold(String(result.nodes))} nodes, ${chalk2.bold(String(result.edges))} edges from ${chalk2.bold(String(result.fileCount))} files (${result.totalLines
|
|
2792
|
+
` ${chalk2.bold(String(result.nodes))} nodes, ${chalk2.bold(String(result.edges))} edges from ${chalk2.bold(String(result.fileCount))} files (${formatThousands(result.totalLines)} lines)`
|
|
2800
2793
|
);
|
|
2801
2794
|
if (result.incremental && result.skippedFiles && result.skippedFiles > 0) {
|
|
2802
2795
|
console.log(chalk2.dim(` ${result.skippedFiles} unchanged files skipped (incremental mode)`));
|
|
@@ -2813,12 +2806,12 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2813
2806
|
\u{1F4CA} Token savings: ${chalk2.bold(bench.reductionVsRelevant + "x")} fewer tokens vs relevant files (${bench.reductionVsFull}x vs full corpus)`)
|
|
2814
2807
|
);
|
|
2815
2808
|
console.log(
|
|
2816
|
-
chalk2.dim(` Full corpus: ~${bench.naiveFullCorpus
|
|
2809
|
+
chalk2.dim(` Full corpus: ~${formatThousands(bench.naiveFullCorpus)} tokens | Graph query: ~${formatThousands(bench.avgQueryTokens)} tokens`)
|
|
2817
2810
|
);
|
|
2818
2811
|
}
|
|
2819
2812
|
console.log(chalk2.green("\n\u2705 Ready. Your AI now has persistent memory."));
|
|
2820
2813
|
console.log(chalk2.dim(" Graph stored in .engram/graph.db"));
|
|
2821
|
-
const resolvedProject =
|
|
2814
|
+
const resolvedProject = pathResolve2(projectPath);
|
|
2822
2815
|
const localSettings = join9(resolvedProject, ".claude", "settings.local.json");
|
|
2823
2816
|
const projectSettings = join9(resolvedProject, ".claude", "settings.json");
|
|
2824
2817
|
const hasHooks = existsSync9(localSettings) && readFileSync5(localSettings, "utf-8").includes("engram intercept") || existsSync9(projectSettings) && readFileSync5(projectSettings, "utf-8").includes("engram intercept");
|
|
@@ -2834,9 +2827,59 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2834
2827
|
)
|
|
2835
2828
|
);
|
|
2836
2829
|
}
|
|
2830
|
+
if (opts.withHook) {
|
|
2831
|
+
const localSettingsPath = join9(
|
|
2832
|
+
pathResolve2(projectPath),
|
|
2833
|
+
".claude",
|
|
2834
|
+
"settings.local.json"
|
|
2835
|
+
);
|
|
2836
|
+
let settings = {};
|
|
2837
|
+
if (existsSync9(localSettingsPath)) {
|
|
2838
|
+
try {
|
|
2839
|
+
const raw = readFileSync5(localSettingsPath, "utf-8");
|
|
2840
|
+
settings = raw.trim() ? JSON.parse(raw) : {};
|
|
2841
|
+
} catch {
|
|
2842
|
+
console.log(
|
|
2843
|
+
chalk2.yellow(
|
|
2844
|
+
"\n \u26A0 --with-hook: settings.local.json is invalid JSON, skipping hook install."
|
|
2845
|
+
)
|
|
2846
|
+
);
|
|
2847
|
+
settings = {};
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
const hookResult = installEngramHooks(settings);
|
|
2851
|
+
if (hookResult.added.length > 0 || hookResult.statusLineAdded) {
|
|
2852
|
+
try {
|
|
2853
|
+
mkdirSync(dirname4(localSettingsPath), { recursive: true });
|
|
2854
|
+
writeFileSync2(
|
|
2855
|
+
localSettingsPath,
|
|
2856
|
+
JSON.stringify(hookResult.updated, null, 2) + "\n"
|
|
2857
|
+
);
|
|
2858
|
+
console.log(
|
|
2859
|
+
chalk2.green(
|
|
2860
|
+
`
|
|
2861
|
+
\u2705 --with-hook: installed ${hookResult.added.length} hook event${hookResult.added.length === 1 ? "" : "s"} into .claude/settings.local.json`
|
|
2862
|
+
)
|
|
2863
|
+
);
|
|
2864
|
+
} catch (err) {
|
|
2865
|
+
console.log(
|
|
2866
|
+
chalk2.yellow(
|
|
2867
|
+
`
|
|
2868
|
+
\u26A0 --with-hook: write failed (${err.message})`
|
|
2869
|
+
)
|
|
2870
|
+
);
|
|
2871
|
+
}
|
|
2872
|
+
} else {
|
|
2873
|
+
console.log(
|
|
2874
|
+
chalk2.dim(
|
|
2875
|
+
"\n --with-hook: Sentinel hook already installed, nothing to do."
|
|
2876
|
+
)
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2837
2880
|
if (opts.fromCcs) {
|
|
2838
|
-
const { importCcs } = await import("./importer-
|
|
2839
|
-
const resolvedProjectPath =
|
|
2881
|
+
const { importCcs } = await import("./importer-3Q5M6QBL.js");
|
|
2882
|
+
const resolvedProjectPath = pathResolve2(projectPath);
|
|
2840
2883
|
const ccsResult = await importCcs(resolvedProjectPath);
|
|
2841
2884
|
if (ccsResult.nodesCreated > 0) {
|
|
2842
2885
|
console.log(
|
|
@@ -2850,7 +2893,7 @@ program.command("init").description("Scan codebase and build knowledge graph (ze
|
|
|
2850
2893
|
}
|
|
2851
2894
|
});
|
|
2852
2895
|
program.command("watch").description("Watch project for file changes and re-index incrementally").argument("[path]", "Project directory", ".").action(async (projectPath) => {
|
|
2853
|
-
const resolvedPath =
|
|
2896
|
+
const resolvedPath = pathResolve2(projectPath);
|
|
2854
2897
|
console.log(
|
|
2855
2898
|
chalk2.dim("\u{1F441} Watching ") + chalk2.white(resolvedPath) + chalk2.dim(" for changes...")
|
|
2856
2899
|
);
|
|
@@ -2860,6 +2903,11 @@ program.command("watch").description("Watch project for file changes and re-inde
|
|
|
2860
2903
|
chalk2.green(" \u21BB ") + chalk2.white(filePath) + chalk2.dim(` (${nodeCount} nodes)`)
|
|
2861
2904
|
);
|
|
2862
2905
|
},
|
|
2906
|
+
onDelete: (filePath, prunedCount) => {
|
|
2907
|
+
console.log(
|
|
2908
|
+
chalk2.yellow(" \xD7 ") + chalk2.white(filePath) + chalk2.dim(` pruned (${prunedCount} nodes)`)
|
|
2909
|
+
);
|
|
2910
|
+
},
|
|
2863
2911
|
onError: (err) => {
|
|
2864
2912
|
console.error(chalk2.red(" \u2717 ") + err.message);
|
|
2865
2913
|
},
|
|
@@ -2875,8 +2923,68 @@ program.command("watch").description("Watch project for file changes and re-inde
|
|
|
2875
2923
|
await new Promise(() => {
|
|
2876
2924
|
});
|
|
2877
2925
|
});
|
|
2926
|
+
program.command("reindex").description("Re-index a single file into the knowledge graph").argument("<file>", "File path (absolute or relative to --project)").option("-p, --project <path>", "Project directory", ".").option("--verbose", "Print stack traces on error", false).action(
|
|
2927
|
+
async (file, opts) => {
|
|
2928
|
+
const root = pathResolve2(opts.project);
|
|
2929
|
+
if (!existsSync9(join9(root, ".engram", "graph.db"))) {
|
|
2930
|
+
console.error(
|
|
2931
|
+
`engram: no graph found at ${root}. Run 'engram init' first.`
|
|
2932
|
+
);
|
|
2933
|
+
process.exit(1);
|
|
2934
|
+
}
|
|
2935
|
+
const absFile = pathResolve2(root, file);
|
|
2936
|
+
try {
|
|
2937
|
+
const result = await syncFile(absFile, root);
|
|
2938
|
+
const line = formatReindexLine(result, file);
|
|
2939
|
+
if (line !== null) console.log(line);
|
|
2940
|
+
process.exitCode = 0;
|
|
2941
|
+
} catch (err) {
|
|
2942
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2943
|
+
console.error(`engram: ${msg}`);
|
|
2944
|
+
if (opts.verbose && err instanceof Error && err.stack) {
|
|
2945
|
+
console.error(err.stack);
|
|
2946
|
+
}
|
|
2947
|
+
process.exit(1);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
);
|
|
2951
|
+
program.command("reindex-hook").description(
|
|
2952
|
+
"PostToolUse hook entry point: reads JSON from stdin, reindexes tool_input.file_path (always exits 0)"
|
|
2953
|
+
).action(async () => {
|
|
2954
|
+
const stdinTimeout = setTimeout(() => {
|
|
2955
|
+
process.exit(0);
|
|
2956
|
+
}, 3e3);
|
|
2957
|
+
stdinTimeout.unref();
|
|
2958
|
+
let input = "";
|
|
2959
|
+
let stdinFailed = false;
|
|
2960
|
+
try {
|
|
2961
|
+
for await (const chunk of process.stdin) {
|
|
2962
|
+
input += chunk;
|
|
2963
|
+
if (input.length > 1e6) break;
|
|
2964
|
+
}
|
|
2965
|
+
} catch {
|
|
2966
|
+
stdinFailed = true;
|
|
2967
|
+
}
|
|
2968
|
+
clearTimeout(stdinTimeout);
|
|
2969
|
+
if (stdinFailed || !input.trim()) {
|
|
2970
|
+
process.exitCode = 0;
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
let payload;
|
|
2974
|
+
try {
|
|
2975
|
+
payload = JSON.parse(input);
|
|
2976
|
+
} catch {
|
|
2977
|
+
process.exitCode = 0;
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
try {
|
|
2981
|
+
await runReindexHook(payload);
|
|
2982
|
+
} catch {
|
|
2983
|
+
}
|
|
2984
|
+
process.exitCode = 0;
|
|
2985
|
+
});
|
|
2878
2986
|
program.command("dashboard").alias("hud").description("Live terminal dashboard showing hook activity and token savings").argument("[path]", "Project directory", ".").action(async (projectPath) => {
|
|
2879
|
-
const resolvedPath =
|
|
2987
|
+
const resolvedPath = pathResolve2(projectPath);
|
|
2880
2988
|
const dbPath = join9(resolvedPath, ".engram", "graph.db");
|
|
2881
2989
|
if (!existsSync9(dbPath)) {
|
|
2882
2990
|
console.error(
|
|
@@ -2895,7 +3003,7 @@ program.command("dashboard").alias("hud").description("Live terminal dashboard s
|
|
|
2895
3003
|
});
|
|
2896
3004
|
});
|
|
2897
3005
|
program.command("hud-label").description("Output JSON label for Claude HUD --extra-cmd (fast, <20ms)").argument("[path]", "Project directory", ".").action(async (projectPath) => {
|
|
2898
|
-
let resolvedPath =
|
|
3006
|
+
let resolvedPath = pathResolve2(projectPath);
|
|
2899
3007
|
let found = false;
|
|
2900
3008
|
for (let depth = 0; depth < 20; depth++) {
|
|
2901
3009
|
if (existsSync9(join9(resolvedPath, ".engram", "graph.db"))) {
|
|
@@ -2991,8 +3099,8 @@ program.command("stats").description("Show knowledge graph statistics and token
|
|
|
2991
3099
|
if (bench.naiveFullCorpus > 0) {
|
|
2992
3100
|
console.log(`
|
|
2993
3101
|
${chalk2.cyan("Token savings:")}`);
|
|
2994
|
-
console.log(` Full corpus: ~${bench.naiveFullCorpus
|
|
2995
|
-
console.log(` Avg query: ~${bench.avgQueryTokens
|
|
3102
|
+
console.log(` Full corpus: ~${formatThousands(bench.naiveFullCorpus)} tokens`);
|
|
3103
|
+
console.log(` Avg query: ~${formatThousands(bench.avgQueryTokens)} tokens`);
|
|
2996
3104
|
console.log(` vs relevant: ${chalk2.bold.cyan(bench.reductionVsRelevant + "x")} fewer tokens`);
|
|
2997
3105
|
console.log(` vs full: ${chalk2.bold.cyan(bench.reductionVsFull + "x")} fewer tokens`);
|
|
2998
3106
|
}
|
|
@@ -3036,8 +3144,8 @@ program.command("mistakes").description("List known mistakes extracted from past
|
|
|
3036
3144
|
program.command("bench").description("Run token reduction benchmark").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3037
3145
|
const result = await benchmark(opts.project);
|
|
3038
3146
|
console.log(chalk2.bold("\n\u26A1 engram token reduction benchmark\n"));
|
|
3039
|
-
console.log(` Full corpus: ~${result.naiveFullCorpus
|
|
3040
|
-
console.log(` Avg graph query: ~${result.avgQueryTokens
|
|
3147
|
+
console.log(` Full corpus: ~${formatThousands(result.naiveFullCorpus)} tokens`);
|
|
3148
|
+
console.log(` Avg graph query: ~${formatThousands(result.avgQueryTokens)} tokens`);
|
|
3041
3149
|
console.log(` vs relevant: ${chalk2.bold.green(result.reductionVsRelevant + "x")} fewer tokens`);
|
|
3042
3150
|
console.log(` vs full corpus: ${chalk2.bold.green(result.reductionVsFull + "x")} fewer tokens
|
|
3043
3151
|
`);
|
|
@@ -3065,7 +3173,7 @@ program.command("gen").description("Generate CLAUDE.md / .cursorrules section fr
|
|
|
3065
3173
|
}
|
|
3066
3174
|
);
|
|
3067
3175
|
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) => {
|
|
3068
|
-
const { generateCursorMdc } = await import("./cursor-mdc-
|
|
3176
|
+
const { generateCursorMdc } = await import("./cursor-mdc-VEOFFDVO.js");
|
|
3069
3177
|
const result = await generateCursorMdc(opts.project);
|
|
3070
3178
|
console.log(
|
|
3071
3179
|
chalk2.green(
|
|
@@ -3073,11 +3181,15 @@ program.command("gen-mdc").description("Generate .cursor/rules/engram-context.md
|
|
|
3073
3181
|
)
|
|
3074
3182
|
);
|
|
3075
3183
|
if (opts.watch) {
|
|
3076
|
-
watchProject(
|
|
3184
|
+
watchProject(pathResolve2(opts.project), {
|
|
3077
3185
|
onReindex: async () => {
|
|
3078
3186
|
const r = await generateCursorMdc(opts.project);
|
|
3079
3187
|
console.log(chalk2.dim(` \u21BB Regenerated MDC (${r.nodes} nodes)`));
|
|
3080
3188
|
},
|
|
3189
|
+
onDelete: async () => {
|
|
3190
|
+
const r = await generateCursorMdc(opts.project);
|
|
3191
|
+
console.log(chalk2.dim(` \xD7 Regenerated MDC (${r.nodes} nodes)`));
|
|
3192
|
+
},
|
|
3081
3193
|
onError: (err) => console.error(chalk2.red(err.message)),
|
|
3082
3194
|
onReady: () => console.log(chalk2.dim(" Watching for changes..."))
|
|
3083
3195
|
});
|
|
@@ -3086,8 +3198,8 @@ program.command("gen-mdc").description("Generate .cursor/rules/engram-context.md
|
|
|
3086
3198
|
}
|
|
3087
3199
|
});
|
|
3088
3200
|
program.command("gen-ccs").description("Export knowledge graph as .context/index.md (CCS format)").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3089
|
-
const { exportCcs } = await import("./exporter-
|
|
3090
|
-
const result = await exportCcs(
|
|
3201
|
+
const { exportCcs } = await import("./exporter-AWXS34AS.js");
|
|
3202
|
+
const result = await exportCcs(pathResolve2(opts.project));
|
|
3091
3203
|
console.log(
|
|
3092
3204
|
chalk2.green(
|
|
3093
3205
|
`\u2705 Generated ${result.filePath} (${result.sectionsWritten} sections, ${result.nodesExported} nodes)`
|
|
@@ -3095,19 +3207,23 @@ program.command("gen-ccs").description("Export knowledge graph as .context/index
|
|
|
3095
3207
|
);
|
|
3096
3208
|
});
|
|
3097
3209
|
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) => {
|
|
3098
|
-
const { generateAiderContext } = await import("./aider-context-
|
|
3099
|
-
const result = await generateAiderContext(
|
|
3210
|
+
const { generateAiderContext } = await import("./aider-context-J557IHIP.js");
|
|
3211
|
+
const result = await generateAiderContext(pathResolve2(opts.project));
|
|
3100
3212
|
console.log(
|
|
3101
3213
|
chalk2.green(
|
|
3102
3214
|
`\u2705 Generated ${result.filePath} (${result.sections} sections, ${result.nodes} nodes)`
|
|
3103
3215
|
)
|
|
3104
3216
|
);
|
|
3105
3217
|
if (opts.watch) {
|
|
3106
|
-
watchProject(
|
|
3218
|
+
watchProject(pathResolve2(opts.project), {
|
|
3107
3219
|
onReindex: async () => {
|
|
3108
3220
|
const r = await generateAiderContext(opts.project);
|
|
3109
3221
|
console.log(chalk2.dim(` \u21BB Regenerated .aider-context.md (${r.nodes} nodes)`));
|
|
3110
3222
|
},
|
|
3223
|
+
onDelete: async () => {
|
|
3224
|
+
const r = await generateAiderContext(opts.project);
|
|
3225
|
+
console.log(chalk2.dim(` \xD7 Regenerated .aider-context.md (${r.nodes} nodes)`));
|
|
3226
|
+
},
|
|
3111
3227
|
onError: (err) => console.error(chalk2.red(err.message)),
|
|
3112
3228
|
onReady: () => console.log(chalk2.dim(" Watching for changes..."))
|
|
3113
3229
|
});
|
|
@@ -3116,19 +3232,23 @@ program.command("gen-aider").description("Generate .aider-context.md from knowle
|
|
|
3116
3232
|
}
|
|
3117
3233
|
});
|
|
3118
3234
|
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-
|
|
3120
|
-
const result = await generateWindsurfRules(
|
|
3235
|
+
const { generateWindsurfRules } = await import("./windsurf-rules-RWPKBHRD.js");
|
|
3236
|
+
const result = await generateWindsurfRules(pathResolve2(opts.project));
|
|
3121
3237
|
console.log(
|
|
3122
3238
|
chalk2.green(
|
|
3123
3239
|
`\u2705 Generated ${result.filePath} (${result.sections} sections, ${result.nodes} nodes)`
|
|
3124
3240
|
)
|
|
3125
3241
|
);
|
|
3126
3242
|
if (opts.watch) {
|
|
3127
|
-
watchProject(
|
|
3243
|
+
watchProject(pathResolve2(opts.project), {
|
|
3128
3244
|
onReindex: async () => {
|
|
3129
3245
|
const r = await generateWindsurfRules(opts.project);
|
|
3130
3246
|
console.log(chalk2.dim(` \u21BB Regenerated .windsurfrules (${r.nodes} nodes)`));
|
|
3131
3247
|
},
|
|
3248
|
+
onDelete: async () => {
|
|
3249
|
+
const r = await generateWindsurfRules(opts.project);
|
|
3250
|
+
console.log(chalk2.dim(` \xD7 Regenerated .windsurfrules (${r.nodes} nodes)`));
|
|
3251
|
+
},
|
|
3132
3252
|
onError: (err) => console.error(chalk2.red(err.message)),
|
|
3133
3253
|
onReady: () => console.log(chalk2.dim(" Watching for changes..."))
|
|
3134
3254
|
});
|
|
@@ -3137,7 +3257,7 @@ program.command("gen-windsurfrules").description("Generate .windsurfrules from k
|
|
|
3137
3257
|
}
|
|
3138
3258
|
});
|
|
3139
3259
|
function resolveSettingsPath(scope, projectPath) {
|
|
3140
|
-
const absProject =
|
|
3260
|
+
const absProject = pathResolve2(projectPath);
|
|
3141
3261
|
switch (scope) {
|
|
3142
3262
|
case "local":
|
|
3143
3263
|
return join9(absProject, ".claude", "settings.local.json");
|
|
@@ -3226,7 +3346,11 @@ program.command("cursor-intercept").description(
|
|
|
3226
3346
|
}
|
|
3227
3347
|
process.exit(0);
|
|
3228
3348
|
});
|
|
3229
|
-
program.command("install-hook").description("Install engram hook entries into Claude Code settings").option("--scope <scope>", "local | project | user", "local").option("--dry-run", "Show diff without writing", false).option("-p, --project <path>", "Project directory", ".").
|
|
3349
|
+
program.command("install-hook").description("Install engram hook entries into Claude Code settings").option("--scope <scope>", "local | project | user", "local").option("--dry-run", "Show diff without writing", false).option("-p, --project <path>", "Project directory", ".").option(
|
|
3350
|
+
"--auto-reindex",
|
|
3351
|
+
"Also register a PostToolUse Edit|Write|MultiEdit entry calling 'engram reindex-hook' (keeps graph fresh after every edit, #8)",
|
|
3352
|
+
false
|
|
3353
|
+
).action(
|
|
3230
3354
|
async (opts) => {
|
|
3231
3355
|
const settingsPath = resolveSettingsPath(opts.scope, opts.project);
|
|
3232
3356
|
if (!settingsPath) {
|
|
@@ -3256,13 +3380,20 @@ program.command("install-hook").description("Install engram hook entries into Cl
|
|
|
3256
3380
|
process.exit(1);
|
|
3257
3381
|
}
|
|
3258
3382
|
}
|
|
3259
|
-
const result = installEngramHooks(existing
|
|
3383
|
+
const result = installEngramHooks(existing, void 0, {
|
|
3384
|
+
autoReindex: opts.autoReindex
|
|
3385
|
+
});
|
|
3260
3386
|
console.log(
|
|
3261
3387
|
chalk2.bold(`
|
|
3262
3388
|
\u{1F4CC} engram install-hook (scope: ${opts.scope})`)
|
|
3263
3389
|
);
|
|
3264
3390
|
console.log(chalk2.dim(` Target: ${settingsPath}`));
|
|
3265
|
-
if (
|
|
3391
|
+
if (opts.autoReindex) {
|
|
3392
|
+
console.log(
|
|
3393
|
+
chalk2.dim(" Auto-reindex: enabled (engram reindex-hook)")
|
|
3394
|
+
);
|
|
3395
|
+
}
|
|
3396
|
+
if (result.added.length === 0 && !result.statusLineAdded && !result.autoReindexAdded) {
|
|
3266
3397
|
console.log(
|
|
3267
3398
|
chalk2.yellow(
|
|
3268
3399
|
`
|
|
@@ -3317,6 +3448,13 @@ program.command("install-hook").description("Install engram hook entries into Cl
|
|
|
3317
3448
|
chalk2.green(" \u2705 StatusLine: engram hud-label (HUD visible in Claude Code)")
|
|
3318
3449
|
);
|
|
3319
3450
|
}
|
|
3451
|
+
if (result.autoReindexAdded) {
|
|
3452
|
+
console.log(
|
|
3453
|
+
chalk2.green(
|
|
3454
|
+
" \u2705 PostToolUse: engram reindex-hook (matcher: Edit|Write|MultiEdit)"
|
|
3455
|
+
)
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3320
3458
|
if (result.alreadyPresent.length > 0) {
|
|
3321
3459
|
console.log(
|
|
3322
3460
|
chalk2.dim(
|
|
@@ -3390,7 +3528,7 @@ program.command("uninstall-hook").description("Remove engram hook entries from C
|
|
|
3390
3528
|
}
|
|
3391
3529
|
});
|
|
3392
3530
|
program.command("hook-stats").description("Summarize hook-log.jsonl for a project").option("-p, --project <path>", "Project directory", ".").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
3393
|
-
const absProject =
|
|
3531
|
+
const absProject = pathResolve2(opts.project);
|
|
3394
3532
|
const projectRoot = findProjectRoot(absProject) ?? absProject;
|
|
3395
3533
|
const entries = readHookLog(projectRoot);
|
|
3396
3534
|
const summary = summarizeHookLog(entries);
|
|
@@ -3401,8 +3539,8 @@ program.command("hook-stats").description("Summarize hook-log.jsonl for a projec
|
|
|
3401
3539
|
console.log(formatStatsSummary(summary));
|
|
3402
3540
|
});
|
|
3403
3541
|
program.command("hook-preview").description("Show what the Read handler would do for a file (dry-run)").argument("<file>", "Target file path").option("-p, --project <path>", "Project directory", ".").action(async (file, opts) => {
|
|
3404
|
-
const absProject =
|
|
3405
|
-
const absFile =
|
|
3542
|
+
const absProject = pathResolve2(opts.project);
|
|
3543
|
+
const absFile = pathResolve2(absProject, file);
|
|
3406
3544
|
const payload = {
|
|
3407
3545
|
hook_event_name: "PreToolUse",
|
|
3408
3546
|
tool_name: "Read",
|
|
@@ -3451,7 +3589,7 @@ program.command("hook-preview").description("Show what the Read handler would do
|
|
|
3451
3589
|
console.log(chalk2.yellow(` Decision: ${decision ?? "unknown"}`));
|
|
3452
3590
|
});
|
|
3453
3591
|
program.command("hook-disable").description("Disable engram hooks via kill switch (does not uninstall)").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3454
|
-
const absProject =
|
|
3592
|
+
const absProject = pathResolve2(opts.project);
|
|
3455
3593
|
const projectRoot = findProjectRoot(absProject);
|
|
3456
3594
|
if (!projectRoot) {
|
|
3457
3595
|
console.error(
|
|
@@ -3478,7 +3616,7 @@ program.command("hook-disable").description("Disable engram hooks via kill switc
|
|
|
3478
3616
|
}
|
|
3479
3617
|
});
|
|
3480
3618
|
program.command("hook-enable").description("Re-enable engram hooks (remove kill switch flag)").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3481
|
-
const absProject =
|
|
3619
|
+
const absProject = pathResolve2(opts.project);
|
|
3482
3620
|
const projectRoot = findProjectRoot(absProject);
|
|
3483
3621
|
if (!projectRoot) {
|
|
3484
3622
|
console.error(chalk2.red(`Not an engram project: ${absProject}`));
|
|
@@ -3507,7 +3645,7 @@ program.command("memory-sync").description(
|
|
|
3507
3645
|
"Write engram's structural facts into MEMORY.md (complementary to Anthropic Auto-Dream)"
|
|
3508
3646
|
).option("-p, --project <path>", "Project directory", ".").option("--dry-run", "Print what would be written without writing", false).action(
|
|
3509
3647
|
async (opts) => {
|
|
3510
|
-
const absProject =
|
|
3648
|
+
const absProject = pathResolve2(opts.project);
|
|
3511
3649
|
const projectRoot = findProjectRoot(absProject);
|
|
3512
3650
|
if (!projectRoot) {
|
|
3513
3651
|
console.error(
|
|
@@ -3605,13 +3743,13 @@ program.command("stress-test").description("Run stress tests: memory, concurrenc
|
|
|
3605
3743
|
}
|
|
3606
3744
|
});
|
|
3607
3745
|
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) => {
|
|
3608
|
-
const { startHttpServer } = await import("./server-
|
|
3609
|
-
await startHttpServer(
|
|
3746
|
+
const { startHttpServer } = await import("./server-A6MUVKQK.js");
|
|
3747
|
+
await startHttpServer(pathResolve2(opts.project), parseInt(opts.port, 10));
|
|
3610
3748
|
});
|
|
3611
3749
|
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
3750
|
const port = parseInt(opts.port, 10);
|
|
3613
3751
|
const publicUrl = `http://127.0.0.1:${port}/ui`;
|
|
3614
|
-
const projectRoot =
|
|
3752
|
+
const projectRoot = pathResolve2(opts.project);
|
|
3615
3753
|
const { existsSync: existsSync10, readFileSync: readFileSync6 } = await import("fs");
|
|
3616
3754
|
const pidPath = join9(projectRoot, ".engram", "http-server.pid");
|
|
3617
3755
|
let alreadyRunning = false;
|
|
@@ -3666,7 +3804,7 @@ program.command("context-server").description("Start Zed-compatible context serv
|
|
|
3666
3804
|
});
|
|
3667
3805
|
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) => {
|
|
3668
3806
|
const { analyzeTuning, applyTuning } = await import("./tuner-KFNNGKG3.js");
|
|
3669
|
-
const proposal = analyzeTuning(
|
|
3807
|
+
const proposal = analyzeTuning(pathResolve2(opts.project));
|
|
3670
3808
|
if (proposal.changes.length === 0) {
|
|
3671
3809
|
console.log(
|
|
3672
3810
|
chalk2.dim(
|
|
@@ -3688,7 +3826,7 @@ program.command("tune").description("Analyze hook-log and propose provider confi
|
|
|
3688
3826
|
);
|
|
3689
3827
|
}
|
|
3690
3828
|
if (opts.apply) {
|
|
3691
|
-
applyTuning(
|
|
3829
|
+
applyTuning(pathResolve2(opts.project), proposal);
|
|
3692
3830
|
console.log(chalk2.green("\n\u2705 Changes applied to .engram/config.json"));
|
|
3693
3831
|
} else {
|
|
3694
3832
|
console.log(chalk2.dim("\nRun with --apply to write these changes."));
|
|
@@ -3696,9 +3834,9 @@ program.command("tune").description("Analyze hook-log and propose provider confi
|
|
|
3696
3834
|
});
|
|
3697
3835
|
var dbCmd = program.command("db").description("Database management");
|
|
3698
3836
|
dbCmd.command("status").description("Show schema version and migration status").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3699
|
-
const { getStore: getStore2 } = await import("./core-
|
|
3837
|
+
const { getStore: getStore2 } = await import("./core-TSXA5XZH.js");
|
|
3700
3838
|
const { CURRENT_SCHEMA_VERSION, getSchemaVersion } = await import("./migrate-UKCO6BUU.js");
|
|
3701
|
-
const store = await getStore2(
|
|
3839
|
+
const store = await getStore2(pathResolve2(opts.project));
|
|
3702
3840
|
try {
|
|
3703
3841
|
const version = getSchemaVersion(store.db);
|
|
3704
3842
|
const pending = CURRENT_SCHEMA_VERSION - version;
|
|
@@ -3713,11 +3851,11 @@ dbCmd.command("status").description("Show schema version and migration status").
|
|
|
3713
3851
|
}
|
|
3714
3852
|
});
|
|
3715
3853
|
dbCmd.command("migrate").description("Run pending schema migrations").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3716
|
-
const { getStore: getStore2 } = await import("./core-
|
|
3854
|
+
const { getStore: getStore2 } = await import("./core-TSXA5XZH.js");
|
|
3717
3855
|
const { runMigrations } = await import("./migrate-UKCO6BUU.js");
|
|
3718
|
-
const store = await getStore2(
|
|
3856
|
+
const store = await getStore2(pathResolve2(opts.project));
|
|
3719
3857
|
try {
|
|
3720
|
-
const dbPath = join9(
|
|
3858
|
+
const dbPath = join9(pathResolve2(opts.project), ".engram", "graph.db");
|
|
3721
3859
|
const result = runMigrations(
|
|
3722
3860
|
store.db,
|
|
3723
3861
|
dbPath
|
|
@@ -3748,11 +3886,11 @@ dbCmd.command("rollback").description("Roll back to an earlier schema version (D
|
|
|
3748
3886
|
console.error(chalk2.red(`Invalid version: ${opts.to}`));
|
|
3749
3887
|
process.exit(1);
|
|
3750
3888
|
}
|
|
3751
|
-
const { getStore: getStore2 } = await import("./core-
|
|
3889
|
+
const { getStore: getStore2 } = await import("./core-TSXA5XZH.js");
|
|
3752
3890
|
const { rollback, getSchemaVersion } = await import("./migrate-UKCO6BUU.js");
|
|
3753
|
-
const store = await getStore2(
|
|
3891
|
+
const store = await getStore2(pathResolve2(opts.project));
|
|
3754
3892
|
try {
|
|
3755
|
-
const dbPath = join9(
|
|
3893
|
+
const dbPath = join9(pathResolve2(opts.project), ".engram", "graph.db");
|
|
3756
3894
|
const current = getSchemaVersion(
|
|
3757
3895
|
store.db
|
|
3758
3896
|
);
|
|
@@ -3837,7 +3975,7 @@ pluginCmd.command("install").description("Install a plugin by copying its .mjs f
|
|
|
3837
3975
|
const { basename: basename6 } = await import("path");
|
|
3838
3976
|
const { getPluginsDir, ensurePluginsDir, validatePlugin } = await import("./plugin-loader-STTGYIL5.js");
|
|
3839
3977
|
const { pathToFileURL } = await import("url");
|
|
3840
|
-
const absPath =
|
|
3978
|
+
const absPath = pathResolve2(file);
|
|
3841
3979
|
if (!existsSync9(absPath)) {
|
|
3842
3980
|
console.error(chalk2.red(`File not found: ${absPath}`));
|
|
3843
3981
|
process.exit(1);
|
|
@@ -3885,9 +4023,9 @@ pluginCmd.command("remove").description("Remove an installed plugin by filename"
|
|
|
3885
4023
|
});
|
|
3886
4024
|
var cacheCmd = program.command("cache").description("Inspect and manage the context cache");
|
|
3887
4025
|
cacheCmd.command("stats").description("Show cache hit rate, entries, and LRU sizes").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3888
|
-
const { getStore: getStore2 } = await import("./core-
|
|
4026
|
+
const { getStore: getStore2 } = await import("./core-TSXA5XZH.js");
|
|
3889
4027
|
const { getContextCache, ContextCache } = await import("./cache-AK6CF3BC.js");
|
|
3890
|
-
const store = await getStore2(
|
|
4028
|
+
const store = await getStore2(pathResolve2(opts.project));
|
|
3891
4029
|
try {
|
|
3892
4030
|
ContextCache.ensureTables(store);
|
|
3893
4031
|
const cache = getContextCache();
|
|
@@ -3919,9 +4057,9 @@ cacheCmd.command("stats").description("Show cache hit rate, entries, and LRU siz
|
|
|
3919
4057
|
}
|
|
3920
4058
|
});
|
|
3921
4059
|
cacheCmd.command("clear").description("Flush all cache layers (query, pattern, hot files)").option("-p, --project <path>", "Project directory", ".").action(async (opts) => {
|
|
3922
|
-
const { getStore: getStore2 } = await import("./core-
|
|
4060
|
+
const { getStore: getStore2 } = await import("./core-TSXA5XZH.js");
|
|
3923
4061
|
const { getContextCache, ContextCache } = await import("./cache-AK6CF3BC.js");
|
|
3924
|
-
const store = await getStore2(
|
|
4062
|
+
const store = await getStore2(pathResolve2(opts.project));
|
|
3925
4063
|
try {
|
|
3926
4064
|
ContextCache.ensureTables(store);
|
|
3927
4065
|
const cache = getContextCache();
|
|
@@ -3938,14 +4076,14 @@ cacheCmd.command("clear").description("Flush all cache layers (query, pattern, h
|
|
|
3938
4076
|
}
|
|
3939
4077
|
});
|
|
3940
4078
|
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) => {
|
|
3941
|
-
const { getStore: getStore2 } = await import("./core-
|
|
4079
|
+
const { getStore: getStore2 } = await import("./core-TSXA5XZH.js");
|
|
3942
4080
|
const { getContextCache, ContextCache } = await import("./cache-AK6CF3BC.js");
|
|
3943
|
-
const store = await getStore2(
|
|
4081
|
+
const store = await getStore2(pathResolve2(opts.project));
|
|
3944
4082
|
try {
|
|
3945
4083
|
ContextCache.ensureTables(store);
|
|
3946
4084
|
const cache = getContextCache();
|
|
3947
4085
|
const topN = parseInt(opts.limit, 10) || 20;
|
|
3948
|
-
const count = cache.warmHotFiles(store,
|
|
4086
|
+
const count = cache.warmHotFiles(store, pathResolve2(opts.project), topN);
|
|
3949
4087
|
if (count === 0) {
|
|
3950
4088
|
console.log(
|
|
3951
4089
|
chalk2.dim(
|
|
@@ -3959,4 +4097,133 @@ cacheCmd.command("warm").description("Pre-warm hot file cache from access freque
|
|
|
3959
4097
|
store.close();
|
|
3960
4098
|
}
|
|
3961
4099
|
});
|
|
4100
|
+
program.command("update").description("Check for and install the latest engram release").option("--check", "Check only \u2014 do not install", false).option("--force", "Bypass 7-day throttle cache on registry check", false).option(
|
|
4101
|
+
"--manager <mgr>",
|
|
4102
|
+
"Override package manager detection (npm | pnpm | yarn | bun)"
|
|
4103
|
+
).option("--dry-run", "Print the upgrade command without executing", false).action(
|
|
4104
|
+
async (opts) => {
|
|
4105
|
+
const { checkForUpdate } = await import("./check-2Z3MPZEJ.js");
|
|
4106
|
+
const result = await checkForUpdate(PKG_VERSION, { force: opts.force });
|
|
4107
|
+
if (result.skipped) {
|
|
4108
|
+
if (result.fromCache === false) {
|
|
4109
|
+
console.log(
|
|
4110
|
+
chalk2.dim("Skipped (opt-out via ENGRAM_NO_UPDATE_CHECK or $CI).")
|
|
4111
|
+
);
|
|
4112
|
+
} else {
|
|
4113
|
+
console.log(chalk2.dim("Skipped (registry unreachable)."));
|
|
4114
|
+
}
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
const ageMin = result.checkedAt ? Math.round((Date.now() - result.checkedAt) / 6e4) : 0;
|
|
4118
|
+
const freshness = result.fromCache ? chalk2.dim(` (cached ${ageMin}m ago)`) : chalk2.dim(" (live)");
|
|
4119
|
+
console.log(
|
|
4120
|
+
`${chalk2.bold("engram")} ${chalk2.dim("installed:")} v${result.current} ${chalk2.dim("latest:")} ${result.latest ?? chalk2.yellow("unknown")}${freshness}`
|
|
4121
|
+
);
|
|
4122
|
+
if (!result.updateAvailable) {
|
|
4123
|
+
console.log(chalk2.green("\u2713 You are on the latest release."));
|
|
4124
|
+
return;
|
|
4125
|
+
}
|
|
4126
|
+
console.log(
|
|
4127
|
+
chalk2.yellow(
|
|
4128
|
+
`\u2B06 v${result.latest} is available \u2014 you're on v${result.current}.`
|
|
4129
|
+
)
|
|
4130
|
+
);
|
|
4131
|
+
if (opts.check) {
|
|
4132
|
+
console.log(chalk2.dim("Run `engram update` to install it."));
|
|
4133
|
+
return;
|
|
4134
|
+
}
|
|
4135
|
+
const { runUpgrade, manualCommand } = await import("./install-YVMVCFQW.js");
|
|
4136
|
+
const outcome = runUpgrade({
|
|
4137
|
+
dryRun: opts.dryRun,
|
|
4138
|
+
manager: opts.manager === "npm" || opts.manager === "pnpm" || opts.manager === "yarn" || opts.manager === "bun" ? opts.manager : void 0
|
|
4139
|
+
});
|
|
4140
|
+
if (outcome.ok) {
|
|
4141
|
+
console.log(chalk2.green(`\u2713 ${outcome.message}`));
|
|
4142
|
+
if (!opts.dryRun) {
|
|
4143
|
+
console.log(chalk2.dim(" Run `engram --version` to verify."));
|
|
4144
|
+
}
|
|
4145
|
+
} else {
|
|
4146
|
+
console.error(chalk2.red(`\u2717 ${outcome.message}`));
|
|
4147
|
+
if (outcome.stderrTail) {
|
|
4148
|
+
console.error(chalk2.dim(outcome.stderrTail));
|
|
4149
|
+
}
|
|
4150
|
+
console.error(chalk2.dim(` Manual: ${manualCommand()}`));
|
|
4151
|
+
process.exitCode = 1;
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
);
|
|
4155
|
+
program.command("doctor").description("Component health report with remediation hints").option("-p, --project <path>", "Project directory", ".").option("-v, --verbose", "Show remediation hints for warn/fail checks", false).option("--json", "Output JSON", false).option(
|
|
4156
|
+
"--export",
|
|
4157
|
+
"Redacted JSON for bug reports (same as --json with --verbose)",
|
|
4158
|
+
false
|
|
4159
|
+
).action(
|
|
4160
|
+
async (opts) => {
|
|
4161
|
+
const { buildReport, formatReport, exportReport } = await import("./report-C3GTM3HY.js");
|
|
4162
|
+
const root = pathResolve2(opts.project);
|
|
4163
|
+
const report = buildReport(root, PKG_VERSION);
|
|
4164
|
+
if (opts.json || opts.export) {
|
|
4165
|
+
console.log(exportReport(report));
|
|
4166
|
+
} else {
|
|
4167
|
+
console.log(formatReport(report, opts.verbose));
|
|
4168
|
+
}
|
|
4169
|
+
process.exitCode = report.overallSeverity === "ok" ? 0 : report.overallSeverity === "warn" ? 1 : 2;
|
|
4170
|
+
}
|
|
4171
|
+
);
|
|
4172
|
+
program.command("setup").description("Zero-friction first-run wizard (init + install-hook + doctor)").option("-p, --project <path>", "Project directory", ".").option("-y, --yes", "Accept all defaults (non-interactive)", false).option("--dry-run", "Print what would happen without touching anything", false).option(
|
|
4173
|
+
"--scope <scope>",
|
|
4174
|
+
"Hook scope for install-hook step (local | project | user)",
|
|
4175
|
+
"local"
|
|
4176
|
+
).action(
|
|
4177
|
+
async (opts) => {
|
|
4178
|
+
const { runSetup } = await import("./wizard-AOXWMSXW.js");
|
|
4179
|
+
const scope = opts.scope === "local" || opts.scope === "project" || opts.scope === "user" ? opts.scope : "local";
|
|
4180
|
+
const result = await runSetup({
|
|
4181
|
+
projectPath: opts.project,
|
|
4182
|
+
yes: opts.yes,
|
|
4183
|
+
dryRun: opts.dryRun,
|
|
4184
|
+
engramVersion: PKG_VERSION,
|
|
4185
|
+
settingsScope: scope
|
|
4186
|
+
});
|
|
4187
|
+
process.exitCode = result.exitCode;
|
|
4188
|
+
}
|
|
4189
|
+
);
|
|
4190
|
+
var FIRST_RUN_SILENT_CMDS = /* @__PURE__ */ new Set([
|
|
4191
|
+
"intercept",
|
|
4192
|
+
"cursor-intercept",
|
|
4193
|
+
"hud-label",
|
|
4194
|
+
"setup",
|
|
4195
|
+
"init",
|
|
4196
|
+
"update",
|
|
4197
|
+
"doctor"
|
|
4198
|
+
]);
|
|
4199
|
+
function maybePrintFirstRunHint() {
|
|
4200
|
+
if (process.env.CI) return;
|
|
4201
|
+
if (process.env.ENGRAM_NO_UPDATE_CHECK === "1") return;
|
|
4202
|
+
const subcommand = process.argv[2];
|
|
4203
|
+
if (!subcommand) return;
|
|
4204
|
+
if (FIRST_RUN_SILENT_CMDS.has(subcommand)) return;
|
|
4205
|
+
try {
|
|
4206
|
+
const cwd = process.cwd();
|
|
4207
|
+
if (existsSync9(join9(cwd, ".engram", "graph.db"))) return;
|
|
4208
|
+
const sentinel = join9(homedir(), ".engram", "first-run-shown");
|
|
4209
|
+
if (existsSync9(sentinel)) return;
|
|
4210
|
+
mkdirSync(dirname4(sentinel), { recursive: true });
|
|
4211
|
+
writeFileSync2(sentinel, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
4212
|
+
process.stderr.write(
|
|
4213
|
+
chalk2.dim("\u{1F4A1} ") + chalk2.yellow("First time in this repo?") + chalk2.dim(" Run ") + chalk2.white("engram setup") + chalk2.dim(" for a zero-friction install.\n")
|
|
4214
|
+
);
|
|
4215
|
+
} catch {
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
function maybePrintUpdateHintSafe() {
|
|
4219
|
+
const subcommand = process.argv[2];
|
|
4220
|
+
if (!subcommand || FIRST_RUN_SILENT_CMDS.has(subcommand)) return;
|
|
4221
|
+
try {
|
|
4222
|
+
import("./notify-5POGKMRX.js").then((m) => m.maybePrintUpdateHint(PKG_VERSION)).catch(() => {
|
|
4223
|
+
});
|
|
4224
|
+
} catch {
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
maybePrintFirstRunHint();
|
|
4228
|
+
maybePrintUpdateHintSafe();
|
|
3962
4229
|
program.parse();
|