engramx 0.1.0 → 0.2.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/README.md +57 -7
- package/dist/{chunk-44GN6IRQ.js → chunk-D53DRZZL.js} +412 -25
- package/dist/chunk-QKCPFSVU.js +361 -0
- package/dist/cli.js +57 -9
- package/dist/{core-M5N34VUU.js → core-H72MM256.js} +3 -1
- package/dist/index.js +15 -3
- package/dist/serve.js +65 -12
- package/package.json +2 -1
- package/dist/chunk-WJUA4VZ7.js +0 -247
package/dist/chunk-WJUA4VZ7.js
DELETED
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
// src/hooks.ts
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, chmodSync, unlinkSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
var HOOK_START = "# engram-hook-start";
|
|
5
|
-
var HOOK_END = "# engram-hook-end";
|
|
6
|
-
var POST_COMMIT_SCRIPT = `
|
|
7
|
-
${HOOK_START}
|
|
8
|
-
# Auto-rebuild engram graph after commit (AST only, no LLM needed)
|
|
9
|
-
ENGRAM_BIN=$(command -v engram 2>/dev/null)
|
|
10
|
-
if [ -z "$ENGRAM_BIN" ]; then
|
|
11
|
-
ENGRAM_BIN=$(npm root -g 2>/dev/null)/engram/dist/cli.js
|
|
12
|
-
fi
|
|
13
|
-
|
|
14
|
-
if [ -d ".engram" ] && [ -f "$ENGRAM_BIN" ]; then
|
|
15
|
-
node "$ENGRAM_BIN" init . --quiet 2>/dev/null &
|
|
16
|
-
fi
|
|
17
|
-
${HOOK_END}
|
|
18
|
-
`;
|
|
19
|
-
var POST_CHECKOUT_SCRIPT = `
|
|
20
|
-
${HOOK_START}
|
|
21
|
-
# Auto-rebuild engram graph on branch switch
|
|
22
|
-
PREV_HEAD=$1
|
|
23
|
-
NEW_HEAD=$2
|
|
24
|
-
BRANCH_SWITCH=$3
|
|
25
|
-
|
|
26
|
-
if [ "$BRANCH_SWITCH" != "1" ]; then
|
|
27
|
-
exit 0
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
ENGRAM_BIN=$(command -v engram 2>/dev/null)
|
|
31
|
-
if [ -z "$ENGRAM_BIN" ]; then
|
|
32
|
-
ENGRAM_BIN=$(npm root -g 2>/dev/null)/engram/dist/cli.js
|
|
33
|
-
fi
|
|
34
|
-
|
|
35
|
-
if [ -d ".engram" ] && [ -f "$ENGRAM_BIN" ]; then
|
|
36
|
-
echo "[engram] Branch switched \u2014 rebuilding graph..."
|
|
37
|
-
node "$ENGRAM_BIN" init . --quiet 2>/dev/null &
|
|
38
|
-
fi
|
|
39
|
-
${HOOK_END}
|
|
40
|
-
`;
|
|
41
|
-
function findGitRoot(from) {
|
|
42
|
-
let current = from;
|
|
43
|
-
while (current !== "/") {
|
|
44
|
-
if (existsSync(join(current, ".git"))) return current;
|
|
45
|
-
current = join(current, "..");
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
function installHook(hooksDir, name, script) {
|
|
50
|
-
const hookPath = join(hooksDir, name);
|
|
51
|
-
if (existsSync(hookPath)) {
|
|
52
|
-
const content = readFileSync(hookPath, "utf-8");
|
|
53
|
-
if (content.includes(HOOK_START)) {
|
|
54
|
-
return `${name}: already installed`;
|
|
55
|
-
}
|
|
56
|
-
writeFileSync(hookPath, content.trimEnd() + "\n\n" + script);
|
|
57
|
-
return `${name}: appended to existing hook`;
|
|
58
|
-
}
|
|
59
|
-
writeFileSync(hookPath, "#!/bin/bash\n" + script);
|
|
60
|
-
chmodSync(hookPath, 493);
|
|
61
|
-
return `${name}: installed`;
|
|
62
|
-
}
|
|
63
|
-
function uninstallHook(hooksDir, name) {
|
|
64
|
-
const hookPath = join(hooksDir, name);
|
|
65
|
-
if (!existsSync(hookPath)) return `${name}: not installed`;
|
|
66
|
-
const content = readFileSync(hookPath, "utf-8");
|
|
67
|
-
if (!content.includes(HOOK_START)) return `${name}: engram hook not found`;
|
|
68
|
-
const cleaned = content.replace(new RegExp(`\\n?${HOOK_START}[\\s\\S]*?${HOOK_END}\\n?`, "g"), "").trim();
|
|
69
|
-
if (!cleaned || cleaned === "#!/bin/bash") {
|
|
70
|
-
unlinkSync(hookPath);
|
|
71
|
-
return `${name}: removed`;
|
|
72
|
-
}
|
|
73
|
-
writeFileSync(hookPath, cleaned + "\n");
|
|
74
|
-
return `${name}: engram section removed (other hooks preserved)`;
|
|
75
|
-
}
|
|
76
|
-
function install(projectRoot) {
|
|
77
|
-
const gitRoot = findGitRoot(projectRoot);
|
|
78
|
-
if (!gitRoot) return "Error: not a git repository";
|
|
79
|
-
const hooksDir = join(gitRoot, ".git", "hooks");
|
|
80
|
-
const results = [
|
|
81
|
-
installHook(hooksDir, "post-commit", POST_COMMIT_SCRIPT),
|
|
82
|
-
installHook(hooksDir, "post-checkout", POST_CHECKOUT_SCRIPT)
|
|
83
|
-
];
|
|
84
|
-
return results.join("\n");
|
|
85
|
-
}
|
|
86
|
-
function uninstall(projectRoot) {
|
|
87
|
-
const gitRoot = findGitRoot(projectRoot);
|
|
88
|
-
if (!gitRoot) return "Error: not a git repository";
|
|
89
|
-
const hooksDir = join(gitRoot, ".git", "hooks");
|
|
90
|
-
const results = [
|
|
91
|
-
uninstallHook(hooksDir, "post-commit"),
|
|
92
|
-
uninstallHook(hooksDir, "post-checkout")
|
|
93
|
-
];
|
|
94
|
-
return results.join("\n");
|
|
95
|
-
}
|
|
96
|
-
function status(projectRoot) {
|
|
97
|
-
const gitRoot = findGitRoot(projectRoot);
|
|
98
|
-
if (!gitRoot) return "Not a git repository";
|
|
99
|
-
const hooksDir = join(gitRoot, ".git", "hooks");
|
|
100
|
-
const check = (name) => {
|
|
101
|
-
const p = join(hooksDir, name);
|
|
102
|
-
if (!existsSync(p)) return "not installed";
|
|
103
|
-
return readFileSync(p, "utf-8").includes(HOOK_START) ? "installed" : "not installed";
|
|
104
|
-
};
|
|
105
|
-
return `post-commit: ${check("post-commit")}
|
|
106
|
-
post-checkout: ${check("post-checkout")}`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// src/autogen.ts
|
|
110
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
111
|
-
import { join as join2 } from "path";
|
|
112
|
-
var AUTOGEN_START = "<!-- engram:start -->";
|
|
113
|
-
var AUTOGEN_END = "<!-- engram:end -->";
|
|
114
|
-
function generateSummary(store) {
|
|
115
|
-
const stats = store.getStats();
|
|
116
|
-
const gods = store.getGodNodes(8);
|
|
117
|
-
const allNodes = store.getAllNodes();
|
|
118
|
-
const allEdges = store.getAllEdges();
|
|
119
|
-
const filesByDir = /* @__PURE__ */ new Map();
|
|
120
|
-
for (const node of allNodes) {
|
|
121
|
-
if (node.kind === "file" && node.sourceFile) {
|
|
122
|
-
const dir = node.sourceFile.split("/").slice(0, -1).join("/") || ".";
|
|
123
|
-
if (!filesByDir.has(dir)) filesByDir.set(dir, []);
|
|
124
|
-
filesByDir.get(dir).push(node.label);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
const kindCounts = /* @__PURE__ */ new Map();
|
|
128
|
-
for (const node of allNodes) {
|
|
129
|
-
kindCounts.set(node.kind, (kindCounts.get(node.kind) ?? 0) + 1);
|
|
130
|
-
}
|
|
131
|
-
const importEdges = allEdges.filter((e) => e.relation === "imports");
|
|
132
|
-
const mostImported = /* @__PURE__ */ new Map();
|
|
133
|
-
for (const edge of importEdges) {
|
|
134
|
-
mostImported.set(edge.target, (mostImported.get(edge.target) ?? 0) + 1);
|
|
135
|
-
}
|
|
136
|
-
const topImported = [...mostImported.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
137
|
-
const lines = [
|
|
138
|
-
AUTOGEN_START,
|
|
139
|
-
"## Codebase Structure (auto-generated by engram)",
|
|
140
|
-
"",
|
|
141
|
-
`**Graph:** ${stats.nodes} nodes, ${stats.edges} edges | ${stats.extractedPct}% extracted, ${stats.inferredPct}% inferred`,
|
|
142
|
-
""
|
|
143
|
-
];
|
|
144
|
-
if (gods.length > 0) {
|
|
145
|
-
lines.push("**Core entities:**");
|
|
146
|
-
for (const g of gods) {
|
|
147
|
-
lines.push(`- \`${g.node.label}\` (${g.node.kind}, ${g.degree} connections) \u2014 ${g.node.sourceFile}`);
|
|
148
|
-
}
|
|
149
|
-
lines.push("");
|
|
150
|
-
}
|
|
151
|
-
if (filesByDir.size > 0) {
|
|
152
|
-
lines.push("**Structure:**");
|
|
153
|
-
for (const [dir, files] of [...filesByDir.entries()].sort()) {
|
|
154
|
-
lines.push(`- \`${dir}/\` \u2014 ${files.join(", ")}`);
|
|
155
|
-
}
|
|
156
|
-
lines.push("");
|
|
157
|
-
}
|
|
158
|
-
if (topImported.length > 0) {
|
|
159
|
-
lines.push("**Key dependencies (most imported):**");
|
|
160
|
-
for (const [target, count] of topImported) {
|
|
161
|
-
const node = store.getNode(target);
|
|
162
|
-
lines.push(`- \`${node?.label ?? target}\` (imported by ${count} files)`);
|
|
163
|
-
}
|
|
164
|
-
lines.push("");
|
|
165
|
-
}
|
|
166
|
-
const decisions = allNodes.filter((n) => n.kind === "decision");
|
|
167
|
-
const patterns = allNodes.filter((n) => n.kind === "pattern");
|
|
168
|
-
const mistakes = allNodes.filter((n) => n.kind === "mistake");
|
|
169
|
-
if (decisions.length > 0) {
|
|
170
|
-
lines.push("**Decisions:**");
|
|
171
|
-
for (const d of decisions.slice(0, 5)) {
|
|
172
|
-
lines.push(`- ${d.label}`);
|
|
173
|
-
}
|
|
174
|
-
lines.push("");
|
|
175
|
-
}
|
|
176
|
-
if (patterns.length > 0) {
|
|
177
|
-
lines.push("**Patterns:**");
|
|
178
|
-
for (const p of patterns.slice(0, 5)) {
|
|
179
|
-
lines.push(`- ${p.label}`);
|
|
180
|
-
}
|
|
181
|
-
lines.push("");
|
|
182
|
-
}
|
|
183
|
-
if (mistakes.length > 0) {
|
|
184
|
-
lines.push("**Known issues:**");
|
|
185
|
-
for (const m of mistakes.slice(0, 3)) {
|
|
186
|
-
lines.push(`- ${m.label}`);
|
|
187
|
-
}
|
|
188
|
-
lines.push("");
|
|
189
|
-
}
|
|
190
|
-
lines.push(
|
|
191
|
-
'**Tip:** Run `engram query "your question"` for structural context instead of reading files.',
|
|
192
|
-
AUTOGEN_END
|
|
193
|
-
);
|
|
194
|
-
return lines.join("\n");
|
|
195
|
-
}
|
|
196
|
-
function writeToFile(filePath, summary) {
|
|
197
|
-
let content = "";
|
|
198
|
-
if (existsSync2(filePath)) {
|
|
199
|
-
content = readFileSync2(filePath, "utf-8");
|
|
200
|
-
}
|
|
201
|
-
if (content.includes(AUTOGEN_START)) {
|
|
202
|
-
content = content.replace(
|
|
203
|
-
new RegExp(`${AUTOGEN_START}[\\s\\S]*?${AUTOGEN_END}`, "g"),
|
|
204
|
-
summary
|
|
205
|
-
);
|
|
206
|
-
} else {
|
|
207
|
-
content = content.trimEnd() + "\n\n" + summary + "\n";
|
|
208
|
-
}
|
|
209
|
-
writeFileSync2(filePath, content);
|
|
210
|
-
}
|
|
211
|
-
async function autogen(projectRoot, target) {
|
|
212
|
-
const { getStore } = await import("./core-M5N34VUU.js");
|
|
213
|
-
const store = await getStore(projectRoot);
|
|
214
|
-
try {
|
|
215
|
-
const summary = generateSummary(store);
|
|
216
|
-
const stats = store.getStats();
|
|
217
|
-
let targetFile;
|
|
218
|
-
if (target === "claude") {
|
|
219
|
-
targetFile = join2(projectRoot, "CLAUDE.md");
|
|
220
|
-
} else if (target === "cursor") {
|
|
221
|
-
targetFile = join2(projectRoot, ".cursorrules");
|
|
222
|
-
} else if (target === "agents") {
|
|
223
|
-
targetFile = join2(projectRoot, "AGENTS.md");
|
|
224
|
-
} else {
|
|
225
|
-
if (existsSync2(join2(projectRoot, "CLAUDE.md"))) {
|
|
226
|
-
targetFile = join2(projectRoot, "CLAUDE.md");
|
|
227
|
-
} else if (existsSync2(join2(projectRoot, ".cursorrules"))) {
|
|
228
|
-
targetFile = join2(projectRoot, ".cursorrules");
|
|
229
|
-
} else if (existsSync2(join2(projectRoot, "AGENTS.md"))) {
|
|
230
|
-
targetFile = join2(projectRoot, "AGENTS.md");
|
|
231
|
-
} else {
|
|
232
|
-
targetFile = join2(projectRoot, "CLAUDE.md");
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
writeToFile(targetFile, summary);
|
|
236
|
-
return { file: targetFile, nodesIncluded: stats.nodes };
|
|
237
|
-
} finally {
|
|
238
|
-
store.close();
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export {
|
|
243
|
-
install,
|
|
244
|
-
uninstall,
|
|
245
|
-
status,
|
|
246
|
-
autogen
|
|
247
|
-
};
|