@shahmilsaari/memory-core 0.2.14 → 0.2.16
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 +95 -8
- package/dist/{chunk-25Y2KI7M.js → chunk-WUL7HLAA.js} +32 -13
- package/dist/cli.js +543 -99
- package/dist/{db-5X5LTUCB.js → db-MF3VKVKH.js} +1 -1
- package/package.json +5 -2
- package/templates/AGENTS.md.hbs +3 -4
- package/templates/AI_RULES.md.hbs +2 -4
- package/templates/ARCHITECTURE.md.hbs +1 -1
- package/templates/CLAUDE.md.hbs +2 -2
- package/templates/DEVIN.md.hbs +1 -1
- package/templates/PROJECT_MEMORY.md.hbs +3 -6
- package/templates/amazonq-guidelines.md.hbs +1 -1
- package/templates/clinerules.hbs +1 -1
- package/templates/copilot-instructions.md.hbs +2 -2
- package/templates/cursor-rule.mdc.hbs +1 -1
- package/templates/cursorrules.hbs +2 -2
- package/templates/gemini-styleguide.md.hbs +1 -1
- package/templates/jetbrains-ai.md.hbs +1 -1
- package/templates/roo-rule.md.hbs +1 -1
- package/templates/windsurfrules.hbs +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,13 +7,14 @@ import {
|
|
|
7
7
|
deleteMemories,
|
|
8
8
|
deleteMemory,
|
|
9
9
|
getMemory,
|
|
10
|
+
getPool,
|
|
10
11
|
listMemories,
|
|
11
12
|
runMigrations,
|
|
12
13
|
saveMemory,
|
|
13
14
|
searchMemories,
|
|
14
15
|
updateMemory,
|
|
15
16
|
upsertMemory
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-WUL7HLAA.js";
|
|
17
18
|
import "./chunk-KSLFLWB4.js";
|
|
18
19
|
|
|
19
20
|
// src/cli.ts
|
|
@@ -24,7 +25,6 @@ import ora from "ora";
|
|
|
24
25
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync2, appendFileSync, rmSync, unlinkSync as unlinkSync2 } from "fs";
|
|
25
26
|
import { join as join6, dirname as dirname2 } from "path";
|
|
26
27
|
import { homedir } from "os";
|
|
27
|
-
import { execSync as execSync2 } from "child_process";
|
|
28
28
|
|
|
29
29
|
// src/generator.ts
|
|
30
30
|
import { readFileSync as readFileSync2, readdirSync, writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
|
|
@@ -112,6 +112,17 @@ var FRAMEWORK_ARCHITECTURE_MAP = {
|
|
|
112
112
|
"Vue.js": ["vue"],
|
|
113
113
|
Svelte: ["svelte"]
|
|
114
114
|
};
|
|
115
|
+
var KNOWN_ARCHITECTURE_KEYS = /* @__PURE__ */ new Set([
|
|
116
|
+
...Object.values(FRAMEWORK_ARCHITECTURE_MAP).flat(),
|
|
117
|
+
"angular",
|
|
118
|
+
"clean-architecture",
|
|
119
|
+
"express",
|
|
120
|
+
"fastify",
|
|
121
|
+
"hexagonal",
|
|
122
|
+
"modular-monolith",
|
|
123
|
+
"mvc",
|
|
124
|
+
"react-native"
|
|
125
|
+
]);
|
|
115
126
|
function normalizeText(value) {
|
|
116
127
|
return value.toLowerCase().replace(/[`"'()[\]{}.,:;!?/\\<>|=*+-]/g, " ").replace(/\s+/g, " ").trim();
|
|
117
128
|
}
|
|
@@ -139,6 +150,33 @@ function mergeMemory(primary, secondary) {
|
|
|
139
150
|
reason
|
|
140
151
|
};
|
|
141
152
|
}
|
|
153
|
+
function memoryArchitectureKeys(memory) {
|
|
154
|
+
if (memory.architecture && memory.architecture !== "global") {
|
|
155
|
+
return [memory.architecture];
|
|
156
|
+
}
|
|
157
|
+
return (memory.tags ?? []).filter((tag) => KNOWN_ARCHITECTURE_KEYS.has(tag));
|
|
158
|
+
}
|
|
159
|
+
function getStackReason(memory, activeArchitectures) {
|
|
160
|
+
const architectureKeys = memoryArchitectureKeys(memory);
|
|
161
|
+
if (architectureKeys.length === 0) {
|
|
162
|
+
return {
|
|
163
|
+
included: true,
|
|
164
|
+
reason: "global memory: no architecture-specific tag"
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const matched = architectureKeys.filter((architecture) => activeArchitectures.has(architecture));
|
|
168
|
+
if (matched.length > 0) {
|
|
169
|
+
return {
|
|
170
|
+
included: true,
|
|
171
|
+
reason: `matched active architecture: ${matched.join(", ")}`
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const active = [...activeArchitectures].join(", ") || "none detected";
|
|
175
|
+
return {
|
|
176
|
+
included: false,
|
|
177
|
+
reason: `excluded: tagged for ${architectureKeys.join(", ")}; active stack is ${active}`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
142
180
|
function dedupeMemories(memories, threshold = 0.8) {
|
|
143
181
|
const deduped = [];
|
|
144
182
|
for (const memory of memories) {
|
|
@@ -160,6 +198,9 @@ function inferProjectArchitectures(cwd = process.cwd(), config) {
|
|
|
160
198
|
const inferred = /* @__PURE__ */ new Set();
|
|
161
199
|
if (config?.backendArchitecture) inferred.add(config.backendArchitecture);
|
|
162
200
|
if (config?.frontendFramework) inferred.add(config.frontendFramework);
|
|
201
|
+
if (config?.projectType === "backend" && !config.backendArchitecture) {
|
|
202
|
+
inferred.add("clean-architecture");
|
|
203
|
+
}
|
|
163
204
|
const detected = detectProject(cwd);
|
|
164
205
|
for (const architecture of FRAMEWORK_ARCHITECTURE_MAP[detected.framework] ?? []) {
|
|
165
206
|
inferred.add(architecture);
|
|
@@ -169,13 +210,63 @@ function inferProjectArchitectures(cwd = process.cwd(), config) {
|
|
|
169
210
|
function getAllowPatterns(config) {
|
|
170
211
|
return [...new Set(config?.allowPatterns?.filter(Boolean) ?? [])];
|
|
171
212
|
}
|
|
213
|
+
function filterRelevantMemories(memories, config, cwd = process.cwd()) {
|
|
214
|
+
return explainMemorySelection(memories, config, cwd).included;
|
|
215
|
+
}
|
|
216
|
+
function explainMemorySelection(memories, config, cwd = process.cwd(), threshold = 0.8) {
|
|
217
|
+
const activeArchitectures = inferProjectArchitectures(cwd, config);
|
|
218
|
+
const activeSet = new Set(activeArchitectures);
|
|
219
|
+
const included = [];
|
|
220
|
+
const decisions = [];
|
|
221
|
+
for (const memory of memories) {
|
|
222
|
+
const stackDecision = getStackReason(memory, activeSet);
|
|
223
|
+
if (!stackDecision.included) {
|
|
224
|
+
decisions.push({
|
|
225
|
+
memory,
|
|
226
|
+
status: "excluded",
|
|
227
|
+
reason: stackDecision.reason
|
|
228
|
+
});
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const existingIndex = included.findIndex((candidate) => {
|
|
232
|
+
if (candidate.content_hash && memory.content_hash && candidate.content_hash === memory.content_hash) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return similarityScore(candidate.content, memory.content) >= threshold;
|
|
236
|
+
});
|
|
237
|
+
if (existingIndex === -1) {
|
|
238
|
+
included.push(memory);
|
|
239
|
+
decisions.push({
|
|
240
|
+
memory,
|
|
241
|
+
status: "included",
|
|
242
|
+
reason: stackDecision.reason
|
|
243
|
+
});
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
included[existingIndex] = mergeMemory(included[existingIndex], memory);
|
|
247
|
+
decisions.push({
|
|
248
|
+
memory,
|
|
249
|
+
status: "excluded",
|
|
250
|
+
reason: `duplicate or near-duplicate of memory #${included[existingIndex].id}`
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
included,
|
|
255
|
+
excluded: decisions.filter((decision) => decision.status === "excluded"),
|
|
256
|
+
decisions,
|
|
257
|
+
activeArchitectures
|
|
258
|
+
};
|
|
259
|
+
}
|
|
172
260
|
function buildContextQuery(parts, maxLength = 1200) {
|
|
173
261
|
return parts.filter(Boolean).join("\n").slice(0, maxLength);
|
|
174
262
|
}
|
|
175
263
|
async function retrieveContextualMemories(options) {
|
|
264
|
+
return (await retrieveMemorySelection(options)).included;
|
|
265
|
+
}
|
|
266
|
+
async function retrieveMemorySelection(options) {
|
|
176
267
|
const architectures = inferProjectArchitectures(options.cwd, options.config);
|
|
177
268
|
const memories = await retrieve(options.query, architectures, options.limit ?? 15);
|
|
178
|
-
return
|
|
269
|
+
return explainMemorySelection(memories, options.config, options.cwd);
|
|
179
270
|
}
|
|
180
271
|
|
|
181
272
|
// src/generator.ts
|
|
@@ -216,6 +307,23 @@ Handlebars.registerHelper(
|
|
|
216
307
|
(arr) => Array.isArray(arr) ? arr.map((i, idx) => `${idx + 1}. ${i}`).join("\n") : ""
|
|
217
308
|
);
|
|
218
309
|
Handlebars.registerHelper("json", (val) => JSON.stringify(val, null, 2));
|
|
310
|
+
Handlebars.registerHelper("memoryBlock", (memory) => {
|
|
311
|
+
const meta = [memory.type, memory.architecture].filter(Boolean).join(" \xB7 ");
|
|
312
|
+
const label = memory.title ? `${memory.title}: ${memory.content}` : memory.content;
|
|
313
|
+
const lines = [`- [${meta || "memory"}] ${label}`];
|
|
314
|
+
if (memory.reason) lines.push(` Why: ${memory.reason}`);
|
|
315
|
+
if (memory.context?.appliesTo?.length) lines.push(` Use when: ${memory.context.appliesTo.join("; ")}`);
|
|
316
|
+
if (memory.context?.avoidWhen?.length) lines.push(` Avoid when: ${memory.context.avoidWhen.join("; ")}`);
|
|
317
|
+
if (memory.context?.examples?.length) {
|
|
318
|
+
lines.push(" Examples:");
|
|
319
|
+
for (const example of memory.context.examples) lines.push(` - ${example}`);
|
|
320
|
+
}
|
|
321
|
+
if (memory.tags?.length) lines.push(` Tags: ${memory.tags.join(", ")}`);
|
|
322
|
+
if (memory.project_name || memory.context?.source) {
|
|
323
|
+
lines.push(` Source: ${memory.context?.source ?? memory.project_name}`);
|
|
324
|
+
}
|
|
325
|
+
return new Handlebars.SafeString(lines.join("\n"));
|
|
326
|
+
});
|
|
219
327
|
function loadProfile(name) {
|
|
220
328
|
const profilePath = join2(PKG_ROOT, "profiles", `${name}.yml`);
|
|
221
329
|
if (!existsSync2(profilePath)) throw new Error(`Profile not found: ${name}`);
|
|
@@ -229,10 +337,15 @@ function listProfiles(layer) {
|
|
|
229
337
|
if (layer === "frontend") return all.filter((p) => p.layer === "frontend" || p.layer === "fullstack");
|
|
230
338
|
return all;
|
|
231
339
|
}
|
|
232
|
-
function buildTemplateData(options) {
|
|
340
|
+
function buildTemplateData(options, cwd = process.cwd()) {
|
|
233
341
|
const backend = options.backendArchitecture ? loadProfile(options.backendArchitecture) : null;
|
|
234
342
|
const frontend = options.frontendFramework ? loadProfile(options.frontendFramework) : null;
|
|
235
|
-
const dedupedMemories =
|
|
343
|
+
const dedupedMemories = filterRelevantMemories(options.memories, {
|
|
344
|
+
projectType: options.projectType,
|
|
345
|
+
backendArchitecture: options.backendArchitecture,
|
|
346
|
+
frontendFramework: options.frontendFramework,
|
|
347
|
+
language: options.language
|
|
348
|
+
}, cwd);
|
|
236
349
|
const allRules = [
|
|
237
350
|
...backend?.rules ?? [],
|
|
238
351
|
...frontend?.rules ?? []
|
|
@@ -300,7 +413,7 @@ function writeFile(filePath, content) {
|
|
|
300
413
|
return "written";
|
|
301
414
|
}
|
|
302
415
|
async function generate(options, cwd = process.cwd(), onlyAgents) {
|
|
303
|
-
const data = buildTemplateData(options);
|
|
416
|
+
const data = buildTemplateData(options, cwd);
|
|
304
417
|
const written = [];
|
|
305
418
|
const skipped = [];
|
|
306
419
|
const files = onlyAgents ? OUTPUT_FILES.filter((f) => onlyAgents.includes(f.agent)) : OUTPUT_FILES;
|
|
@@ -823,9 +936,28 @@ function toPortableMemory(memory) {
|
|
|
823
936
|
title: memory.title,
|
|
824
937
|
content: memory.content,
|
|
825
938
|
reason: memory.reason,
|
|
939
|
+
context: memory.context,
|
|
826
940
|
tags: memory.tags ?? []
|
|
827
941
|
};
|
|
828
942
|
}
|
|
943
|
+
function normalizeStringArray(value) {
|
|
944
|
+
if (!Array.isArray(value)) return void 0;
|
|
945
|
+
const entries = value.filter((entry) => typeof entry === "string" && entry.trim() !== "");
|
|
946
|
+
return entries.length ? entries : void 0;
|
|
947
|
+
}
|
|
948
|
+
function parseContext(value) {
|
|
949
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
950
|
+
const record = value;
|
|
951
|
+
const context = {};
|
|
952
|
+
const appliesTo = normalizeStringArray(record.appliesTo);
|
|
953
|
+
const avoidWhen = normalizeStringArray(record.avoidWhen);
|
|
954
|
+
const examples = normalizeStringArray(record.examples);
|
|
955
|
+
if (appliesTo) context.appliesTo = appliesTo;
|
|
956
|
+
if (avoidWhen) context.avoidWhen = avoidWhen;
|
|
957
|
+
if (examples) context.examples = examples;
|
|
958
|
+
if (typeof record.source === "string" && record.source.trim() !== "") context.source = record.source;
|
|
959
|
+
return Object.keys(context).length ? context : void 0;
|
|
960
|
+
}
|
|
829
961
|
function writeMemoryFile(memories, cwd = process.cwd()) {
|
|
830
962
|
const path = join3(cwd, MEMORY_FILE);
|
|
831
963
|
writeFileSync2(path, JSON.stringify(memories, null, 2) + "\n", "utf-8");
|
|
@@ -864,6 +996,7 @@ function parseMemoryFile(raw) {
|
|
|
864
996
|
title: typeof record.title === "string" ? record.title : void 0,
|
|
865
997
|
content: record.content,
|
|
866
998
|
reason: typeof record.reason === "string" ? record.reason : void 0,
|
|
999
|
+
context: parseContext(record.context),
|
|
867
1000
|
tags: Array.isArray(record.tags) ? record.tags.filter((tag) => typeof tag === "string") : []
|
|
868
1001
|
};
|
|
869
1002
|
});
|
|
@@ -922,7 +1055,7 @@ async function promptToSaveViolations(violations) {
|
|
|
922
1055
|
default: selected.reason ?? selected.issue ?? ""
|
|
923
1056
|
});
|
|
924
1057
|
const { embed: embed2 } = await import("./embedding-PAYD2JYW.js");
|
|
925
|
-
const { upsertMemory: upsertMemory2 } = await import("./db-
|
|
1058
|
+
const { upsertMemory: upsertMemory2 } = await import("./db-MF3VKVKH.js");
|
|
926
1059
|
await upsertMemory2({
|
|
927
1060
|
type: "rule",
|
|
928
1061
|
scope: "project",
|
|
@@ -939,7 +1072,7 @@ async function promptToSaveViolations(violations) {
|
|
|
939
1072
|
}
|
|
940
1073
|
async function loadIgnorePatterns() {
|
|
941
1074
|
try {
|
|
942
|
-
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-
|
|
1075
|
+
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-MF3VKVKH.js");
|
|
943
1076
|
const ignores = await listMemories2({ type: "ignore", limit: 1e3 });
|
|
944
1077
|
await closePool2();
|
|
945
1078
|
return ignores.map((ignore) => ignore.content);
|
|
@@ -996,6 +1129,124 @@ ${violation.file}`.toLowerCase();
|
|
|
996
1129
|
return !allowPatterns.some((pattern) => haystack.includes(pattern.toLowerCase()));
|
|
997
1130
|
});
|
|
998
1131
|
}
|
|
1132
|
+
function normalizeViolation(value) {
|
|
1133
|
+
if (!value || typeof value !== "object") return null;
|
|
1134
|
+
const candidate = value;
|
|
1135
|
+
if (typeof candidate.rule !== "string" || typeof candidate.issue !== "string") return null;
|
|
1136
|
+
return {
|
|
1137
|
+
rule: candidate.rule,
|
|
1138
|
+
file: typeof candidate.file === "string" ? candidate.file : "diff",
|
|
1139
|
+
line: typeof candidate.line === "number" ? candidate.line : void 0,
|
|
1140
|
+
issue: candidate.issue,
|
|
1141
|
+
suggestion: typeof candidate.suggestion === "string" ? candidate.suggestion : void 0,
|
|
1142
|
+
reason: typeof candidate.reason === "string" ? candidate.reason : void 0
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
function parseModelViolations(raw) {
|
|
1146
|
+
const candidates = [
|
|
1147
|
+
raw,
|
|
1148
|
+
raw.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "")
|
|
1149
|
+
];
|
|
1150
|
+
const objectStart = raw.indexOf("{");
|
|
1151
|
+
const objectEnd = raw.lastIndexOf("}");
|
|
1152
|
+
if (objectStart !== -1 && objectEnd > objectStart) {
|
|
1153
|
+
candidates.push(raw.slice(objectStart, objectEnd + 1));
|
|
1154
|
+
}
|
|
1155
|
+
const arrayStart = raw.indexOf("[");
|
|
1156
|
+
const arrayEnd = raw.lastIndexOf("]");
|
|
1157
|
+
if (arrayStart !== -1 && arrayEnd > arrayStart) {
|
|
1158
|
+
candidates.push(raw.slice(arrayStart, arrayEnd + 1));
|
|
1159
|
+
}
|
|
1160
|
+
for (const candidate of candidates) {
|
|
1161
|
+
try {
|
|
1162
|
+
const parsed = JSON.parse(candidate);
|
|
1163
|
+
const items = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.violations) ? parsed.violations : parsed?.rule ? [parsed] : null;
|
|
1164
|
+
if (!items) continue;
|
|
1165
|
+
return {
|
|
1166
|
+
valid: true,
|
|
1167
|
+
violations: items.map(normalizeViolation).filter((violation) => violation !== null)
|
|
1168
|
+
};
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
return { valid: false, violations: [] };
|
|
1173
|
+
}
|
|
1174
|
+
function getAddedLines(diff) {
|
|
1175
|
+
const lines = [];
|
|
1176
|
+
let currentFile = "diff";
|
|
1177
|
+
let newLineNumber = 0;
|
|
1178
|
+
for (const line of diff.split("\n")) {
|
|
1179
|
+
if (line.startsWith("+++ b/")) {
|
|
1180
|
+
currentFile = line.slice("+++ b/".length);
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
const hunk = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
1184
|
+
if (hunk) {
|
|
1185
|
+
newLineNumber = Number(hunk[1]);
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
1189
|
+
lines.push({
|
|
1190
|
+
file: currentFile,
|
|
1191
|
+
line: Number.isFinite(newLineNumber) ? newLineNumber : void 0,
|
|
1192
|
+
content: line.slice(1)
|
|
1193
|
+
});
|
|
1194
|
+
newLineNumber += 1;
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
if (!line.startsWith("-") && newLineNumber > 0) {
|
|
1198
|
+
newLineNumber += 1;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return lines;
|
|
1202
|
+
}
|
|
1203
|
+
function dedupeViolations(violations) {
|
|
1204
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1205
|
+
const deduped = [];
|
|
1206
|
+
for (const violation of violations) {
|
|
1207
|
+
const key = [
|
|
1208
|
+
violation.rule,
|
|
1209
|
+
violation.file,
|
|
1210
|
+
violation.line ?? "",
|
|
1211
|
+
violation.issue
|
|
1212
|
+
].join("\0");
|
|
1213
|
+
if (seen.has(key)) continue;
|
|
1214
|
+
seen.add(key);
|
|
1215
|
+
deduped.push(violation);
|
|
1216
|
+
}
|
|
1217
|
+
return deduped;
|
|
1218
|
+
}
|
|
1219
|
+
function findDeterministicViolations(diff, rules, avoids, allowPatterns = []) {
|
|
1220
|
+
const rulePhrases = rules.flatMap(
|
|
1221
|
+
(rule) => extractForbiddenPhrases(rule).map((phrase) => ({ rule, phrase }))
|
|
1222
|
+
);
|
|
1223
|
+
const avoidPhrases = avoids.map((avoid) => ({
|
|
1224
|
+
rule: `Avoid: ${avoid}`,
|
|
1225
|
+
phrase: avoid.toLowerCase()
|
|
1226
|
+
}));
|
|
1227
|
+
const phrases = [...rulePhrases, ...avoidPhrases].filter((item) => item.phrase.length > 0);
|
|
1228
|
+
if (phrases.length === 0) return [];
|
|
1229
|
+
const violations = [];
|
|
1230
|
+
for (const addedLine of getAddedLines(diff)) {
|
|
1231
|
+
const normalizedLine = addedLine.content.toLowerCase();
|
|
1232
|
+
if (allowPatterns.some((pattern) => normalizedLine.includes(pattern.toLowerCase()))) {
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
for (const { rule, phrase } of phrases) {
|
|
1236
|
+
if (normalizedLine.includes(phrase)) {
|
|
1237
|
+
violations.push({
|
|
1238
|
+
rule,
|
|
1239
|
+
file: addedLine.file,
|
|
1240
|
+
line: addedLine.line,
|
|
1241
|
+
issue: `Added line contains forbidden phrase: "${phrase}"`,
|
|
1242
|
+
suggestion: "Remove this pattern or add an explicit ignore memory if it is intentional.",
|
|
1243
|
+
reason: reasonMap.get(rule)
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
return dedupeViolations(violations);
|
|
1249
|
+
}
|
|
999
1250
|
async function verifyViolations(diff, violations, allowPatterns, debug) {
|
|
1000
1251
|
if (violations.length === 0) return violations;
|
|
1001
1252
|
const systemPrompt = `You are verifying candidate architecture violations.
|
|
@@ -1023,9 +1274,8 @@ ${JSON.stringify(violations, null, 2)}`;
|
|
|
1023
1274
|
{ role: "system", content: systemPrompt },
|
|
1024
1275
|
{ role: "user", content: userPrompt }
|
|
1025
1276
|
]);
|
|
1026
|
-
const parsed =
|
|
1027
|
-
if (
|
|
1028
|
-
if (Array.isArray(parsed)) return parsed;
|
|
1277
|
+
const parsed = parseModelViolations(raw);
|
|
1278
|
+
if (parsed.valid) return parsed.violations;
|
|
1029
1279
|
return violations;
|
|
1030
1280
|
} catch {
|
|
1031
1281
|
return violations;
|
|
@@ -1142,7 +1392,8 @@ Do not include any text outside the JSON object.`;
|
|
|
1142
1392
|
console.log(chalk.dim(diffToSend));
|
|
1143
1393
|
console.log(chalk.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"));
|
|
1144
1394
|
}
|
|
1145
|
-
|
|
1395
|
+
const deterministicViolations = findDeterministicViolations(diff, rules, avoids, allowPatterns);
|
|
1396
|
+
let modelViolations = [];
|
|
1146
1397
|
try {
|
|
1147
1398
|
const raw = await callChatModel([
|
|
1148
1399
|
{ role: "system", content: systemPrompt },
|
|
@@ -1153,36 +1404,29 @@ ${diffToSend}` }
|
|
|
1153
1404
|
if (options.verbose || options.debug) {
|
|
1154
1405
|
console.log(chalk.gray(` raw response: ${options.debug ? raw : raw.slice(0, 200)}`));
|
|
1155
1406
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
violations = parsed.violations;
|
|
1162
|
-
} else if (parsed?.rule) {
|
|
1163
|
-
violations = [parsed];
|
|
1164
|
-
} else {
|
|
1165
|
-
violations = [];
|
|
1166
|
-
}
|
|
1167
|
-
} catch {
|
|
1168
|
-
violations = [];
|
|
1407
|
+
const parsed = parseModelViolations(raw);
|
|
1408
|
+
if (parsed.valid) {
|
|
1409
|
+
modelViolations = parsed.violations;
|
|
1410
|
+
} else {
|
|
1411
|
+
console.log(chalk.yellow(" \u26A0 AI returned invalid JSON \u2014 using deterministic checks only."));
|
|
1169
1412
|
}
|
|
1170
1413
|
} catch (err) {
|
|
1171
1414
|
if (err.message?.startsWith("MODEL_NOT_FOUND:")) {
|
|
1172
1415
|
printModelMissing(err.message.split(":")[1]);
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
console.log(chalk.yellow("\n \u26A0 Ollama not running \u2014 skipping rule check."));
|
|
1416
|
+
modelViolations = [];
|
|
1417
|
+
} else if (err.cause?.code === "ECONNREFUSED" || err.message?.includes("ECONNREFUSED")) {
|
|
1418
|
+
console.log(chalk.yellow("\n \u26A0 Ollama not running \u2014 using deterministic checks only."));
|
|
1177
1419
|
console.log(chalk.gray(" Start it: ollama serve\n"));
|
|
1178
|
-
|
|
1420
|
+
modelViolations = [];
|
|
1421
|
+
} else {
|
|
1422
|
+
console.log(chalk.yellow(`
|
|
1423
|
+
\u26A0 AI rule check failed: ${err.message}`));
|
|
1424
|
+
console.log(chalk.gray(" Using deterministic checks only.\n"));
|
|
1425
|
+
modelViolations = [];
|
|
1179
1426
|
}
|
|
1180
|
-
console.log(chalk.yellow(`
|
|
1181
|
-
\u26A0 Rule check failed: ${err.message}
|
|
1182
|
-
`));
|
|
1183
|
-
return;
|
|
1184
1427
|
}
|
|
1185
|
-
|
|
1428
|
+
modelViolations = await verifyViolations(diff, modelViolations, allowPatterns, options.debug ?? false);
|
|
1429
|
+
let violations = dedupeViolations([...deterministicViolations, ...modelViolations]);
|
|
1186
1430
|
violations = applyAllowPatterns(violations, allowPatterns);
|
|
1187
1431
|
if (violations.length === 0) {
|
|
1188
1432
|
console.log(chalk.green(" \u2713 No rule violations \u2014 commit allowed.\n"));
|
|
@@ -1454,7 +1698,7 @@ ${JSON.stringify(violations, null, 2)}`;
|
|
|
1454
1698
|
}
|
|
1455
1699
|
async function loadIgnorePatterns2() {
|
|
1456
1700
|
try {
|
|
1457
|
-
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-
|
|
1701
|
+
const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-MF3VKVKH.js");
|
|
1458
1702
|
const ignores = await listMemories2({ type: "ignore", limit: 1e3 });
|
|
1459
1703
|
await closePool2();
|
|
1460
1704
|
return ignores.map((ignore) => ignore.content);
|
|
@@ -1633,6 +1877,55 @@ async function startWatch(options = {}) {
|
|
|
1633
1877
|
});
|
|
1634
1878
|
}
|
|
1635
1879
|
|
|
1880
|
+
// src/remote-install.ts
|
|
1881
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
1882
|
+
var CAVEMAN_INSTALL_URL = "https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh";
|
|
1883
|
+
var MAX_INSTALLER_BYTES = 2e5;
|
|
1884
|
+
var TRUSTED_INSTALL_HOSTS = /* @__PURE__ */ new Set(["raw.githubusercontent.com"]);
|
|
1885
|
+
function assertTrustedInstallerUrl(url) {
|
|
1886
|
+
const parsed = new URL(url);
|
|
1887
|
+
if (parsed.protocol !== "https:") {
|
|
1888
|
+
throw new Error("Remote installer URL must use HTTPS");
|
|
1889
|
+
}
|
|
1890
|
+
if (!TRUSTED_INSTALL_HOSTS.has(parsed.hostname)) {
|
|
1891
|
+
throw new Error(`Remote installer host is not trusted: ${parsed.hostname}`);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function defaultRunScript(script) {
|
|
1895
|
+
return spawnSync3("bash", ["-s"], {
|
|
1896
|
+
input: script,
|
|
1897
|
+
encoding: "utf-8",
|
|
1898
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
async function fetchRemoteInstaller(url, fetchImpl = fetch) {
|
|
1902
|
+
assertTrustedInstallerUrl(url);
|
|
1903
|
+
const response = await fetchImpl(url, {
|
|
1904
|
+
signal: AbortSignal.timeout(15e3)
|
|
1905
|
+
});
|
|
1906
|
+
if (!response.ok) {
|
|
1907
|
+
throw new Error(`Failed to download installer (${response.status})`);
|
|
1908
|
+
}
|
|
1909
|
+
const script = await response.text();
|
|
1910
|
+
if (script.length > MAX_INSTALLER_BYTES) {
|
|
1911
|
+
throw new Error(`Installer is too large (${script.length} bytes)`);
|
|
1912
|
+
}
|
|
1913
|
+
if (script.includes("\0")) {
|
|
1914
|
+
throw new Error("Installer contains null bytes");
|
|
1915
|
+
}
|
|
1916
|
+
return script;
|
|
1917
|
+
}
|
|
1918
|
+
async function installCavemanTokenSaver(options = {}) {
|
|
1919
|
+
const url = options.url ?? CAVEMAN_INSTALL_URL;
|
|
1920
|
+
const script = await fetchRemoteInstaller(url, options.fetchImpl ?? fetch);
|
|
1921
|
+
const result = (options.runScript ?? defaultRunScript)(script);
|
|
1922
|
+
if (result.error) throw result.error;
|
|
1923
|
+
if (result.status !== 0) {
|
|
1924
|
+
const stderr = result.stderr ? String(result.stderr).trim() : "";
|
|
1925
|
+
throw new Error(stderr || `Installer exited with status ${result.status}`);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1636
1929
|
// src/cli.ts
|
|
1637
1930
|
function printBanner(projectName, agentCount, status) {
|
|
1638
1931
|
const pg = status ? status.postgresOk ? chalk3.green(" \u2713 PostgreSQL ") + chalk3.bold("connected") : chalk3.red(" \u2717 PostgreSQL ") + chalk3.bold("not connected \u2014 check DATABASE_URL") : chalk3.green(" \u2713 Memory ") + chalk3.bold("PostgreSQL + pgvector ready");
|
|
@@ -1697,6 +1990,7 @@ async function checkConnections(dbUrl, ollamaUrl, chatModel) {
|
|
|
1697
1990
|
}
|
|
1698
1991
|
var { version } = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf-8"));
|
|
1699
1992
|
var CONFIG_FILE = ".memory-core.json";
|
|
1993
|
+
var LOCAL_GENERATED_FILES = [".memory-core-stats.json"];
|
|
1700
1994
|
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
1701
1995
|
var DEFAULT_EMBEDDING_MODEL = "nomic-embed-text";
|
|
1702
1996
|
var DEFAULT_CHAT_MODEL = "llama3.2";
|
|
@@ -1772,6 +2066,22 @@ function ensureEnvFileIgnored(envPath = getEnvPath()) {
|
|
|
1772
2066
|
`);
|
|
1773
2067
|
}
|
|
1774
2068
|
}
|
|
2069
|
+
function appendMissingGitignoreEntries(entries, heading) {
|
|
2070
|
+
const gitignorePath = join6(process.cwd(), ".gitignore");
|
|
2071
|
+
const existing = existsSync6(gitignorePath) ? readFileSync6(gitignorePath, "utf-8") : "";
|
|
2072
|
+
const existingEntries = new Set(
|
|
2073
|
+
existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
2074
|
+
);
|
|
2075
|
+
const toAdd = entries.filter((entry) => !existingEntries.has(entry));
|
|
2076
|
+
if (toAdd.length === 0) {
|
|
2077
|
+
return 0;
|
|
2078
|
+
}
|
|
2079
|
+
const prefix = existing.trim().length > 0 ? "\n" : "";
|
|
2080
|
+
appendFileSync(gitignorePath, `${prefix}${heading}
|
|
2081
|
+
${toAdd.join("\n")}
|
|
2082
|
+
`);
|
|
2083
|
+
return toAdd.length;
|
|
2084
|
+
}
|
|
1775
2085
|
function normalizeProvider(value) {
|
|
1776
2086
|
const provider2 = value.trim().toLowerCase();
|
|
1777
2087
|
if (provider2 === "ollama" || provider2 === "openai" || provider2 === "anthropic" || provider2 === "minimax") {
|
|
@@ -1858,6 +2168,21 @@ function updateProjectConfig(mutator) {
|
|
|
1858
2168
|
function parseTags(tags) {
|
|
1859
2169
|
return tags ? tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
1860
2170
|
}
|
|
2171
|
+
function parseList(value) {
|
|
2172
|
+
const entries = value?.split(",").map((entry) => entry.trim()).filter(Boolean) ?? [];
|
|
2173
|
+
return entries.length ? entries : void 0;
|
|
2174
|
+
}
|
|
2175
|
+
function buildMemoryContext(opts) {
|
|
2176
|
+
const context = {};
|
|
2177
|
+
const appliesTo = parseList(opts.appliesTo);
|
|
2178
|
+
const avoidWhen = parseList(opts.avoidWhen);
|
|
2179
|
+
const examples = parseList(opts.example);
|
|
2180
|
+
if (appliesTo) context.appliesTo = appliesTo;
|
|
2181
|
+
if (avoidWhen) context.avoidWhen = avoidWhen;
|
|
2182
|
+
if (examples) context.examples = examples;
|
|
2183
|
+
if (opts.source?.trim()) context.source = opts.source.trim();
|
|
2184
|
+
return Object.keys(context).length ? context : void 0;
|
|
2185
|
+
}
|
|
1861
2186
|
function truncate(value, length) {
|
|
1862
2187
|
if (!value) return "";
|
|
1863
2188
|
return value.length > length ? `${value.slice(0, Math.max(0, length - 1))}\u2026` : value;
|
|
@@ -1877,6 +2202,9 @@ function printMemoryTable(memories, title = "Rules in memory") {
|
|
|
1877
2202
|
});
|
|
1878
2203
|
console.log(chalk3.gray("\n Use: memory-core remove <id> | memory-core edit <id>\n"));
|
|
1879
2204
|
}
|
|
2205
|
+
function getCurrentListArchitectures(config) {
|
|
2206
|
+
return inferProjectArchitectures(process.cwd(), config).filter((architecture) => architecture !== "global");
|
|
2207
|
+
}
|
|
1880
2208
|
function printStatusLine(label, value) {
|
|
1881
2209
|
console.log(` ${chalk3.dim(label.padEnd(18))} ${value}`);
|
|
1882
2210
|
}
|
|
@@ -1974,6 +2302,7 @@ async function printProjectStatus() {
|
|
|
1974
2302
|
printStatusLine("Architectures", architectures.length ? architectures.join(", ") : chalk3.gray("none detected"));
|
|
1975
2303
|
printStatusLine("Agents", config?.agents?.length ? `${config.agents.length} selected` : chalk3.gray("none saved"));
|
|
1976
2304
|
printStatusLine("Caveman", config?.caveman?.enabled ? `enabled (${config.caveman.intensity})` : "disabled");
|
|
2305
|
+
printStatusLine("Auto sync", config?.autoSync === false ? "disabled" : "enabled");
|
|
1977
2306
|
printStatusLine("Allow patterns", String(getAllowPatterns(config).length));
|
|
1978
2307
|
printStatusLine("Env file", `${existsSync6(envPath) ? "present" : "missing"} (${envPath.split("/").pop()})`);
|
|
1979
2308
|
printStatusLine("Memory file", existsSync6(memoryFilePath) ? MEMORY_FILE : chalk3.gray("not exported"));
|
|
@@ -2001,6 +2330,94 @@ async function printProjectStatus() {
|
|
|
2001
2330
|
}
|
|
2002
2331
|
console.log();
|
|
2003
2332
|
}
|
|
2333
|
+
function printMemorySelection(selection, limit = 4) {
|
|
2334
|
+
const active = selection.activeArchitectures.join(", ") || "none detected";
|
|
2335
|
+
console.log(chalk3.gray(` Stack filter: ${active}`));
|
|
2336
|
+
const included = selection.decisions.filter((decision) => decision.status === "included");
|
|
2337
|
+
if (included.length > 0) {
|
|
2338
|
+
console.log(chalk3.gray(` Included ${included.length}:`));
|
|
2339
|
+
for (const decision of included.slice(0, limit)) {
|
|
2340
|
+
console.log(chalk3.gray(` + ${decision.memory.content} (${decision.reason})`));
|
|
2341
|
+
}
|
|
2342
|
+
if (included.length > limit) {
|
|
2343
|
+
console.log(chalk3.gray(` \u2026 ${included.length - limit} more included`));
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
if (selection.excluded.length > 0) {
|
|
2347
|
+
console.log(chalk3.gray(` Excluded ${selection.excluded.length}:`));
|
|
2348
|
+
for (const decision of selection.excluded.slice(0, limit)) {
|
|
2349
|
+
console.log(chalk3.gray(` - ${decision.memory.content} (${decision.reason})`));
|
|
2350
|
+
}
|
|
2351
|
+
if (selection.excluded.length > limit) {
|
|
2352
|
+
console.log(chalk3.gray(` \u2026 ${selection.excluded.length - limit} more excluded`));
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
function getConfiguredAgents(config) {
|
|
2357
|
+
const agents = config.agents?.length ? config.agents : AGENT_NAMES.filter((agent) => agent !== "Shared");
|
|
2358
|
+
return [.../* @__PURE__ */ new Set([...agents, "Shared"])];
|
|
2359
|
+
}
|
|
2360
|
+
async function syncGeneratedFiles(config, agents, options = {}) {
|
|
2361
|
+
const memorySpinner = ora(options.label ?? "Syncing memories\u2026").start();
|
|
2362
|
+
let memories = [];
|
|
2363
|
+
try {
|
|
2364
|
+
const archQuery = [config.backendArchitecture, config.frontendFramework, config.language].filter(Boolean).join(" ");
|
|
2365
|
+
const selection = await retrieveMemorySelection({
|
|
2366
|
+
query: archQuery,
|
|
2367
|
+
cwd: process.cwd(),
|
|
2368
|
+
config,
|
|
2369
|
+
limit: 25
|
|
2370
|
+
});
|
|
2371
|
+
memories = selection.included;
|
|
2372
|
+
memorySpinner.succeed(`Found ${memories.length} memories`);
|
|
2373
|
+
if (options.showSelection) {
|
|
2374
|
+
printMemorySelection(selection);
|
|
2375
|
+
}
|
|
2376
|
+
} catch (err) {
|
|
2377
|
+
memorySpinner.warn(`Could not retrieve memories: ${err.message}`);
|
|
2378
|
+
}
|
|
2379
|
+
const spinner = ora("Regenerating agent files\u2026").start();
|
|
2380
|
+
const result = await generate(
|
|
2381
|
+
{
|
|
2382
|
+
projectName: config.projectName,
|
|
2383
|
+
projectType: config.projectType,
|
|
2384
|
+
backendArchitecture: config.backendArchitecture,
|
|
2385
|
+
frontendFramework: config.frontendFramework,
|
|
2386
|
+
language: config.language,
|
|
2387
|
+
memories,
|
|
2388
|
+
caveman: config.caveman
|
|
2389
|
+
},
|
|
2390
|
+
process.cwd(),
|
|
2391
|
+
agents
|
|
2392
|
+
);
|
|
2393
|
+
spinner.succeed(
|
|
2394
|
+
`Synced \u2014 ${chalk3.green(`${result.written.length} updated`)}, ${chalk3.dim(`${result.skipped.length} already up to date`)}`
|
|
2395
|
+
);
|
|
2396
|
+
if (result.written.length > 0) {
|
|
2397
|
+
result.written.forEach((file) => console.log(chalk3.gray(` \u2713 ${file}`)));
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
async function autoSyncGeneratedFiles(config, action, enabled = true) {
|
|
2401
|
+
if (!enabled) {
|
|
2402
|
+
console.log(chalk3.gray(" Auto-sync skipped (--no-sync). Run memory-core sync when ready."));
|
|
2403
|
+
return;
|
|
2404
|
+
}
|
|
2405
|
+
if (!config) {
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
if (config.autoSync === false) {
|
|
2409
|
+
console.log(chalk3.gray(" Auto-sync disabled for this project. Run memory-core sync when ready."));
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
try {
|
|
2413
|
+
await syncGeneratedFiles(config, getConfiguredAgents(config), {
|
|
2414
|
+
label: `Auto-syncing agent files after ${action}\u2026`
|
|
2415
|
+
});
|
|
2416
|
+
} catch (err) {
|
|
2417
|
+
console.log(chalk3.yellow(` Auto-sync skipped: ${err.message}`));
|
|
2418
|
+
console.log(chalk3.gray(" Run memory-core sync manually when ready."));
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2004
2421
|
var program = new Command();
|
|
2005
2422
|
program.name("memory-core").description("Universal AI memory core \u2014 generate AI context files for all coding agents").version(version);
|
|
2006
2423
|
program.command("init").description("Initialize memory-core in the current project").option("--quick", "Use smart defaults and skip optional prompts").action(async (opts) => {
|
|
@@ -2212,11 +2629,20 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
2212
2629
|
message: "Pull relevant memories from previous projects?",
|
|
2213
2630
|
default: true
|
|
2214
2631
|
});
|
|
2215
|
-
|
|
2216
|
-
message: "Install caveman token saver?
|
|
2632
|
+
let installCaveman = quick ? false : await confirm({
|
|
2633
|
+
message: "Install caveman token saver? Downloads and runs the upstream installer.",
|
|
2217
2634
|
default: false
|
|
2218
2635
|
});
|
|
2219
2636
|
let cavemanIntensity = "full";
|
|
2637
|
+
if (installCaveman) {
|
|
2638
|
+
const allowRemoteInstaller = await confirm({
|
|
2639
|
+
message: `Security check: download and execute installer from ${CAVEMAN_INSTALL_URL}?`,
|
|
2640
|
+
default: false
|
|
2641
|
+
});
|
|
2642
|
+
if (!allowRemoteInstaller) {
|
|
2643
|
+
installCaveman = false;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2220
2646
|
if (installCaveman) {
|
|
2221
2647
|
cavemanIntensity = await select({
|
|
2222
2648
|
message: "Caveman intensity?",
|
|
@@ -2258,20 +2684,23 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
2258
2684
|
language,
|
|
2259
2685
|
caveman: { enabled: installCaveman, intensity: cavemanIntensity },
|
|
2260
2686
|
agents: selectedAgents,
|
|
2261
|
-
allowPatterns: []
|
|
2687
|
+
allowPatterns: [],
|
|
2688
|
+
autoSync: true
|
|
2262
2689
|
};
|
|
2263
2690
|
let memories = [];
|
|
2264
2691
|
if (pullMemories) {
|
|
2265
2692
|
const spinner2 = ora("Retrieving relevant memories\u2026").start();
|
|
2266
2693
|
try {
|
|
2267
2694
|
const archQuery = [backendArchitecture, frontendFramework, language].filter(Boolean).join(" ");
|
|
2268
|
-
|
|
2695
|
+
const selection = await retrieveMemorySelection({
|
|
2269
2696
|
query: archQuery,
|
|
2270
2697
|
cwd: process.cwd(),
|
|
2271
2698
|
config,
|
|
2272
2699
|
limit: 20
|
|
2273
2700
|
});
|
|
2701
|
+
memories = selection.included;
|
|
2274
2702
|
spinner2.succeed(`Found ${memories.length} relevant memories`);
|
|
2703
|
+
printMemorySelection(selection);
|
|
2275
2704
|
} catch (err) {
|
|
2276
2705
|
spinner2.warn(`Could not retrieve memories: ${err.message}`);
|
|
2277
2706
|
}
|
|
@@ -2279,10 +2708,7 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
2279
2708
|
if (installCaveman) {
|
|
2280
2709
|
const spinner2 = ora("Installing caveman token saver\u2026").start();
|
|
2281
2710
|
try {
|
|
2282
|
-
|
|
2283
|
-
"curl -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh | bash",
|
|
2284
|
-
{ stdio: "pipe", cwd: process.cwd() }
|
|
2285
|
-
);
|
|
2711
|
+
await installCavemanTokenSaver();
|
|
2286
2712
|
spinner2.succeed("Caveman installed");
|
|
2287
2713
|
} catch (err) {
|
|
2288
2714
|
spinner2.warn(`Caveman install failed: ${err.message}`);
|
|
@@ -2296,15 +2722,11 @@ program.command("init").description("Initialize memory-core in the current proje
|
|
|
2296
2722
|
);
|
|
2297
2723
|
writeProjectConfig(config);
|
|
2298
2724
|
spinner.succeed(`Generated ${written.written.length} files`);
|
|
2299
|
-
const
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
if (toAdd.length > 0) {
|
|
2305
|
-
const block = "\n# memory-core generated files\n" + toAdd.join("\n") + "\n";
|
|
2306
|
-
appendFileSync(gitignorePath, block);
|
|
2307
|
-
console.log(chalk3.green(` \u2713 Added ${toAdd.length} generated files to .gitignore`));
|
|
2725
|
+
const gitignoreEntries = [...written.written, ...LOCAL_GENERATED_FILES];
|
|
2726
|
+
if (gitignoreEntries.length > 0) {
|
|
2727
|
+
const added = appendMissingGitignoreEntries(gitignoreEntries, "# memory-core generated files");
|
|
2728
|
+
if (added > 0) {
|
|
2729
|
+
console.log(chalk3.green(` \u2713 Added ${added} generated files to .gitignore`));
|
|
2308
2730
|
}
|
|
2309
2731
|
}
|
|
2310
2732
|
if (enableHook) {
|
|
@@ -2339,44 +2761,32 @@ program.command("sync").description("Re-pull memories and regenerate AI agent fi
|
|
|
2339
2761
|
console.log(chalk3.yellow(" No agents selected \u2014 nothing to sync."));
|
|
2340
2762
|
process.exit(0);
|
|
2341
2763
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
limit: 25
|
|
2351
|
-
});
|
|
2352
|
-
spinner.text = `Found ${memories.length} memories \u2014 regenerating files\u2026`;
|
|
2353
|
-
} catch (err) {
|
|
2354
|
-
spinner.warn(`Could not retrieve memories: ${err.message}`);
|
|
2764
|
+
await syncGeneratedFiles(config, [...selectedAgents, "Shared"], { showSelection: true });
|
|
2765
|
+
await closePool();
|
|
2766
|
+
});
|
|
2767
|
+
program.command("auto-sync [mode]").description("Show or change automatic agent file sync (on|off)").action((mode) => {
|
|
2768
|
+
const config = readProjectConfig();
|
|
2769
|
+
if (!config) {
|
|
2770
|
+
console.error(chalk3.red("No .memory-core.json found. Run: memory-core init"));
|
|
2771
|
+
process.exit(1);
|
|
2355
2772
|
}
|
|
2356
|
-
const
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
language: config.language,
|
|
2363
|
-
memories,
|
|
2364
|
-
caveman: config.caveman
|
|
2365
|
-
},
|
|
2366
|
-
process.cwd(),
|
|
2367
|
-
[...selectedAgents, "Shared"]
|
|
2368
|
-
);
|
|
2369
|
-
const updatedCount = result.written.length;
|
|
2370
|
-
const skippedCount = result.skipped.length;
|
|
2371
|
-
spinner.succeed(
|
|
2372
|
-
`Synced \u2014 ${chalk3.green(`${updatedCount} updated`)}, ${chalk3.dim(`${skippedCount} already up to date`)}`
|
|
2373
|
-
);
|
|
2374
|
-
if (result.written.length > 0) {
|
|
2375
|
-
result.written.forEach((f) => console.log(chalk3.gray(` \u2713 ${f}`)));
|
|
2773
|
+
const normalized = mode?.trim().toLowerCase();
|
|
2774
|
+
if (!normalized || normalized === "status") {
|
|
2775
|
+
console.log(chalk3.bold("\n Auto-sync\n"));
|
|
2776
|
+
console.log(` Status: ${config.autoSync === false ? chalk3.yellow("disabled") : chalk3.green("enabled")}`);
|
|
2777
|
+
console.log(chalk3.gray(" Manual sync is always available: memory-core sync\n"));
|
|
2778
|
+
return;
|
|
2376
2779
|
}
|
|
2377
|
-
|
|
2780
|
+
if (normalized !== "on" && normalized !== "off") {
|
|
2781
|
+
console.error(chalk3.red("Use: memory-core auto-sync [on|off|status]"));
|
|
2782
|
+
process.exit(1);
|
|
2783
|
+
}
|
|
2784
|
+
const enabled = normalized === "on";
|
|
2785
|
+
writeProjectConfig({ ...config, autoSync: enabled });
|
|
2786
|
+
console.log(chalk3.green(`Auto-sync ${enabled ? "enabled" : "disabled"}`));
|
|
2787
|
+
console.log(chalk3.gray(" Manual sync is always available: memory-core sync"));
|
|
2378
2788
|
});
|
|
2379
|
-
program.command("remember <text>").description("Save a new memory to the central database").option("-t, --type <type>", "Memory type (decision|rule|pattern|note)", "decision").option("-s, --scope <scope>", "Scope (global|project)", "project").option("--tags <tags>", "Comma-separated tags").option("-r, --reason <reason>", "Why this rule exists \u2014 helps agents understand intent and debug violations").action(async (text, opts) => {
|
|
2789
|
+
program.command("remember <text>").description("Save a new memory to the central database").option("-t, --type <type>", "Memory type (decision|rule|pattern|note)", "decision").option("-s, --scope <scope>", "Scope (global|project)", "project").option("--tags <tags>", "Comma-separated tags").option("-r, --reason <reason>", "Why this rule exists \u2014 helps agents understand intent and debug violations").option("--applies-to <items>", "Comma-separated situations where this memory applies").option("--avoid-when <items>", "Comma-separated situations where this memory should not be used").option("--example <items>", "Comma-separated examples that teach agents how to apply this memory").option("--source <source>", "Human-readable source for this memory").option("--no-sync", "Skip automatic agent file sync after saving").action(async (text, opts) => {
|
|
2380
2790
|
const config = readProjectConfig();
|
|
2381
2791
|
let reason = opts.reason;
|
|
2382
2792
|
if (!reason) {
|
|
@@ -2395,12 +2805,14 @@ program.command("remember <text>").description("Save a new memory to the central
|
|
|
2395
2805
|
projectName: config?.projectName,
|
|
2396
2806
|
content: text,
|
|
2397
2807
|
reason: reason || void 0,
|
|
2398
|
-
|
|
2808
|
+
context: buildMemoryContext(opts),
|
|
2809
|
+
tags: parseTags(opts.tags),
|
|
2399
2810
|
embedding
|
|
2400
2811
|
});
|
|
2401
2812
|
const reasonLine = reason ? chalk3.gray(`
|
|
2402
2813
|
Why: ${reason}`) : "";
|
|
2403
2814
|
spinner.succeed(chalk3.green(`Memory saved: "${text}"`) + reasonLine);
|
|
2815
|
+
await autoSyncGeneratedFiles(config, "remember", opts.sync);
|
|
2404
2816
|
} catch (err) {
|
|
2405
2817
|
spinner.fail(`Failed: ${err.message}`);
|
|
2406
2818
|
process.exit(1);
|
|
@@ -2428,6 +2840,10 @@ program.command("search <query>").description("Search memories using semantic si
|
|
|
2428
2840
|
const sim = m.similarity ? chalk3.gray(` (${(m.similarity * 100).toFixed(0)}% match)`) : "";
|
|
2429
2841
|
console.log(chalk3.cyan(` ${i + 1}. [${m.type}] ${m.title ?? ""}`));
|
|
2430
2842
|
console.log(chalk3.white(` ${m.content}`) + sim);
|
|
2843
|
+
if (m.reason) console.log(chalk3.gray(` why: ${m.reason}`));
|
|
2844
|
+
if (m.context?.appliesTo?.length) console.log(chalk3.gray(` use when: ${m.context.appliesTo.join("; ")}`));
|
|
2845
|
+
if (m.context?.avoidWhen?.length) console.log(chalk3.gray(` avoid when: ${m.context.avoidWhen.join("; ")}`));
|
|
2846
|
+
if (m.context?.examples?.length) console.log(chalk3.gray(` examples: ${m.context.examples.join("; ")}`));
|
|
2431
2847
|
if (m.tags?.length) console.log(chalk3.gray(` tags: ${m.tags.join(", ")}`));
|
|
2432
2848
|
console.log();
|
|
2433
2849
|
});
|
|
@@ -2455,9 +2871,10 @@ program.command("export").description(`Export DB memories to ${MEMORY_FILE}`).op
|
|
|
2455
2871
|
await closePool();
|
|
2456
2872
|
}
|
|
2457
2873
|
});
|
|
2458
|
-
program.command("import").description(`Import memories from ${MEMORY_FILE}`).option("--url <url>", "Import memories from a remote URL").option("-f, --file <file>", `Import file (default: ${MEMORY_FILE})`).action(async (opts) => {
|
|
2874
|
+
program.command("import").description(`Import memories from ${MEMORY_FILE}`).option("--url <url>", "Import memories from a remote URL").option("-f, --file <file>", `Import file (default: ${MEMORY_FILE})`).option("--no-sync", "Skip automatic agent file sync after import").action(async (opts) => {
|
|
2459
2875
|
const spinner = ora("Reading memories\u2026").start();
|
|
2460
2876
|
try {
|
|
2877
|
+
const config = readProjectConfig();
|
|
2461
2878
|
const memories = opts.url ? await readMemoryFileFromUrl(opts.url) : opts.file ? parseMemoryFile(readFileSync6(join6(process.cwd(), opts.file), "utf-8")) : readMemoryFile();
|
|
2462
2879
|
let inserted = 0;
|
|
2463
2880
|
let skipped = 0;
|
|
@@ -2472,6 +2889,7 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
|
|
|
2472
2889
|
title: memory.title,
|
|
2473
2890
|
content: memory.content,
|
|
2474
2891
|
reason: memory.reason,
|
|
2892
|
+
context: memory.context,
|
|
2475
2893
|
tags: memory.tags,
|
|
2476
2894
|
embedding
|
|
2477
2895
|
});
|
|
@@ -2479,6 +2897,9 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
|
|
|
2479
2897
|
else skipped++;
|
|
2480
2898
|
}
|
|
2481
2899
|
spinner.succeed(`Imported ${inserted} memories, skipped ${skipped} duplicates`);
|
|
2900
|
+
if (inserted > 0) {
|
|
2901
|
+
await autoSyncGeneratedFiles(config, "import", opts.sync);
|
|
2902
|
+
}
|
|
2482
2903
|
} catch (err) {
|
|
2483
2904
|
spinner.fail(`Import failed: ${err.message}`);
|
|
2484
2905
|
process.exit(1);
|
|
@@ -2486,15 +2907,24 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
|
|
|
2486
2907
|
await closePool();
|
|
2487
2908
|
}
|
|
2488
2909
|
});
|
|
2489
|
-
program.command("list").description("List memories from the local database").option("--type <type>", "Filter by type").option("--scope <scope>", "Filter by scope").option("--arch <architecture>", "Filter by architecture").option("-n, --limit <n>", "Maximum rows to show", "200").action(async (opts) => {
|
|
2910
|
+
program.command("list").description("List memories from the local database").option("--type <type>", "Filter by type").option("--scope <scope>", "Filter by scope").option("--arch <architecture>", "Filter by architecture").option("--project <name>", "Filter by project name").option("--current", "Only show memories relevant to the current project (default)").option("--all", "Show all memories in the database").option("-n, --limit <n>", "Maximum rows to show", "200").action(async (opts) => {
|
|
2490
2911
|
try {
|
|
2912
|
+
const config = readProjectConfig();
|
|
2913
|
+
const architectures = opts.arch ? opts.arch : opts.all ? void 0 : getCurrentListArchitectures(config);
|
|
2914
|
+
const projectName = opts.project ? opts.project : opts.all || opts.arch ? void 0 : config?.projectName;
|
|
2491
2915
|
const memories = await listMemories({
|
|
2492
2916
|
type: opts.type,
|
|
2493
2917
|
scope: opts.scope,
|
|
2494
|
-
architecture:
|
|
2918
|
+
architecture: architectures,
|
|
2919
|
+
projectName,
|
|
2920
|
+
includeGlobal: !opts.all,
|
|
2495
2921
|
limit: parseInt(opts.limit, 10)
|
|
2496
2922
|
});
|
|
2497
|
-
|
|
2923
|
+
const title = opts.all ? "All memories" : `Current project memories${architectures ? ` (${Array.isArray(architectures) ? architectures.join(", ") : architectures})` : ""}`;
|
|
2924
|
+
printMemoryTable(memories, title);
|
|
2925
|
+
if (!opts.all) {
|
|
2926
|
+
console.log(chalk3.gray(" Showing current project context plus shared/global memories. Use --all for the full database.\n"));
|
|
2927
|
+
}
|
|
2498
2928
|
} catch (err) {
|
|
2499
2929
|
console.error(chalk3.red(`List failed: ${err.message}`));
|
|
2500
2930
|
process.exit(1);
|
|
@@ -2502,14 +2932,16 @@ program.command("list").description("List memories from the local database").opt
|
|
|
2502
2932
|
await closePool();
|
|
2503
2933
|
}
|
|
2504
2934
|
});
|
|
2505
|
-
program.command("remove <id>").description("Remove a memory by ID").action(async (id) => {
|
|
2935
|
+
program.command("remove <id>").description("Remove a memory by ID").option("--no-sync", "Skip automatic agent file sync after removal").action(async (id, opts) => {
|
|
2506
2936
|
try {
|
|
2937
|
+
const config = readProjectConfig();
|
|
2507
2938
|
const deleted = await deleteMemory(parseInt(id, 10));
|
|
2508
2939
|
if (!deleted) {
|
|
2509
2940
|
console.log(chalk3.yellow(`No memory found with ID ${id}`));
|
|
2510
2941
|
process.exit(1);
|
|
2511
2942
|
}
|
|
2512
2943
|
console.log(chalk3.green(`Removed memory ${id}`));
|
|
2944
|
+
await autoSyncGeneratedFiles(config, "remove", opts.sync);
|
|
2513
2945
|
} catch (err) {
|
|
2514
2946
|
console.error(chalk3.red(`Remove failed: ${err.message}`));
|
|
2515
2947
|
process.exit(1);
|
|
@@ -2517,8 +2949,9 @@ program.command("remove <id>").description("Remove a memory by ID").action(async
|
|
|
2517
2949
|
await closePool();
|
|
2518
2950
|
}
|
|
2519
2951
|
});
|
|
2520
|
-
program.command("forget").description("Bulk-delete memories by tag, scope, type, or architecture").option("--tag <tag>", "Delete memories with this tag").option("--scope <scope>", "Filter by scope").option("--type <type>", "Filter by type").option("--arch <architecture>", "Filter by architecture").action(async (opts) => {
|
|
2952
|
+
program.command("forget").description("Bulk-delete memories by tag, scope, type, or architecture").option("--tag <tag>", "Delete memories with this tag").option("--scope <scope>", "Filter by scope").option("--type <type>", "Filter by type").option("--arch <architecture>", "Filter by architecture").option("--no-sync", "Skip automatic agent file sync after deletion").action(async (opts) => {
|
|
2521
2953
|
try {
|
|
2954
|
+
const config = readProjectConfig();
|
|
2522
2955
|
const deleted = await deleteMemories({
|
|
2523
2956
|
tag: opts.tag,
|
|
2524
2957
|
scope: opts.scope,
|
|
@@ -2526,6 +2959,9 @@ program.command("forget").description("Bulk-delete memories by tag, scope, type,
|
|
|
2526
2959
|
architecture: opts.arch
|
|
2527
2960
|
});
|
|
2528
2961
|
console.log(chalk3.green(`Deleted ${deleted} memories`));
|
|
2962
|
+
if (deleted > 0) {
|
|
2963
|
+
await autoSyncGeneratedFiles(config, "forget", opts.sync);
|
|
2964
|
+
}
|
|
2529
2965
|
} catch (err) {
|
|
2530
2966
|
console.error(chalk3.red(`Forget failed: ${err.message}`));
|
|
2531
2967
|
process.exit(1);
|
|
@@ -2533,9 +2969,10 @@ program.command("forget").description("Bulk-delete memories by tag, scope, type,
|
|
|
2533
2969
|
await closePool();
|
|
2534
2970
|
}
|
|
2535
2971
|
});
|
|
2536
|
-
program.command("edit <id>").description("Edit a memory interactively").action(async (id) => {
|
|
2972
|
+
program.command("edit <id>").description("Edit a memory interactively").option("--no-sync", "Skip automatic agent file sync after editing").action(async (id, opts) => {
|
|
2537
2973
|
const memoryId = parseInt(id, 10);
|
|
2538
2974
|
try {
|
|
2975
|
+
const config = readProjectConfig();
|
|
2539
2976
|
const existing = await getMemory(memoryId);
|
|
2540
2977
|
if (!existing) {
|
|
2541
2978
|
console.log(chalk3.yellow(`No memory found with ID ${id}`));
|
|
@@ -2546,6 +2983,10 @@ program.command("edit <id>").description("Edit a memory interactively").action(a
|
|
|
2546
2983
|
const title = await input({ message: "Title?", default: existing.title ?? "" });
|
|
2547
2984
|
const content = await input({ message: "Content?", default: existing.content });
|
|
2548
2985
|
const reason = await input({ message: "Reason?", default: existing.reason ?? "" });
|
|
2986
|
+
const appliesTo = await input({ message: "Applies to? (comma-separated)", default: existing.context?.appliesTo?.join(",") ?? "" });
|
|
2987
|
+
const avoidWhen = await input({ message: "Avoid when? (comma-separated)", default: existing.context?.avoidWhen?.join(",") ?? "" });
|
|
2988
|
+
const examples = await input({ message: "Examples? (comma-separated)", default: existing.context?.examples?.join(",") ?? "" });
|
|
2989
|
+
const source = await input({ message: "Source?", default: existing.context?.source ?? "" });
|
|
2549
2990
|
const tags = await input({ message: "Tags?", default: existing.tags.join(",") });
|
|
2550
2991
|
const embedding = content === existing.content ? void 0 : await embed(content);
|
|
2551
2992
|
await updateMemory(memoryId, {
|
|
@@ -2554,10 +2995,12 @@ program.command("edit <id>").description("Edit a memory interactively").action(a
|
|
|
2554
2995
|
title: title || void 0,
|
|
2555
2996
|
content,
|
|
2556
2997
|
reason: reason || void 0,
|
|
2998
|
+
context: buildMemoryContext({ appliesTo, avoidWhen, example: examples, source }),
|
|
2557
2999
|
tags: parseTags(tags),
|
|
2558
3000
|
embedding
|
|
2559
3001
|
});
|
|
2560
3002
|
console.log(chalk3.green(`Updated memory ${id}`));
|
|
3003
|
+
await autoSyncGeneratedFiles(config, "edit", opts.sync);
|
|
2561
3004
|
} catch (err) {
|
|
2562
3005
|
console.error(chalk3.red(`Edit failed: ${err.message}`));
|
|
2563
3006
|
process.exit(1);
|
|
@@ -2565,8 +3008,9 @@ program.command("edit <id>").description("Edit a memory interactively").action(a
|
|
|
2565
3008
|
await closePool();
|
|
2566
3009
|
}
|
|
2567
3010
|
});
|
|
2568
|
-
program.command("ignore [pattern]").description("Manage project-scoped false-positive ignore patterns").option("--list", "List ignored patterns").option("--remove <id>", "Remove an ignored pattern by ID").action(async (pattern, opts) => {
|
|
3011
|
+
program.command("ignore [pattern]").description("Manage project-scoped false-positive ignore patterns").option("--list", "List ignored patterns").option("--remove <id>", "Remove an ignored pattern by ID").option("--no-sync", "Skip automatic agent file sync after changing ignore patterns").action(async (pattern, opts) => {
|
|
2569
3012
|
try {
|
|
3013
|
+
const config = readProjectConfig();
|
|
2570
3014
|
if (opts.list) {
|
|
2571
3015
|
printMemoryTable(await listMemories({ type: "ignore", limit: 1e3 }), "Ignored patterns");
|
|
2572
3016
|
return;
|
|
@@ -2578,13 +3022,13 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
|
|
|
2578
3022
|
process.exit(1);
|
|
2579
3023
|
}
|
|
2580
3024
|
console.log(chalk3.green(`Removed ignore pattern ${opts.remove}`));
|
|
3025
|
+
await autoSyncGeneratedFiles(config, "ignore remove", opts.sync);
|
|
2581
3026
|
return;
|
|
2582
3027
|
}
|
|
2583
3028
|
if (!pattern) {
|
|
2584
3029
|
console.error(chalk3.red("Provide a pattern, --list, or --remove <id>"));
|
|
2585
3030
|
process.exit(1);
|
|
2586
3031
|
}
|
|
2587
|
-
const config = readProjectConfig();
|
|
2588
3032
|
const embedding = await embed(pattern);
|
|
2589
3033
|
await upsertMemory({
|
|
2590
3034
|
type: "ignore",
|
|
@@ -2596,6 +3040,7 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
|
|
|
2596
3040
|
embedding
|
|
2597
3041
|
});
|
|
2598
3042
|
console.log(chalk3.green(`Ignored pattern saved: "${pattern}"`));
|
|
3043
|
+
await autoSyncGeneratedFiles(config, "ignore", opts.sync);
|
|
2599
3044
|
} catch (err) {
|
|
2600
3045
|
console.error(chalk3.red(`Ignore failed: ${err.message}`));
|
|
2601
3046
|
process.exit(1);
|
|
@@ -2673,7 +3118,6 @@ program.command("reset").description("Remove memory-core generated files and loc
|
|
|
2673
3118
|
default: false
|
|
2674
3119
|
});
|
|
2675
3120
|
if (ok) {
|
|
2676
|
-
const { getPool } = await import("./db-5X5LTUCB.js");
|
|
2677
3121
|
await getPool().query("DROP TABLE IF EXISTS memories");
|
|
2678
3122
|
await closePool();
|
|
2679
3123
|
console.log(chalk3.yellow("Dropped memories table"));
|