@shahmilsaari/memory-core 1.0.22 → 1.0.25

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/dist/cli.js CHANGED
@@ -1,1293 +1,48 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AGENT_NAMES,
4
+ MEMORY_FILE,
4
5
  OUTPUT_FILES,
5
- buildContextQuery,
6
- callChatModel,
6
+ checkCi,
7
+ checkCommitMsg,
8
+ checkFile,
9
+ checkStaged,
7
10
  closePool,
8
11
  detectProject,
9
- findAstDeterministicViolationsForDiff,
12
+ findSchemaViolations,
10
13
  generate,
11
14
  getAllowPatterns,
12
- getChatProviderLabel,
13
15
  getDefaultApplicationContainer,
14
16
  getPool,
15
17
  inferProjectArchitectures,
18
+ installHook,
16
19
  listProfiles,
17
20
  migrateGraphSnapshots,
21
+ parseMemoryFile,
18
22
  probeGraphSnapshotStore,
19
- retrieveContextualMemories,
23
+ readBypassStats,
24
+ readMemoryFile,
25
+ readMemoryFileFromUrl,
26
+ recordBypass,
20
27
  retrieveMemorySelection,
21
28
  runMigrations,
22
- seeds
23
- } from "./chunk-UZDALJVQ.js";
29
+ seeds,
30
+ toPortableMemory,
31
+ uninstallHook,
32
+ writeMemoryFile
33
+ } from "./chunk-35ZWQFTO.js";
24
34
 
25
35
  // src/cli.ts
26
36
  import { Command } from "commander";
27
37
  import { input, select, confirm } from "@inquirer/prompts";
28
- import chalk2 from "chalk";
38
+ import chalk from "chalk";
29
39
  import ora from "ora";
30
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync, appendFileSync, rmSync } from "fs";
31
- import { join as join3, dirname, resolve } from "path";
40
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync, rmSync } from "fs";
41
+ import { join, dirname, resolve } from "path";
32
42
  import { homedir } from "os";
33
43
 
34
- // src/hook.ts
35
- import { execSync, spawnSync } from "child_process";
36
- import { writeFileSync as writeFileSync2, existsSync as existsSync2, unlinkSync, readFileSync as readFileSync2, chmodSync, statSync } from "fs";
37
- import { join as join2 } from "path";
38
- import chalk from "chalk";
39
-
40
- // src/memory-file.ts
41
- import { existsSync, readFileSync, writeFileSync } from "fs";
42
- import { join } from "path";
43
- var MEMORY_FILE = "memories.json";
44
- function toPortableMemory(memory) {
45
- return {
46
- type: memory.type,
47
- scope: memory.scope,
48
- architecture: memory.architecture,
49
- projectName: memory.project_name,
50
- title: memory.title,
51
- content: memory.content,
52
- reason: memory.reason,
53
- context: memory.context,
54
- tags: memory.tags ?? []
55
- };
56
- }
57
- function normalizeStringArray(value) {
58
- if (!Array.isArray(value)) return void 0;
59
- const entries = value.filter((entry) => typeof entry === "string" && entry.trim() !== "");
60
- return entries.length ? entries : void 0;
61
- }
62
- function parseContext(value) {
63
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
64
- const record = value;
65
- const context = {};
66
- const appliesTo = normalizeStringArray(record.appliesTo);
67
- const avoidWhen = normalizeStringArray(record.avoidWhen);
68
- const examples = normalizeStringArray(record.examples);
69
- if (appliesTo) context.appliesTo = appliesTo;
70
- if (avoidWhen) context.avoidWhen = avoidWhen;
71
- if (examples) context.examples = examples;
72
- if (typeof record.source === "string" && record.source.trim() !== "") context.source = record.source;
73
- return Object.keys(context).length ? context : void 0;
74
- }
75
- function writeMemoryFile(memories, cwd = process.cwd()) {
76
- const path = join(cwd, MEMORY_FILE);
77
- writeFileSync(path, JSON.stringify(memories, null, 2) + "\n", "utf-8");
78
- return path;
79
- }
80
- function readMemoryFile(cwd = process.cwd()) {
81
- const path = join(cwd, MEMORY_FILE);
82
- if (!existsSync(path)) {
83
- throw new Error(`${MEMORY_FILE} not found. Run: memory-core export`);
84
- }
85
- return parseMemoryFile(readFileSync(path, "utf-8"));
86
- }
87
- async function readMemoryFileFromUrl(url) {
88
- const res = await fetch(url, { signal: AbortSignal.timeout(15e3) });
89
- if (!res.ok) throw new Error(`Failed to download ${url}: HTTP ${res.status}`);
90
- return parseMemoryFile(await res.text());
91
- }
92
- function parseMemoryFile(raw) {
93
- const parsed = JSON.parse(raw);
94
- if (!Array.isArray(parsed)) {
95
- throw new Error(`${MEMORY_FILE} must be a JSON array`);
96
- }
97
- return parsed.map((item, index) => {
98
- if (!item || typeof item !== "object") {
99
- throw new Error(`Memory at index ${index} must be an object`);
100
- }
101
- const record = item;
102
- if (typeof record.content !== "string" || record.content.trim() === "") {
103
- throw new Error(`Memory at index ${index} is missing content`);
104
- }
105
- return {
106
- type: typeof record.type === "string" ? record.type : "rule",
107
- scope: typeof record.scope === "string" ? record.scope : "project",
108
- architecture: typeof record.architecture === "string" ? record.architecture : void 0,
109
- projectName: typeof record.projectName === "string" ? record.projectName : void 0,
110
- title: typeof record.title === "string" ? record.title : void 0,
111
- content: record.content,
112
- reason: typeof record.reason === "string" ? record.reason : void 0,
113
- context: parseContext(record.context),
114
- tags: Array.isArray(record.tags) ? record.tags.filter((tag) => typeof tag === "string") : []
115
- };
116
- });
117
- }
118
-
119
- // src/hook.ts
120
- var reasonMap = new Map(
121
- seeds.filter((s) => s.reason).map((s) => [s.content, s.reason])
122
- );
123
- var HOOK_PATH = join2(".git", "hooks", "pre-commit");
124
- var HOOK_MARKER = "# archmind-memory-core";
125
- var COMMIT_MSG_HOOK_PATH = join2(".git", "hooks", "commit-msg");
126
- var COMMIT_MSG_HOOK_MARKER = "# archmind-memory-core commit-msg";
127
- var RULE_CACHE_FILE = ".memory-core-rules-cache.json";
128
- var DB_VERSION_FILE = ".memory-core-db-version";
129
- var RULE_CACHE_TTL_MS = 5 * 60 * 1e3;
130
- function buildHookBody(advisory, fast = false) {
131
- const suffix = advisory ? " || true" : "";
132
- const checkArgs = fast ? "check --staged --fast" : "check --staged";
133
- return `${HOOK_MARKER}${advisory ? " advisory" : ""}
134
- if [ "\${MEMORY_CORE_SKIP_HOOK:-}" = "1" ] || [ "\${ARCHMIND_SKIP_HOOK:-}" = "1" ] || [ "\${HUSKY:-}" = "0" ] || [ "\${HUSKY_SKIP_HOOKS:-}" = "1" ]; then
135
- exit 0
136
- fi
137
- if [ -n "\${SKIP:-}" ] && echo ",$SKIP," | grep -qiE ',(memory-core|archmind),'; then
138
- exit 0
139
- fi
140
- if [ -n "\${SKIP_HOOKS:-}" ]; then
141
- exit 0
142
- fi
143
- if command -v memory-core >/dev/null 2>&1; then
144
- memory-core ${checkArgs}${suffix}
145
- elif [ -f "./node_modules/.bin/memory-core" ]; then
146
- ./node_modules/.bin/memory-core ${checkArgs}${suffix}
147
- elif [ -f "./dist/cli.js" ]; then
148
- node ./dist/cli.js ${checkArgs}${suffix}
149
- else
150
- exit 0
151
- fi
152
- `;
153
- }
154
- function buildHookScript(advisory, fast = false) {
155
- return `#!/bin/sh
156
-
157
- ${buildHookBody(advisory, fast)}`;
158
- }
159
- function normalizeHookPreamble(content) {
160
- const lines = content.split("\n");
161
- const normalized = [];
162
- let shebangSeen = false;
163
- for (const line of lines) {
164
- if (/^\s*#!\/bin\/sh\s*$/.test(line)) {
165
- if (shebangSeen) continue;
166
- shebangSeen = true;
167
- normalized.push("#!/bin/sh");
168
- continue;
169
- }
170
- normalized.push(line);
171
- }
172
- return normalized.join("\n").replace(/\n{3,}/g, "\n\n").trim();
173
- }
174
- function toRuleStatEntry(raw) {
175
- if (raw === void 0) return { count: 0, falsePositives: 0 };
176
- if (typeof raw === "number") return { count: raw, falsePositives: 0 };
177
- return raw;
178
- }
179
- function readPositiveIntEnv(name, fallback) {
180
- const raw = Number(process.env[name]);
181
- return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : fallback;
182
- }
183
- function isFastCheck(options) {
184
- return options.fast === true || process.env.MEMORY_CORE_CHECK_FAST === "1";
185
- }
186
- async function withTimeout(promise, timeoutMs, fallback) {
187
- let timer;
188
- try {
189
- return await Promise.race([
190
- promise,
191
- new Promise((resolve2) => {
192
- timer = setTimeout(() => resolve2(fallback), timeoutMs);
193
- })
194
- ]);
195
- } finally {
196
- if (timer) clearTimeout(timer);
197
- }
198
- }
199
- function recordViolations(violations, source = "hook") {
200
- const statsPath = join2(process.cwd(), ".memory-core-stats.json");
201
- let stats = { rules: {}, files: {} };
202
- if (existsSync2(statsPath)) {
203
- try {
204
- stats = JSON.parse(readFileSync2(statsPath, "utf-8"));
205
- } catch {
206
- stats = { rules: {}, files: {} };
207
- }
208
- }
209
- stats.rules ??= {};
210
- stats.files ??= {};
211
- for (const violation of violations) {
212
- const existing = toRuleStatEntry(stats.rules[violation.rule]);
213
- stats.rules[violation.rule] = { count: existing.count + 1, falsePositives: existing.falsePositives };
214
- if (violation.file) stats.files[violation.file] = (stats.files[violation.file] ?? 0) + 1;
215
- }
216
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
217
- const recent = violations.map((violation) => ({ ...violation, timestamp, source }));
218
- stats.recentViolations = [...recent, ...stats.recentViolations ?? []].slice(0, 50);
219
- writeFileSync2(statsPath, JSON.stringify(stats, null, 2) + "\n", "utf-8");
220
- }
221
- function resetViolationStats(cwd = process.cwd()) {
222
- const statsPath = join2(cwd, ".memory-core-stats.json");
223
- if (!existsSync2(statsPath)) return;
224
- let stats = {};
225
- try {
226
- const parsed = JSON.parse(readFileSync2(statsPath, "utf-8"));
227
- if (parsed && typeof parsed === "object") {
228
- stats = parsed;
229
- }
230
- } catch {
231
- stats = {};
232
- }
233
- stats.rules = {};
234
- stats.files = {};
235
- writeFileSync2(statsPath, JSON.stringify(stats, null, 2) + "\n", "utf-8");
236
- }
237
- async function promptToSaveViolations(violations) {
238
- if (!process.stdin.isTTY || violations.length === 0) return;
239
- try {
240
- const app = getDefaultApplicationContainer();
241
- const { confirm: confirm2, input: input2 } = await import("@inquirer/prompts");
242
- const save = await confirm2({
243
- message: "Save a caught violation as a project rule?",
244
- default: false
245
- });
246
- if (!save) return;
247
- const choices = violations.map((violation, index) => `${index + 1}. ${violation.rule}`);
248
- const selected = violations.length === 1 ? violations[0] : violations[Number(await input2({ message: `Which violation? ${choices.join(" | ")}`, default: "1" })) - 1] ?? violations[0];
249
- const reason = await input2({
250
- message: "Why should this rule exist?",
251
- default: selected.reason ?? selected.issue ?? ""
252
- });
253
- const storedReason = reason.trim() || selected.reason || selected.issue || `Captured from violation: ${selected.rule}`;
254
- await app.services.memoryEngine.remember({
255
- type: "rule",
256
- scope: "project",
257
- content: selected.rule,
258
- reason: storedReason,
259
- tags: ["violation"]
260
- });
261
- console.log(chalk.green(" \u2713 Saved as project rule. Run memory-core sync to propagate it.\n"));
262
- } catch (err) {
263
- console.log(chalk.yellow(` Could not save violation: ${err.message}
264
- `));
265
- }
266
- }
267
- function readRuleCache(cwd) {
268
- const cachePath = join2(cwd, RULE_CACHE_FILE);
269
- const configPath = join2(cwd, ".memory-core.json");
270
- if (!existsSync2(cachePath) || !existsSync2(configPath)) return null;
271
- try {
272
- const entry = JSON.parse(readFileSync2(cachePath, "utf-8"));
273
- const now = Date.now();
274
- if (now - entry.timestamp > RULE_CACHE_TTL_MS) return null;
275
- const configMtime = statSync(configPath).mtimeMs;
276
- if (configMtime !== entry.configMtime) return null;
277
- const dbVersionPath = join2(cwd, DB_VERSION_FILE);
278
- const dbVersionMtime = existsSync2(dbVersionPath) ? statSync(dbVersionPath).mtimeMs : 0;
279
- if (dbVersionMtime !== entry.dbVersionMtime) return null;
280
- return entry;
281
- } catch {
282
- return null;
283
- }
284
- }
285
- function saveRuleCache(cwd, data) {
286
- const configPath = join2(cwd, ".memory-core.json");
287
- try {
288
- const configMtime = statSync(configPath).mtimeMs;
289
- const dbVersionPath = join2(cwd, DB_VERSION_FILE);
290
- const dbVersionMtime = existsSync2(dbVersionPath) ? statSync(dbVersionPath).mtimeMs : 0;
291
- const entry = {
292
- timestamp: Date.now(),
293
- configMtime,
294
- dbVersionMtime,
295
- ...data
296
- };
297
- writeFileSync2(join2(cwd, RULE_CACHE_FILE), JSON.stringify(entry, null, 2) + "\n", "utf-8");
298
- } catch {
299
- }
300
- }
301
- async function loadIgnorePatterns() {
302
- try {
303
- const app = getDefaultApplicationContainer();
304
- const ignores = await app.services.memoryEngine.list({ type: "ignore", limit: 1e3 });
305
- return ignores.map((ignore) => ignore.content);
306
- } catch {
307
- return [];
308
- }
309
- }
310
- function getProfileRules(config) {
311
- const rules = [];
312
- const avoids = [];
313
- if (config.backendArchitecture) {
314
- const profile = listProfiles("backend").find((p) => p.name === config.backendArchitecture);
315
- if (profile) {
316
- rules.push(...profile.rules);
317
- avoids.push(...profile.avoid);
318
- }
319
- }
320
- if (config.frontendFramework) {
321
- const profile = listProfiles("frontend").find((p) => p.name === config.frontendFramework);
322
- if (profile) {
323
- rules.push(...profile.rules);
324
- avoids.push(...profile.avoid);
325
- }
326
- }
327
- return { rules, avoids };
328
- }
329
- async function loadRelevantRules(config, diff, stagedFiles, fallbackRules) {
330
- try {
331
- const query = buildContextQuery([
332
- stagedFiles.join("\n"),
333
- diff.slice(0, 1200),
334
- config.backendArchitecture,
335
- config.frontendFramework,
336
- config.language
337
- ]);
338
- const memories = await retrieveContextualMemories({
339
- query,
340
- cwd: process.cwd(),
341
- config,
342
- limit: 15
343
- });
344
- const selected = memories.filter((memory) => ["rule", "pattern", "decision"].includes(memory.type)).map((memory) => memory.content);
345
- return selected.length > 0 ? selected : fallbackRules;
346
- } catch {
347
- return fallbackRules;
348
- }
349
- }
350
- function applyAllowPatterns(violations, allowPatterns) {
351
- if (allowPatterns.length === 0) return violations;
352
- return violations.filter((violation) => {
353
- const haystack = `${violation.rule}
354
- ${violation.issue}
355
- ${violation.file}`.toLowerCase();
356
- return !allowPatterns.some((pattern) => haystack.includes(pattern.toLowerCase()));
357
- });
358
- }
359
- function normalizeViolation(value) {
360
- if (!value || typeof value !== "object") return null;
361
- const candidate = value;
362
- if (typeof candidate.rule !== "string" || typeof candidate.issue !== "string") return null;
363
- return {
364
- rule: candidate.rule,
365
- file: typeof candidate.file === "string" ? candidate.file : "diff",
366
- line: typeof candidate.line === "number" ? candidate.line : void 0,
367
- issue: candidate.issue,
368
- suggestion: typeof candidate.suggestion === "string" ? candidate.suggestion : void 0,
369
- reason: typeof candidate.reason === "string" ? candidate.reason : void 0
370
- };
371
- }
372
- function parseModelViolations(raw) {
373
- const candidates = [
374
- raw,
375
- raw.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "")
376
- ];
377
- const objectStart = raw.indexOf("{");
378
- const objectEnd = raw.lastIndexOf("}");
379
- if (objectStart !== -1 && objectEnd > objectStart) {
380
- candidates.push(raw.slice(objectStart, objectEnd + 1));
381
- }
382
- const arrayStart = raw.indexOf("[");
383
- const arrayEnd = raw.lastIndexOf("]");
384
- if (arrayStart !== -1 && arrayEnd > arrayStart) {
385
- candidates.push(raw.slice(arrayStart, arrayEnd + 1));
386
- }
387
- for (const candidate of candidates) {
388
- try {
389
- const parsed = JSON.parse(candidate);
390
- const items = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.violations) ? parsed.violations : parsed?.rule ? [parsed] : null;
391
- if (!items) continue;
392
- return {
393
- valid: true,
394
- violations: items.map(normalizeViolation).filter((violation) => violation !== null)
395
- };
396
- } catch {
397
- }
398
- }
399
- return { valid: false, violations: [] };
400
- }
401
- function getAddedLines(diff) {
402
- const lines = [];
403
- let currentFile = "diff";
404
- let newLineNumber = 0;
405
- for (const line of diff.split("\n")) {
406
- if (line.startsWith("+++ b/")) {
407
- currentFile = line.slice("+++ b/".length);
408
- continue;
409
- }
410
- const hunk = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
411
- if (hunk) {
412
- newLineNumber = Number(hunk[1]);
413
- continue;
414
- }
415
- if (line.startsWith("+") && !line.startsWith("+++")) {
416
- lines.push({
417
- file: currentFile,
418
- line: Number.isFinite(newLineNumber) ? newLineNumber : void 0,
419
- content: line.slice(1)
420
- });
421
- newLineNumber += 1;
422
- continue;
423
- }
424
- if (!line.startsWith("-") && newLineNumber > 0) {
425
- newLineNumber += 1;
426
- }
427
- }
428
- return lines;
429
- }
430
- function dedupeViolations(violations) {
431
- const seen = /* @__PURE__ */ new Set();
432
- const deduped = [];
433
- for (const violation of violations) {
434
- const key = [
435
- violation.rule,
436
- violation.file,
437
- violation.line ?? "",
438
- violation.issue
439
- ].join("\0");
440
- if (seen.has(key)) continue;
441
- seen.add(key);
442
- deduped.push(violation);
443
- }
444
- return deduped;
445
- }
446
- function normalizeKeyPath(value) {
447
- return value.replace(/\\/g, "/").replace(/^\.\/+/, "").toLowerCase();
448
- }
449
- function violationRecurrenceKey(violation) {
450
- return [
451
- violation.rule.trim().toLowerCase(),
452
- normalizeKeyPath(violation.file || "diff"),
453
- violation.issue.trim().toLowerCase()
454
- ].join("\0");
455
- }
456
- function findRecurringViolations(currentViolations, recentViolations, minCount = 2) {
457
- if (currentViolations.length === 0 || recentViolations.length === 0) return [];
458
- const counts = /* @__PURE__ */ new Map();
459
- for (const recent of recentViolations) {
460
- const key = violationRecurrenceKey(recent);
461
- counts.set(key, (counts.get(key) ?? 0) + 1);
462
- }
463
- return currentViolations.filter((violation) => (counts.get(violationRecurrenceKey(violation)) ?? 0) >= minCount);
464
- }
465
- function extractIssuePhrase(issue) {
466
- const quoted = issue.match(/"([^"]{3,160})"/);
467
- if (quoted?.[1]) return quoted[1].trim();
468
- const afterColon = issue.split(":").slice(1).join(":").trim();
469
- if (afterColon.length >= 3) return afterColon.slice(0, 180);
470
- const fallback = issue.trim();
471
- return fallback.length >= 3 ? fallback.slice(0, 180) : null;
472
- }
473
- function buildIgnorePatternFromDecision(decision) {
474
- const explicit = decision.pattern?.trim();
475
- if (explicit && explicit.length >= 3) return explicit;
476
- return extractIssuePhrase(decision.issue);
477
- }
478
- function parseFalsePositiveDecision(value) {
479
- if (!value || typeof value !== "object") return null;
480
- const candidate = value;
481
- if (typeof candidate.rule !== "string" || typeof candidate.issue !== "string") return null;
482
- return {
483
- rule: candidate.rule,
484
- file: typeof candidate.file === "string" ? candidate.file : void 0,
485
- line: typeof candidate.line === "number" ? candidate.line : void 0,
486
- issue: candidate.issue,
487
- falsePositive: candidate.falsePositive === true,
488
- pattern: typeof candidate.pattern === "string" ? candidate.pattern : void 0,
489
- reason: typeof candidate.reason === "string" ? candidate.reason : void 0
490
- };
491
- }
492
- function parseFalsePositiveDecisions(raw) {
493
- const candidates = [
494
- raw,
495
- raw.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "")
496
- ];
497
- const objectStart = raw.indexOf("{");
498
- const objectEnd = raw.lastIndexOf("}");
499
- if (objectStart !== -1 && objectEnd > objectStart) {
500
- candidates.push(raw.slice(objectStart, objectEnd + 1));
501
- }
502
- const arrayStart = raw.indexOf("[");
503
- const arrayEnd = raw.lastIndexOf("]");
504
- if (arrayStart !== -1 && arrayEnd > arrayStart) {
505
- candidates.push(raw.slice(arrayStart, arrayEnd + 1));
506
- }
507
- for (const candidate of candidates) {
508
- try {
509
- const parsed = JSON.parse(candidate);
510
- const items = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.decisions) ? parsed.decisions : parsed?.rule ? [parsed] : null;
511
- if (!items) continue;
512
- return {
513
- valid: true,
514
- decisions: items.map(parseFalsePositiveDecision).filter((decision) => decision !== null)
515
- };
516
- } catch {
517
- }
518
- }
519
- return { valid: false, decisions: [] };
520
- }
521
- function loadRecentViolationsFromStats(cwd = process.cwd()) {
522
- const statsPath = join2(cwd, ".memory-core-stats.json");
523
- if (!existsSync2(statsPath)) return [];
524
- try {
525
- const parsed = JSON.parse(readFileSync2(statsPath, "utf-8"));
526
- if (!Array.isArray(parsed.recentViolations)) return [];
527
- return parsed.recentViolations.filter(
528
- (entry) => Boolean(entry) && typeof entry.rule === "string" && typeof entry.issue === "string" && typeof entry.file === "string" && typeof entry.timestamp === "string"
529
- );
530
- } catch {
531
- return [];
532
- }
533
- }
534
- function incrementFalsePositivesForPatterns(learnedPatterns, violations, cwd = process.cwd()) {
535
- if (learnedPatterns.length === 0 || violations.length === 0) return;
536
- const statsPath = join2(cwd, ".memory-core-stats.json");
537
- if (!existsSync2(statsPath)) return;
538
- let stats;
539
- try {
540
- stats = JSON.parse(readFileSync2(statsPath, "utf-8"));
541
- } catch {
542
- return;
543
- }
544
- stats.rules ??= {};
545
- for (const violation of violations) {
546
- const haystack = `${violation.rule}
547
- ${violation.issue}
548
- ${violation.file}`.toLowerCase();
549
- const matched = learnedPatterns.some((p) => haystack.includes(p.toLowerCase()));
550
- if (!matched) continue;
551
- const existing = toRuleStatEntry(stats.rules[violation.rule]);
552
- stats.rules[violation.rule] = { count: existing.count, falsePositives: existing.falsePositives + 1 };
553
- }
554
- writeFileSync2(statsPath, JSON.stringify(stats, null, 2) + "\n", "utf-8");
555
- }
556
- async function learnGlobalIgnoresFromFalsePositives(options) {
557
- if (options.currentViolations.length === 0) return [];
558
- const recentViolations = loadRecentViolationsFromStats();
559
- const recurring = findRecurringViolations(options.currentViolations, recentViolations, 2);
560
- if (recurring.length === 0) return [];
561
- const systemPrompt = `You are verifying repeated architecture-rule alerts.
562
- Mark falsePositive=true ONLY when the alert is clearly a false positive for this staged diff.
563
- For each false positive, return a concise ignore pattern that will suppress only this recurring false alert.
564
- Prefer exact snippets from the issue text.
565
-
566
- Return strict JSON:
567
- {"decisions":[{"rule":"...","file":"...","line":1,"issue":"...","falsePositive":true,"pattern":"...","reason":"..."}]}
568
- Do not include any text outside JSON.`;
569
- const userPrompt = `Staged diff:
570
- ${options.diff.slice(0, 6e3)}
571
-
572
- Recurring violations:
573
- ${JSON.stringify(recurring, null, 2)}
574
-
575
- Existing allow patterns:
576
- ${JSON.stringify(options.allowPatterns, null, 2)}`;
577
- if (options.debug) {
578
- console.log(chalk.gray("\n [debug] false-positive recheck prompt:"));
579
- console.log(chalk.dim(systemPrompt));
580
- console.log(chalk.dim(userPrompt));
581
- 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"));
582
- }
583
- try {
584
- const recheckTimeoutMs = readPositiveIntEnv("MEMORY_CORE_FALSE_POSITIVE_TIMEOUT_MS", 6e3);
585
- const raw = await callChatModel([
586
- { role: "system", content: systemPrompt },
587
- { role: "user", content: userPrompt }
588
- ], { timeoutMs: recheckTimeoutMs });
589
- const parsed = parseFalsePositiveDecisions(raw);
590
- if (!parsed.valid) return [];
591
- const existing = new Set(options.allowPatterns.map((pattern) => pattern.toLowerCase()));
592
- const app = getDefaultApplicationContainer();
593
- const inserted = [];
594
- for (const decision of parsed.decisions) {
595
- if (!decision.falsePositive) continue;
596
- const pattern = buildIgnorePatternFromDecision(decision);
597
- if (!pattern) continue;
598
- const normalized = pattern.toLowerCase();
599
- if (existing.has(normalized)) continue;
600
- try {
601
- await app.services.memoryEngine.remember({
602
- type: "ignore",
603
- scope: "global",
604
- architecture: "global",
605
- content: pattern,
606
- reason: `Auto-added from repeated false-positive recheck for "${decision.rule}"${decision.reason ? `: ${decision.reason}` : ""}`,
607
- tags: ["ignore", "auto-false-positive"]
608
- });
609
- existing.add(normalized);
610
- inserted.push(pattern);
611
- } catch {
612
- }
613
- }
614
- if (inserted.length > 0) {
615
- incrementFalsePositivesForPatterns(inserted, options.currentViolations);
616
- }
617
- return inserted;
618
- } catch {
619
- return [];
620
- }
621
- }
622
- function normalizePath(value) {
623
- return value.replace(/\\/g, "/").replace(/^\.\/+/, "");
624
- }
625
- function resolveChangedFile(candidate, changedFiles) {
626
- const normalizedCandidate = normalizePath(candidate);
627
- const candidates = [normalizedCandidate];
628
- if (/^(?:a|b)\//.test(normalizedCandidate)) {
629
- candidates.push(normalizedCandidate.slice(2));
630
- }
631
- for (const current of candidates) {
632
- if (changedFiles.has(current)) return current;
633
- for (const changed of changedFiles) {
634
- if (changed.endsWith(`/${current}`) || current.endsWith(`/${changed}`)) {
635
- return changed;
636
- }
637
- }
638
- }
639
- return void 0;
640
- }
641
- function buildModelInputFromDiff(diff, maxChars = 8e3) {
642
- const addedLines = getAddedLines(diff);
643
- if (addedLines.length === 0) {
644
- const truncated2 = diff.length > maxChars;
645
- return {
646
- text: truncated2 ? diff.slice(0, maxChars) + "\n\n[diff truncated]" : diff,
647
- source: "diff",
648
- truncated: truncated2
649
- };
650
- }
651
- const chunks = [];
652
- let currentFile = "";
653
- for (const addedLine of addedLines) {
654
- if (addedLine.file !== currentFile) {
655
- currentFile = addedLine.file;
656
- chunks.push(`
657
- # ${currentFile}`);
658
- }
659
- const line = addedLine.line ?? "?";
660
- chunks.push(`${line}: ${addedLine.content}`);
661
- }
662
- const summary = chunks.join("\n").trim();
663
- const truncated = summary.length > maxChars;
664
- return {
665
- text: truncated ? summary.slice(0, maxChars) + "\n\n[added lines truncated]" : summary,
666
- source: "added-lines",
667
- truncated
668
- };
669
- }
670
- function findDeterministicViolations(diff, rules, avoids, allowPatterns = []) {
671
- const rulePhrases = rules.flatMap(
672
- (rule) => extractForbiddenPhrases(rule).map((phrase) => ({ rule, phrase }))
673
- );
674
- const avoidPhrases = avoids.map((avoid) => ({
675
- rule: `Avoid: ${avoid}`,
676
- phrase: avoid.toLowerCase()
677
- }));
678
- const phrases = [...rulePhrases, ...avoidPhrases].filter((item) => item.phrase.length > 0);
679
- if (phrases.length === 0) return [];
680
- const violations = [];
681
- for (const addedLine of getAddedLines(diff)) {
682
- const normalizedLine = addedLine.content.toLowerCase();
683
- if (allowPatterns.some((pattern) => normalizedLine.includes(pattern.toLowerCase()))) {
684
- continue;
685
- }
686
- for (const { rule, phrase } of phrases) {
687
- if (normalizedLine.includes(phrase)) {
688
- violations.push({
689
- rule,
690
- file: addedLine.file,
691
- line: addedLine.line,
692
- issue: `Added line contains forbidden phrase: "${phrase}"`,
693
- suggestion: "Remove this pattern or add an explicit ignore memory if it is intentional.",
694
- reason: reasonMap.get(rule)
695
- });
696
- }
697
- }
698
- }
699
- return dedupeViolations(violations);
700
- }
701
- function suppressBatchRepetitions(violations, threshold = 3) {
702
- const pairCounts = /* @__PURE__ */ new Map();
703
- for (const v of violations) {
704
- const key = `${v.rule}\0${v.file}`;
705
- pairCounts.set(key, (pairCounts.get(key) ?? 0) + 1);
706
- }
707
- const suppressedKeys = /* @__PURE__ */ new Set();
708
- for (const [key, count] of pairCounts) {
709
- if (count >= threshold) suppressedKeys.add(key);
710
- }
711
- if (suppressedKeys.size === 0) return { filtered: violations, suppressedCount: 0 };
712
- const filtered = violations.filter((v) => !suppressedKeys.has(`${v.rule}\0${v.file}`));
713
- return { filtered, suppressedCount: violations.length - filtered.length };
714
- }
715
- function groupViolationsByRule(violations) {
716
- const groups = /* @__PURE__ */ new Map();
717
- for (const v of violations) {
718
- const existing = groups.get(v.rule);
719
- if (existing) {
720
- existing.push(v);
721
- } else {
722
- groups.set(v.rule, [v]);
723
- }
724
- }
725
- return groups;
726
- }
727
- function filterModelViolationsByStagedDiff(violations, stagedFiles, diff) {
728
- if (violations.length === 0) return violations;
729
- const changedFiles = new Set(stagedFiles.map((file) => normalizePath(file)));
730
- if (changedFiles.size === 0) return [];
731
- const linesByFile = /* @__PURE__ */ new Map();
732
- for (const addedLine of getAddedLines(diff)) {
733
- const file = normalizePath(addedLine.file);
734
- if (!changedFiles.has(file)) continue;
735
- if (typeof addedLine.line !== "number") continue;
736
- const list = linesByFile.get(file) ?? [];
737
- list.push(addedLine.line);
738
- linesByFile.set(file, list);
739
- }
740
- const LINE_TOLERANCE = 3;
741
- const filtered = [];
742
- for (const violation of violations) {
743
- if (!violation.file || violation.file === "diff") {
744
- filtered.push(violation);
745
- continue;
746
- }
747
- const resolvedFile = resolveChangedFile(violation.file, changedFiles);
748
- if (!resolvedFile) continue;
749
- const candidateLines = linesByFile.get(resolvedFile) ?? [];
750
- if (typeof violation.line === "number" && candidateLines.length > 0) {
751
- const supported = candidateLines.some((line) => Math.abs(line - violation.line) <= LINE_TOLERANCE);
752
- if (!supported) continue;
753
- }
754
- filtered.push({ ...violation, file: resolvedFile });
755
- }
756
- return filtered;
757
- }
758
- function installHook(advisory = true, fast = false) {
759
- if (!existsSync2(".git")) {
760
- console.error(chalk.red("\n Not a git repository. Run from project root.\n"));
761
- process.exit(1);
762
- }
763
- const script = buildHookScript(advisory, fast);
764
- const body = buildHookBody(advisory, fast).trimEnd();
765
- if (existsSync2(HOOK_PATH)) {
766
- const existing = readFileSync2(HOOK_PATH, "utf-8");
767
- if (existing.includes(HOOK_MARKER)) {
768
- const markerIndex = existing.indexOf(HOOK_MARKER);
769
- const beforeRaw = markerIndex > 0 ? existing.slice(0, markerIndex) : "";
770
- const normalizedBefore = normalizeHookPreamble(beforeRaw);
771
- const preamble = normalizedBefore.length > 0 ? normalizedBefore : "#!/bin/sh";
772
- const preambleWithShebang = preamble.startsWith("#!/bin/sh") ? preamble : `#!/bin/sh
773
- ${preamble}`;
774
- writeFileSync2(HOOK_PATH, `${preambleWithShebang}
775
-
776
- ${body}
777
- `);
778
- chmodSync(HOOK_PATH, 493);
779
- installCommitMsgHook(advisory);
780
- const modeLabel2 = advisory ? chalk.cyan("advisory") : chalk.yellow("strict");
781
- console.log(chalk.green("\n \u2713 Pre-commit hook updated") + chalk.dim(` (${modeLabel2} mode)`));
782
- if (fast) console.log(chalk.gray(` Check mode: fast deterministic checks`));
783
- return;
784
- }
785
- writeFileSync2(HOOK_PATH, existing.trimEnd() + "\n\n" + body + "\n");
786
- } else {
787
- writeFileSync2(HOOK_PATH, script);
788
- }
789
- chmodSync(HOOK_PATH, 493);
790
- installCommitMsgHook(advisory);
791
- const modeLabel = advisory ? "advisory (logs violations, never blocks)" : "strict (blocks commits on violations)";
792
- console.log(chalk.green("\n \u2713 Pre-commit hook installed") + chalk.dim(` \u2014 ${modeLabel}`));
793
- console.log(chalk.gray(fast ? " Check mode: fast deterministic checks" : ` Chat model: ${process.env.OLLAMA_CHAT_MODEL ?? "llama3.2"}`));
794
- console.log(chalk.gray(" Commit message rules: memory-core commit-rules --list"));
795
- console.log(chalk.gray(" To uninstall: memory-core hook uninstall\n"));
796
- }
797
- function uninstallHook() {
798
- if (!existsSync2(HOOK_PATH)) {
799
- console.log(chalk.yellow("\n No pre-commit hook found.\n"));
800
- return;
801
- }
802
- const content = readFileSync2(HOOK_PATH, "utf-8");
803
- if (!content.includes(HOOK_MARKER)) {
804
- console.log(chalk.yellow("\n ArchMind hook not found in pre-commit \u2014 nothing to remove.\n"));
805
- return;
806
- }
807
- const markerIndex = content.indexOf(HOOK_MARKER);
808
- const before = markerIndex > 1 ? normalizeHookPreamble(content.slice(0, markerIndex)) : "";
809
- if (before && before !== "#!/bin/sh") {
810
- writeFileSync2(HOOK_PATH, `${before}
811
- `);
812
- } else {
813
- unlinkSync(HOOK_PATH);
814
- }
815
- uninstallCommitMsgHook();
816
- console.log(chalk.green("\n \u2713 Pre-commit hook removed\n"));
817
- }
818
- function buildCommitMsgHookBody(advisory) {
819
- const suffix = advisory ? " || true" : "";
820
- return `${COMMIT_MSG_HOOK_MARKER}${advisory ? " advisory" : ""}
821
- if [ "\${MEMORY_CORE_SKIP_HOOK:-}" = "1" ] || [ "\${ARCHMIND_SKIP_HOOK:-}" = "1" ] || [ "\${HUSKY:-}" = "0" ] || [ "\${HUSKY_SKIP_HOOKS:-}" = "1" ]; then
822
- exit 0
823
- fi
824
- if [ -n "\${SKIP:-}" ] && echo ",$SKIP," | grep -qiE ',(memory-core|archmind),'; then
825
- exit 0
826
- fi
827
- if [ -n "\${SKIP_HOOKS:-}" ]; then
828
- exit 0
829
- fi
830
- if command -v memory-core >/dev/null 2>&1; then
831
- memory-core check --commit-msg "$1"${suffix}
832
- elif [ -f "./node_modules/.bin/memory-core" ]; then
833
- ./node_modules/.bin/memory-core check --commit-msg "$1"${suffix}
834
- elif [ -f "./dist/cli.js" ]; then
835
- node ./dist/cli.js check --commit-msg "$1"${suffix}
836
- else
837
- exit 0
838
- fi
839
- `;
840
- }
841
- function installCommitMsgHook(advisory = true) {
842
- const body = buildCommitMsgHookBody(advisory).trimEnd();
843
- const script = `#!/bin/sh
844
-
845
- ${body}
846
- `;
847
- if (existsSync2(COMMIT_MSG_HOOK_PATH)) {
848
- const existing = readFileSync2(COMMIT_MSG_HOOK_PATH, "utf-8");
849
- if (existing.includes(COMMIT_MSG_HOOK_MARKER)) {
850
- const markerIndex = existing.indexOf(COMMIT_MSG_HOOK_MARKER);
851
- const beforeRaw = markerIndex > 0 ? existing.slice(0, markerIndex) : "";
852
- const normalizedBefore = normalizeHookPreamble(beforeRaw);
853
- const preamble = normalizedBefore.length > 0 ? normalizedBefore : "#!/bin/sh";
854
- const preambleWithShebang = preamble.startsWith("#!/bin/sh") ? preamble : `#!/bin/sh
855
- ${preamble}`;
856
- writeFileSync2(COMMIT_MSG_HOOK_PATH, `${preambleWithShebang}
857
-
858
- ${body}
859
- `);
860
- } else {
861
- writeFileSync2(COMMIT_MSG_HOOK_PATH, existing.trimEnd() + "\n\n" + body + "\n");
862
- }
863
- } else {
864
- writeFileSync2(COMMIT_MSG_HOOK_PATH, script);
865
- }
866
- chmodSync(COMMIT_MSG_HOOK_PATH, 493);
867
- }
868
- function uninstallCommitMsgHook() {
869
- if (!existsSync2(COMMIT_MSG_HOOK_PATH)) return;
870
- const content = readFileSync2(COMMIT_MSG_HOOK_PATH, "utf-8");
871
- if (!content.includes(COMMIT_MSG_HOOK_MARKER)) return;
872
- const markerIndex = content.indexOf(COMMIT_MSG_HOOK_MARKER);
873
- const before = markerIndex > 1 ? normalizeHookPreamble(content.slice(0, markerIndex)) : "";
874
- if (before && before !== "#!/bin/sh") {
875
- writeFileSync2(COMMIT_MSG_HOOK_PATH, `${before}
876
- `);
877
- } else {
878
- unlinkSync(COMMIT_MSG_HOOK_PATH);
879
- }
880
- }
881
- async function checkCommitMsg(msgFile, options = {}) {
882
- if (!existsSync2(msgFile)) {
883
- if (options.verbose) console.log(chalk.gray(" No commit message file \u2014 skipping."));
884
- return;
885
- }
886
- const raw = readFileSync2(msgFile, "utf-8");
887
- const cleanMsg = raw.split("\n").filter((l) => !l.startsWith("#")).join("\n").trim();
888
- if (!cleanMsg) {
889
- if (options.verbose) console.log(chalk.gray(" Empty commit message \u2014 skipping."));
890
- return;
891
- }
892
- const configPath = join2(process.cwd(), ".memory-core.json");
893
- if (!existsSync2(configPath)) return;
894
- const config = JSON.parse(readFileSync2(configPath, "utf-8"));
895
- const rules = (config.commitRules ?? []).filter(Boolean);
896
- if (rules.length === 0) return;
897
- console.log(chalk.cyan("\n archmind \u2014 checking commit message\u2026"));
898
- const violations = [];
899
- for (const rule of rules) {
900
- try {
901
- const regex = new RegExp(rule.pattern, "im");
902
- const matched = regex.test(cleanMsg);
903
- const violated = rule.negate ? matched : !matched;
904
- if (violated) violations.push({ rule });
905
- } catch {
906
- if (options.debug) console.log(chalk.yellow(` [debug] Invalid regex: "${rule.pattern}"`));
907
- }
908
- }
909
- if (violations.length === 0) {
910
- console.log(chalk.green(" \u2713 Commit message OK.\n"));
911
- return;
912
- }
913
- const blocking = violations.filter((v) => !v.rule.advisory);
914
- violations.forEach(({ rule }) => {
915
- const prefix = rule.advisory ? chalk.yellow(" \u26A0 ") : chalk.red(" \u2717 ");
916
- console.log(prefix + rule.message);
917
- const matchLabel = rule.negate ? "(must NOT match)" : "(must match)";
918
- console.log(chalk.dim(` Pattern: ${rule.pattern} ${matchLabel}`));
919
- });
920
- console.log();
921
- if (blocking.length === 0) return;
922
- console.log(chalk.dim(" Fix the commit message, then commit again."));
923
- console.log(chalk.dim(" To bypass: MEMORY_CORE_SKIP_HOOK=1 git commit"));
924
- console.log(chalk.dim(" Manage rules: memory-core commit-rules --list\n"));
925
- process.exit(1);
926
- }
927
- async function checkStaged(options = {}) {
928
- const SOURCE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|php|rb|go|java|cs|swift|kt|rs|vue|svelte)$/;
929
- let diff;
930
- let stagedFiles = [];
931
- try {
932
- stagedFiles = execSync("git diff --cached --name-only --diff-filter=ACMRT", { encoding: "utf-8" }).split("\n").filter((f) => f && SOURCE_EXTENSIONS.test(f)).map((f) => normalizePath(f));
933
- if (stagedFiles.length === 0) {
934
- if (options.verbose) console.log(chalk.gray(" No source files staged \u2014 skipping rule check."));
935
- return;
936
- }
937
- const result = spawnSync(
938
- "git",
939
- ["diff", "--cached", "--unified=0", "--diff-filter=ACMRT", "--", ...stagedFiles],
940
- { encoding: "utf-8" }
941
- );
942
- diff = result.stdout ?? "";
943
- } catch {
944
- console.error(chalk.red(" Failed to read staged diff."));
945
- process.exit(1);
946
- }
947
- if (!diff.trim()) {
948
- if (options.verbose) console.log(chalk.gray(" No staged changes to check."));
949
- return;
950
- }
951
- const configPath = join2(process.cwd(), ".memory-core.json");
952
- if (!existsSync2(configPath)) return;
953
- const config = JSON.parse(readFileSync2(configPath, "utf-8"));
954
- const { rules: fallbackRules, avoids } = getProfileRules(config);
955
- const fast = isFastCheck(options);
956
- const ruleLoadTimeoutMs = readPositiveIntEnv("MEMORY_CORE_RULE_LOAD_TIMEOUT_MS", 2e3);
957
- const ignoreLoadTimeoutMs = readPositiveIntEnv("MEMORY_CORE_IGNORE_LOAD_TIMEOUT_MS", 1500);
958
- let rules;
959
- let ignores;
960
- let allowPatterns;
961
- if (fast) {
962
- rules = fallbackRules;
963
- ignores = [];
964
- allowPatterns = [...new Set(getAllowPatterns(config))];
965
- } else {
966
- const cwd = process.cwd();
967
- const cached = readRuleCache(cwd);
968
- if (cached) {
969
- rules = cached.rules;
970
- ignores = cached.ignores;
971
- allowPatterns = cached.allowPatterns;
972
- if (options.debug) {
973
- console.log(chalk.gray(" [debug] using cached rules (TTL valid)"));
974
- }
975
- } else {
976
- const [loadedRules, loadedIgnores] = await Promise.all([
977
- withTimeout(loadRelevantRules(config, diff, stagedFiles, fallbackRules), ruleLoadTimeoutMs, fallbackRules),
978
- withTimeout(loadIgnorePatterns(), ignoreLoadTimeoutMs, [])
979
- ]);
980
- rules = loadedRules;
981
- ignores = loadedIgnores;
982
- allowPatterns = [.../* @__PURE__ */ new Set([...getAllowPatterns(config), ...loadedIgnores])];
983
- saveRuleCache(cwd, { rules, ignores, allowPatterns });
984
- }
985
- }
986
- if (rules.length === 0) return;
987
- const modelInputMaxChars = readPositiveIntEnv("MEMORY_CORE_MODEL_INPUT_MAX_CHARS", 8e3);
988
- const modelInput = buildModelInputFromDiff(diff, modelInputMaxChars);
989
- console.log(chalk.cyan("\n archmind \u2014 checking staged changes against rules\u2026"));
990
- if (options.verbose || options.debug) {
991
- const sourceLabel = modelInput.source === "added-lines" ? "added lines" : "diff";
992
- const modelLabel = fast ? "skipped (--fast)" : getChatProviderLabel();
993
- console.log(chalk.gray(` model: ${modelLabel} rules: ${rules.length} diff: ${diff.length} chars input: ${sourceLabel}${modelInput.truncated ? " (truncated)" : ""}`));
994
- }
995
- const rulesWithReasons = rules.map((r, i) => {
996
- const why = reasonMap.get(r);
997
- return why ? `${i + 1}. ${r}
998
- WHY: ${why}` : `${i + 1}. ${r}`;
999
- }).join("\n");
1000
- const systemPrompt = `You are a strict code reviewer enforcing architecture and framework rules.
1001
- Analyze the provided staged changes and identify ONLY clear, definite rule violations \u2014 not style preferences.
1002
- Use the WHY for each rule to understand intent and judge edge cases correctly.
1003
-
1004
- Rules to enforce:
1005
- ${rulesWithReasons}
1006
-
1007
- Things that must never appear:
1008
- ${avoids.map((a, i) => `${i + 1}. ${a}`).join("\n")}
1009
-
1010
- Never flag these accepted project patterns:
1011
- ${allowPatterns.length ? allowPatterns.map((a, i) => `${i + 1}. ${a}`).join("\n") : "(none)"}
1012
-
1013
- IMPORTANT: You MUST respond with a JSON object that has a "violations" key containing an array.
1014
- For each violation include a "reason" field \u2014 copy the WHY from the rule to explain to the developer why this matters.
1015
- Example with violations: {"violations":[{"rule":"Use functional components only","file":"User.tsx","line":3,"issue":"Class component used","suggestion":"Convert to a function component using hooks","reason":"Class components cannot use hooks and the entire React ecosystem now assumes functional components"}]}
1016
- Example with no violations: {"violations":[]}
1017
- Do not include any text outside the JSON object.`;
1018
- if (options.debug) {
1019
- console.log(chalk.gray("\n [debug] prompt:"));
1020
- 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"));
1021
- console.log(systemPrompt);
1022
- 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"));
1023
- console.log(chalk.gray(` [debug] diff length: ${diff.length} chars`));
1024
- console.log(chalk.gray(` [debug] model input source: ${modelInput.source}`));
1025
- console.log(chalk.dim(modelInput.text));
1026
- 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"));
1027
- }
1028
- const deterministicViolations = findDeterministicViolations(diff, rules, avoids, allowPatterns);
1029
- const astViolations = findAstDeterministicViolationsForDiff(diff, {
1030
- cwd: process.cwd(),
1031
- config,
1032
- rules,
1033
- reasonLookup: reasonMap
1034
- });
1035
- let modelViolations = [];
1036
- let aiFallback = fast;
1037
- if (fast) {
1038
- if (options.verbose || options.debug) {
1039
- console.log(chalk.gray(" AI check skipped; running deterministic checks only."));
1040
- }
1041
- } else try {
1042
- const checkTimeoutMs = readPositiveIntEnv("MEMORY_CORE_CHECK_TIMEOUT_MS", readPositiveIntEnv("CHAT_TIMEOUT_MS", 2e4));
1043
- const raw = await callChatModel([
1044
- { role: "system", content: systemPrompt },
1045
- { role: "user", content: `Review these staged changes:
1046
-
1047
- ${modelInput.text}` }
1048
- ], { timeoutMs: checkTimeoutMs });
1049
- if (options.verbose || options.debug) {
1050
- console.log(chalk.gray(` raw response: ${options.debug ? raw : raw.slice(0, 200)}`));
1051
- }
1052
- const parsed = parseModelViolations(raw);
1053
- if (parsed.valid) {
1054
- modelViolations = parsed.violations;
1055
- } else {
1056
- console.log(chalk.yellow(" \u26A0 AI returned invalid JSON \u2014 using deterministic checks only."));
1057
- }
1058
- } catch (err) {
1059
- if (err.message?.startsWith("MODEL_NOT_FOUND:")) {
1060
- printModelMissing(err.message.split(":")[1]);
1061
- aiFallback = true;
1062
- modelViolations = [];
1063
- } else if (err.message?.startsWith("TIMEOUT:")) {
1064
- const timeoutMs = err.message.split(":")[1];
1065
- console.log(chalk.yellow(`
1066
- \u26A0 AI check timed out after ${timeoutMs}ms \u2014 switching to fast deterministic checks for this run.`));
1067
- console.log(chalk.gray(" Set MEMORY_CORE_CHECK_TIMEOUT_MS to tune this.\n"));
1068
- aiFallback = true;
1069
- modelViolations = [];
1070
- } else if (err.cause?.code === "ECONNREFUSED" || err.message?.includes("ECONNREFUSED")) {
1071
- console.log(chalk.yellow("\n \u26A0 Ollama not running \u2014 using deterministic checks only."));
1072
- console.log(chalk.gray(" Start it: ollama serve\n"));
1073
- aiFallback = true;
1074
- modelViolations = [];
1075
- } else {
1076
- console.log(chalk.yellow(`
1077
- \u26A0 AI rule check failed: ${err.message}`));
1078
- console.log(chalk.gray(" Using deterministic checks only.\n"));
1079
- aiFallback = true;
1080
- modelViolations = [];
1081
- }
1082
- }
1083
- modelViolations = filterModelViolationsByStagedDiff(modelViolations, stagedFiles, diff);
1084
- let violations = dedupeViolations([...deterministicViolations, ...astViolations, ...modelViolations]);
1085
- violations = applyAllowPatterns(violations, allowPatterns);
1086
- if (violations.length > 0) {
1087
- const { filtered, suppressedCount } = suppressBatchRepetitions(violations);
1088
- if (suppressedCount > 0) {
1089
- console.log(
1090
- chalk.dim(
1091
- ` \u2139 Auto-suppressed ${suppressedCount} repetitive violation${suppressedCount > 1 ? "s" : ""} (same rule fired \u22653\xD7 on the same file \u2014 consider tuning the rule)`
1092
- )
1093
- );
1094
- violations = filtered;
1095
- }
1096
- }
1097
- if (!aiFallback && violations.length > 0) {
1098
- const learnedPatterns = await learnGlobalIgnoresFromFalsePositives({
1099
- diff,
1100
- currentViolations: violations,
1101
- allowPatterns,
1102
- debug: options.debug
1103
- });
1104
- if (learnedPatterns.length > 0) {
1105
- if (options.verbose || options.debug) {
1106
- console.log(chalk.gray(` learned ${learnedPatterns.length} global ignore pattern${learnedPatterns.length > 1 ? "s" : ""} from false-positive recheck`));
1107
- }
1108
- const refinedAllowPatterns = [.../* @__PURE__ */ new Set([...allowPatterns, ...learnedPatterns])];
1109
- violations = applyAllowPatterns(violations, refinedAllowPatterns);
1110
- }
1111
- }
1112
- if (violations.length === 0) {
1113
- resetViolationStats();
1114
- console.log(chalk.green(" \u2713 No rule violations \u2014 commit allowed.\n"));
1115
- return;
1116
- }
1117
- console.log(
1118
- chalk.red.bold(
1119
- `
1120
- \u2717 ${violations.length} rule violation${violations.length > 1 ? "s" : ""} found \u2014 commit blocked
1121
- `
1122
- )
1123
- );
1124
- let ruleStatsSnapshot = {};
1125
- {
1126
- const statsPath = join2(process.cwd(), ".memory-core-stats.json");
1127
- if (existsSync2(statsPath)) {
1128
- try {
1129
- const parsed = JSON.parse(readFileSync2(statsPath, "utf-8"));
1130
- ruleStatsSnapshot = parsed.rules ?? {};
1131
- } catch {
1132
- }
1133
- }
1134
- }
1135
- const MAX_LOCATIONS = 5;
1136
- const groups = groupViolationsByRule(violations);
1137
- let groupIndex = 0;
1138
- for (const [rule, group] of groups) {
1139
- groupIndex++;
1140
- const isCluster = group.length > 1;
1141
- const first = group[0];
1142
- if (isCluster) {
1143
- console.log(chalk.bold.red(`
1144
- [${groupIndex}] ${rule}`) + chalk.dim(` \xD7${group.length}`));
1145
- } else {
1146
- const loc = first.file ? first.line ? `${first.file}:${first.line}` : first.file : "unknown location";
1147
- console.log(chalk.bold(`
1148
- [${groupIndex}] ${loc}`));
1149
- console.log(chalk.yellow(" Rule: ") + rule);
1150
- }
1151
- const why = first.reason ?? reasonMap.get(rule);
1152
- if (why) console.log(chalk.dim(" Why: ") + chalk.dim(why));
1153
- if (first.suggestion) console.log(chalk.green(" Fix: ") + first.suggestion);
1154
- if (isCluster) {
1155
- console.log();
1156
- const shown = group.slice(0, MAX_LOCATIONS);
1157
- const overflow = group.length - MAX_LOCATIONS;
1158
- for (const v of shown) {
1159
- const loc = v.file ? v.line ? `${v.file}:${v.line}` : v.file : "unknown location";
1160
- const issue = v.issue ? chalk.dim(` ${v.issue}`) : "";
1161
- console.log(chalk.dim(` ${loc}`) + issue);
1162
- }
1163
- if (overflow > 0) {
1164
- console.log(chalk.dim(` ... and ${overflow} more`));
1165
- }
1166
- } else {
1167
- if (first.issue) console.log(chalk.red(" Issue: ") + first.issue);
1168
- }
1169
- const ruleEntry = toRuleStatEntry(ruleStatsSnapshot[rule]);
1170
- if (ruleEntry.count > 5 && ruleEntry.falsePositives > 0) {
1171
- const rate = Math.round(ruleEntry.falsePositives / ruleEntry.count * 100);
1172
- if (rate > 40) {
1173
- console.log(chalk.yellow(`
1174
- Noisy: ${rate}% historical false-positive rate`));
1175
- console.log(chalk.dim(` Silence: memory-core allow "${rule}"`));
1176
- console.log(chalk.dim(` Review all: memory-core tune`));
1177
- } else if (rate > 25) {
1178
- console.log(chalk.dim(`
1179
- Note: ${rate}% false-positive rate \u2014 run: memory-core tune`));
1180
- }
1181
- }
1182
- console.log();
1183
- }
1184
- console.log(chalk.dim(" Fix the violations above, then commit again."));
1185
- console.log(chalk.dim(" To bypass (not recommended): git commit --no-verify"));
1186
- console.log(chalk.dim(" Env bypass: MEMORY_CORE_SKIP_HOOK=1 git commit"));
1187
- console.log(chalk.dim(' To save as memory: memory-core remember "<lesson>"'));
1188
- console.log();
1189
- recordViolations(violations);
1190
- await promptToSaveViolations(violations);
1191
- process.exit(1);
1192
- }
1193
- function extractForbiddenPhrases(content) {
1194
- const phrases = [];
1195
- const normalized = content.replace(/\s+/g, " ");
1196
- const patterns = [
1197
- /\bnever\s+([^.;]+)/gi,
1198
- /\bmust not\s+([^.;]+)/gi,
1199
- /\bdo not\s+([^.;]+)/gi
1200
- ];
1201
- for (const pattern of patterns) {
1202
- for (const match of normalized.matchAll(pattern)) {
1203
- const phrase = match[1]?.trim();
1204
- if (phrase && phrase.split(/\s+/).length >= 2) phrases.push(phrase.toLowerCase());
1205
- }
1206
- }
1207
- return phrases;
1208
- }
1209
- function getCiDiff() {
1210
- const baseRef = process.env.GITHUB_BASE_REF;
1211
- const commands = [
1212
- baseRef ? `git diff --unified=0 --diff-filter=ACMRT origin/${baseRef}...HEAD` : "",
1213
- "git diff --unified=0 --diff-filter=ACMRT HEAD~1 HEAD",
1214
- "git diff --cached --unified=0 --diff-filter=ACMRT"
1215
- ].filter(Boolean);
1216
- for (const command of commands) {
1217
- try {
1218
- const diff = execSync(command, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
1219
- if (diff.trim()) return diff;
1220
- } catch {
1221
- }
1222
- }
1223
- return "";
1224
- }
1225
- async function checkCi(options = {}) {
1226
- let memories;
1227
- try {
1228
- memories = readMemoryFile();
1229
- } catch (err) {
1230
- console.error(chalk.red(`
1231
- CI check failed: ${err.message}
1232
- `));
1233
- process.exit(1);
1234
- }
1235
- const rules = memories.filter((memory) => memory.type !== "ignore");
1236
- const ignores = memories.filter((memory) => memory.type === "ignore").map((memory) => memory.content.toLowerCase());
1237
- const phrases = rules.flatMap(
1238
- (memory) => extractForbiddenPhrases(memory.content).map((phrase) => ({ rule: memory.content, phrase }))
1239
- );
1240
- const diff = getCiDiff();
1241
- const addedLines = diff.split("\n").filter((line) => line.startsWith("+") && !line.startsWith("+++")).map((line) => line.slice(1));
1242
- if (options.debug) {
1243
- console.log(chalk.gray(`
1244
- [debug] memories: ${memories.length}`));
1245
- console.log(chalk.gray(` [debug] text rules: ${phrases.length}`));
1246
- console.log(chalk.gray(` [debug] diff length: ${diff.length} chars
1247
- `));
1248
- }
1249
- const violations = [];
1250
- for (const line of addedLines) {
1251
- const normalizedLine = line.toLowerCase();
1252
- if (ignores.some((ignore) => normalizedLine.includes(ignore))) continue;
1253
- for (const { rule, phrase } of phrases) {
1254
- if (normalizedLine.includes(phrase)) {
1255
- violations.push({
1256
- rule,
1257
- file: "diff",
1258
- issue: `Added line contains forbidden phrase: "${phrase}"`
1259
- });
1260
- }
1261
- }
1262
- }
1263
- if (violations.length === 0) {
1264
- console.log(chalk.green(`
1265
- \u2713 CI memory check passed (${rules.length} rules loaded from memories.json)
1266
- `));
1267
- return;
1268
- }
1269
- console.log(chalk.red.bold(`
1270
- \u2717 ${violations.length} CI violation${violations.length > 1 ? "s" : ""} found
1271
- `));
1272
- violations.forEach((violation, index) => {
1273
- console.log(chalk.bold(` [${index + 1}] ${violation.file}`));
1274
- console.log(chalk.yellow(" Rule: ") + violation.rule);
1275
- console.log(chalk.red(" Issue: ") + violation.issue);
1276
- console.log();
1277
- });
1278
- recordViolations(violations, "ci");
1279
- process.exit(1);
1280
- }
1281
- function printModelMissing(model2) {
1282
- console.log(chalk.yellow(`
1283
- \u26A0 Chat model "${model2}" not found in Ollama.`));
1284
- console.log(chalk.gray(` Pull a model: ollama pull ${model2}`));
1285
- console.log(chalk.gray(" Or set OLLAMA_CHAT_MODEL=<model> in .env"));
1286
- console.log(chalk.gray(" Recommended: llama3.2 | qwen2.5-coder:3b | mistral\n"));
1287
- }
1288
-
1289
44
  // src/remote-install.ts
1290
- import { spawnSync as spawnSync2 } from "child_process";
45
+ import { spawnSync } from "child_process";
1291
46
  var CAVEMAN_INSTALL_URL = "https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh";
1292
47
  var MAX_INSTALLER_BYTES = 2e5;
1293
48
  var TRUSTED_INSTALL_HOSTS = /* @__PURE__ */ new Set(["raw.githubusercontent.com"]);
@@ -1301,7 +56,7 @@ function assertTrustedInstallerUrl(url) {
1301
56
  }
1302
57
  }
1303
58
  function defaultRunScript(script) {
1304
- return spawnSync2("bash", ["-s"], {
59
+ return spawnSync("bash", ["-s"], {
1305
60
  input: script,
1306
61
  encoding: "utf-8",
1307
62
  stdio: ["pipe", "pipe", "pipe"]
@@ -1337,38 +92,38 @@ async function installCavemanTokenSaver(options = {}) {
1337
92
 
1338
93
  // src/cli.ts
1339
94
  function printBanner(projectName, agentCount, status) {
1340
- const pg = status ? status.postgresOk ? chalk2.green(" \u2713 PostgreSQL ") + chalk2.bold("connected") : chalk2.red(" \u2717 PostgreSQL ") + chalk2.bold("not connected \u2014 check DATABASE_URL") : chalk2.green(" \u2713 Memory ") + chalk2.bold("PostgreSQL + pgvector ready");
1341
- const ol = status ? status.ollamaOk ? chalk2.green(" \u2713 Ollama ") + chalk2.bold(`connected (model: ${status.chatModel})`) : chalk2.red(" \u2717 Ollama ") + chalk2.bold("not running \u2014 start with: ollama serve") : null;
95
+ const pg = status ? status.postgresOk ? chalk.green(" \u2713 PostgreSQL ") + chalk.bold("connected") : chalk.red(" \u2717 PostgreSQL ") + chalk.bold("not connected \u2014 check DATABASE_URL") : chalk.green(" \u2713 Memory ") + chalk.bold("PostgreSQL + pgvector ready");
96
+ const ol = status ? status.ollamaOk ? chalk.green(" \u2713 Ollama ") + chalk.bold(`connected (model: ${status.chatModel})`) : chalk.red(" \u2717 Ollama ") + chalk.bold("not running \u2014 start with: ollama serve") : null;
1342
97
  const lines = [
1343
98
  "",
1344
- chalk2.cyan(" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 "),
1345
- chalk2.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557"),
1346
- chalk2.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551"),
1347
- chalk2.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551"),
1348
- chalk2.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D"),
1349
- chalk2.cyan(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D"),
99
+ chalk.cyan(" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 "),
100
+ chalk.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557"),
101
+ chalk.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551"),
102
+ chalk.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551"),
103
+ chalk.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D"),
104
+ chalk.cyan(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D"),
1350
105
  "",
1351
- chalk2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 ") + chalk2.bold.white("C O R E") + chalk2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
106
+ 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 ") + chalk.bold.white("C O R E") + 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"),
1352
107
  "",
1353
- chalk2.green(` \u2713 Project `) + chalk2.bold(projectName),
1354
- chalk2.green(` \u2713 Agents `) + chalk2.bold(`${agentCount} AI agents configured`),
108
+ chalk.green(` \u2713 Project `) + chalk.bold(projectName),
109
+ chalk.green(` \u2713 Agents `) + chalk.bold(`${agentCount} AI agents configured`),
1355
110
  pg,
1356
111
  ...ol ? [ol] : [],
1357
112
  "",
1358
- chalk2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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"),
1359
- chalk2.dim(" Built by ") + chalk2.bold.white("Shahmil Saari"),
113
+ 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\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"),
114
+ chalk.dim(" Built by ") + chalk.bold.white("Shahmil Saari"),
1360
115
  "",
1361
- chalk2.bold(" Every AI agent in this project now follows your rules."),
116
+ chalk.bold(" Every AI agent in this project now follows your rules."),
1362
117
  "",
1363
- chalk2.gray(" Next steps:"),
1364
- chalk2.gray(' memory-core remember "Your architectural decision"'),
1365
- chalk2.gray(' memory-core search "query"'),
1366
- chalk2.gray(" memory-core sync"),
118
+ chalk.gray(" Next steps:"),
119
+ chalk.gray(' memory-core remember "Your architectural decision"'),
120
+ chalk.gray(' memory-core search "query"'),
121
+ chalk.gray(" memory-core sync"),
1367
122
  ""
1368
123
  ];
1369
124
  lines.forEach((l) => console.log(l));
1370
125
  }
1371
- var { version } = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf-8"));
126
+ var { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
1372
127
  var CONFIG_FILE = ".memory-core.json";
1373
128
  var LOCAL_GENERATED_FILES = [".memory-core-stats.json"];
1374
129
  var LOCAL_STATE_FILES = [CONFIG_FILE, ".memory-core.env", ...LOCAL_GENERATED_FILES];
@@ -1379,13 +134,13 @@ var DEFAULT_EMBEDDING_MODEL = "nomic-embed-text";
1379
134
  var DEFAULT_CHAT_MODEL = "llama3.2";
1380
135
  var phase1 = getDefaultApplicationContainer();
1381
136
  function getEnvPath() {
1382
- const memoryEnv = join3(process.cwd(), ".memory-core.env");
1383
- if (existsSync3(memoryEnv)) return memoryEnv;
1384
- const dotEnv = join3(process.cwd(), ".env");
1385
- return existsSync3(dotEnv) ? dotEnv : memoryEnv;
137
+ const memoryEnv = join(process.cwd(), ".memory-core.env");
138
+ if (existsSync(memoryEnv)) return memoryEnv;
139
+ const dotEnv = join(process.cwd(), ".env");
140
+ return existsSync(dotEnv) ? dotEnv : memoryEnv;
1386
141
  }
1387
142
  function getWriteEnvPath() {
1388
- return join3(process.cwd(), ".memory-core.env");
143
+ return join(process.cwd(), ".memory-core.env");
1389
144
  }
1390
145
  function parseEnvFile(raw) {
1391
146
  const lines = raw.split(/\r?\n/);
@@ -1403,7 +158,7 @@ function parseEnvFile(raw) {
1403
158
  }
1404
159
  function readRuntimeEnv() {
1405
160
  const envPath = getEnvPath();
1406
- const fileValues = existsSync3(envPath) ? parseEnvFile(readFileSync3(envPath, "utf-8")) : {};
161
+ const fileValues = existsSync(envPath) ? parseEnvFile(readFileSync(envPath, "utf-8")) : {};
1407
162
  const values = {
1408
163
  ...fileValues
1409
164
  };
@@ -1437,7 +192,7 @@ function writeRuntimeEnv(values, envPath = getWriteEnvPath()) {
1437
192
  const value = values[key];
1438
193
  if (value) lines.push(`${key}=${value}`);
1439
194
  }
1440
- writeFileSync3(envPath, `${lines.join("\n")}
195
+ writeFileSync(envPath, `${lines.join("\n")}
1441
196
  `, "utf-8");
1442
197
  }
1443
198
  function applyRuntimeEnv(values) {
@@ -1446,8 +201,8 @@ function applyRuntimeEnv(values) {
1446
201
  }
1447
202
  }
1448
203
  function appendMissingGitignoreEntries(entries, heading) {
1449
- const gitignorePath = join3(process.cwd(), ".gitignore");
1450
- const existing = existsSync3(gitignorePath) ? readFileSync3(gitignorePath, "utf-8") : "";
204
+ const gitignorePath = join(process.cwd(), ".gitignore");
205
+ const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : "";
1451
206
  const existingEntries = new Set(
1452
207
  existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
1453
208
  );
@@ -1462,9 +217,9 @@ ${toAdd.join("\n")}
1462
217
  return toAdd.length;
1463
218
  }
1464
219
  function removeMemoryCoreGitignoreBlock(entries, heading = GITIGNORE_HEADING) {
1465
- const gitignorePath = join3(process.cwd(), ".gitignore");
1466
- if (!existsSync3(gitignorePath)) return false;
1467
- const existing = readFileSync3(gitignorePath, "utf-8");
220
+ const gitignorePath = join(process.cwd(), ".gitignore");
221
+ if (!existsSync(gitignorePath)) return false;
222
+ const existing = readFileSync(gitignorePath, "utf-8");
1468
223
  const entrySet = new Set(entries);
1469
224
  const lines = existing.split(/\r?\n/);
1470
225
  const kept = [];
@@ -1493,15 +248,15 @@ function removeMemoryCoreGitignoreBlock(entries, heading = GITIGNORE_HEADING) {
1493
248
  }
1494
249
  if (!changed) return false;
1495
250
  const content = kept.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\s+$/u, "");
1496
- writeFileSync3(gitignorePath, content ? `${content}
251
+ writeFileSync(gitignorePath, content ? `${content}
1497
252
  ` : "", "utf-8");
1498
253
  return true;
1499
254
  }
1500
255
  function removeProjectFiles(relativePaths) {
1501
256
  const removed = [];
1502
257
  for (const relativePath of [...new Set(relativePaths)]) {
1503
- const target = join3(process.cwd(), relativePath);
1504
- if (!existsSync3(target)) continue;
258
+ const target = join(process.cwd(), relativePath);
259
+ if (!existsSync(target)) continue;
1505
260
  rmSync(target, { force: true, recursive: true });
1506
261
  removed.push(relativePath);
1507
262
  }
@@ -1568,16 +323,16 @@ async function verifyOllamaConnection(ollamaUrl) {
1568
323
  }
1569
324
  }
1570
325
  function readProjectConfig() {
1571
- const path = join3(process.cwd(), CONFIG_FILE);
1572
- if (!existsSync3(path)) return null;
326
+ const path = join(process.cwd(), CONFIG_FILE);
327
+ if (!existsSync(path)) return null;
1573
328
  try {
1574
- return JSON.parse(readFileSync3(path, "utf-8"));
329
+ return JSON.parse(readFileSync(path, "utf-8"));
1575
330
  } catch {
1576
331
  return null;
1577
332
  }
1578
333
  }
1579
334
  function writeProjectConfig(config) {
1580
- writeFileSync3(join3(process.cwd(), CONFIG_FILE), JSON.stringify(config, null, 2));
335
+ writeFileSync(join(process.cwd(), CONFIG_FILE), JSON.stringify(config, null, 2));
1581
336
  }
1582
337
  function updateProjectConfig(mutator) {
1583
338
  const current = readProjectConfig() ?? {
@@ -1649,11 +404,11 @@ function toPortableFromRecord(memory) {
1649
404
  });
1650
405
  }
1651
406
  function printMemoryTable(memories, title = "Rules in memory") {
1652
- console.log(chalk2.bold(`
407
+ console.log(chalk.bold(`
1653
408
  ${title} (${memories.length} total)
1654
409
  `));
1655
- console.log(chalk2.dim(" ID Type Scope Title / Content"));
1656
- console.log(chalk2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
410
+ console.log(chalk.dim(" ID Type Scope Title / Content"));
411
+ 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1657
412
  memories.forEach((memory) => {
1658
413
  const id = String(memory.id).padEnd(4);
1659
414
  const type = memory.type.padEnd(10);
@@ -1661,19 +416,19 @@ function printMemoryTable(memories, title = "Rules in memory") {
1661
416
  const label = truncate(memory.title || memory.content, 64);
1662
417
  console.log(` ${id} ${type} ${scope} ${label}`);
1663
418
  });
1664
- console.log(chalk2.gray("\n Use: memory-core remove <id> | memory-core edit <id>\n"));
419
+ console.log(chalk.gray("\n Use: memory-core remove <id> | memory-core edit <id>\n"));
1665
420
  }
1666
421
  function getCurrentListArchitectures(config) {
1667
422
  return inferProjectArchitectures(process.cwd(), config).filter((architecture) => architecture !== "global");
1668
423
  }
1669
424
  function printStatusLine(label, value) {
1670
- console.log(` ${chalk2.dim(label.padEnd(18))} ${value}`);
425
+ console.log(` ${chalk.dim(label.padEnd(18))} ${value}`);
1671
426
  }
1672
427
  function abbreviate(value, max = 96) {
1673
428
  return value.length > max ? `${value.slice(0, max - 1)}\u2026` : value;
1674
429
  }
1675
430
  function graphStoreFilePath(cwd = process.cwd()) {
1676
- return join3(cwd, ".memory-core", "graph-snapshots.json");
431
+ return join(cwd, ".memory-core", "graph-snapshots.json");
1677
432
  }
1678
433
  function graphBackendLabel(values) {
1679
434
  return values.DATABASE_URL ? "postgres (file fallback enabled)" : "file";
@@ -1693,8 +448,8 @@ async function runModelDoctor() {
1693
448
  const ollamaUrl = values.OLLAMA_URL ?? DEFAULT_OLLAMA_URL;
1694
449
  const embeddingModel = values.OLLAMA_MODEL ?? DEFAULT_EMBEDDING_MODEL;
1695
450
  const dbUrl = values.DATABASE_URL ?? "";
1696
- console.log(chalk2.bold("\n memory-core model doctor\n"));
1697
- printStatusLine("Env file", existsSync3(envPath) ? envPath : `${envPath} ${chalk2.yellow("(will be created on first write)")}`);
451
+ console.log(chalk.bold("\n memory-core model doctor\n"));
452
+ printStatusLine("Env file", existsSync(envPath) ? envPath : `${envPath} ${chalk.yellow("(will be created on first write)")}`);
1698
453
  printStatusLine("Provider", provider2);
1699
454
  printStatusLine("Chat model", model2);
1700
455
  printStatusLine("Embedding model", embeddingModel);
@@ -1704,56 +459,56 @@ async function runModelDoctor() {
1704
459
  const dbError = await verifyDatabaseConnection(dbUrl);
1705
460
  if (dbError) {
1706
461
  ok = false;
1707
- console.log(chalk2.red(" \u2717 PostgreSQL ") + chalk2.dim(dbError));
462
+ console.log(chalk.red(" \u2717 PostgreSQL ") + chalk.dim(dbError));
1708
463
  } else {
1709
- console.log(chalk2.green(" \u2713 PostgreSQL ") + chalk2.dim("connected"));
464
+ console.log(chalk.green(" \u2713 PostgreSQL ") + chalk.dim("connected"));
1710
465
  }
1711
466
  const ollamaError = await verifyOllamaConnection(ollamaUrl);
1712
467
  if (ollamaError) {
1713
468
  ok = false;
1714
- console.log(chalk2.red(" \u2717 Ollama ") + chalk2.dim(ollamaError));
469
+ console.log(chalk.red(" \u2717 Ollama ") + chalk.dim(ollamaError));
1715
470
  } else {
1716
- console.log(chalk2.green(" \u2713 Ollama ") + chalk2.dim("reachable"));
471
+ console.log(chalk.green(" \u2713 Ollama ") + chalk.dim("reachable"));
1717
472
  }
1718
473
  if (!ollamaError) {
1719
474
  try {
1720
475
  const installedEmbeddingModel = await resolveOllamaInstalledModel(ollamaUrl, embeddingModel);
1721
476
  if (installedEmbeddingModel) {
1722
- console.log(chalk2.green(" \u2713 Embedding ") + chalk2.dim(`${installedEmbeddingModel} installed`));
477
+ console.log(chalk.green(" \u2713 Embedding ") + chalk.dim(`${installedEmbeddingModel} installed`));
1723
478
  } else {
1724
479
  ok = false;
1725
- console.log(chalk2.red(" \u2717 Embedding ") + chalk2.dim(`${embeddingModel} not installed in Ollama`));
480
+ console.log(chalk.red(" \u2717 Embedding ") + chalk.dim(`${embeddingModel} not installed in Ollama`));
1726
481
  }
1727
482
  } catch (err) {
1728
483
  ok = false;
1729
- console.log(chalk2.red(" \u2717 Embedding ") + chalk2.dim(err.message));
484
+ console.log(chalk.red(" \u2717 Embedding ") + chalk.dim(err.message));
1730
485
  }
1731
486
  }
1732
487
  if (provider2 === "ollama") {
1733
488
  if (ollamaError) {
1734
489
  ok = false;
1735
- console.log(chalk2.red(" \u2717 Chat model ") + chalk2.dim("cannot verify while Ollama is unreachable"));
490
+ console.log(chalk.red(" \u2717 Chat model ") + chalk.dim("cannot verify while Ollama is unreachable"));
1736
491
  } else {
1737
492
  try {
1738
493
  const installedChatModel = await resolveOllamaInstalledModel(ollamaUrl, model2);
1739
494
  if (installedChatModel) {
1740
- console.log(chalk2.green(" \u2713 Chat model ") + chalk2.dim(`${installedChatModel} installed`));
495
+ console.log(chalk.green(" \u2713 Chat model ") + chalk.dim(`${installedChatModel} installed`));
1741
496
  } else {
1742
497
  ok = false;
1743
- console.log(chalk2.red(" \u2717 Chat model ") + chalk2.dim(`${model2} not installed in Ollama`));
498
+ console.log(chalk.red(" \u2717 Chat model ") + chalk.dim(`${model2} not installed in Ollama`));
1744
499
  }
1745
500
  } catch (err) {
1746
501
  ok = false;
1747
- console.log(chalk2.red(" \u2717 Chat model ") + chalk2.dim(err.message));
502
+ console.log(chalk.red(" \u2717 Chat model ") + chalk.dim(err.message));
1748
503
  }
1749
504
  }
1750
505
  } else {
1751
506
  if (!values.CHAT_API_KEY) {
1752
507
  ok = false;
1753
- console.log(chalk2.red(` \u2717 ${providerLabel(provider2)} API`) + chalk2.dim(" CHAT_API_KEY is missing"));
508
+ console.log(chalk.red(` \u2717 ${providerLabel(provider2)} API`) + chalk.dim(" CHAT_API_KEY is missing"));
1754
509
  } else {
1755
- console.log(chalk2.green(` \u2713 ${providerLabel(provider2)} API`) + chalk2.dim(" key configured"));
1756
- console.log(chalk2.gray(" Remote provider connectivity is not verified live by doctor."));
510
+ console.log(chalk.green(` \u2713 ${providerLabel(provider2)} API`) + chalk.dim(" key configured"));
511
+ console.log(chalk.gray(" Remote provider connectivity is not verified live by doctor."));
1757
512
  }
1758
513
  }
1759
514
  console.log();
@@ -1765,33 +520,33 @@ async function printProjectStatus() {
1765
520
  const provider2 = getConfiguredProvider(values);
1766
521
  const model2 = getConfiguredChatModel(values);
1767
522
  const architectures = inferProjectArchitectures(process.cwd(), config);
1768
- const generatedFiles = OUTPUT_FILES.map((entry) => entry.path).filter((relativePath) => existsSync3(join3(process.cwd(), relativePath)));
1769
- const hookPath = join3(process.cwd(), ".git", "hooks", "pre-commit");
1770
- const memoryFilePath = join3(process.cwd(), MEMORY_FILE);
1771
- const statsPath = join3(process.cwd(), ".memory-core-stats.json");
523
+ const generatedFiles = OUTPUT_FILES.map((entry) => entry.path).filter((relativePath) => existsSync(join(process.cwd(), relativePath)));
524
+ const hookPath = join(process.cwd(), ".git", "hooks", "pre-commit");
525
+ const memoryFilePath = join(process.cwd(), MEMORY_FILE);
526
+ const statsPath = join(process.cwd(), ".memory-core-stats.json");
1772
527
  const dbError = await verifyDatabaseConnection(values.DATABASE_URL ?? "");
1773
528
  const ollamaError = await verifyOllamaConnection(values.OLLAMA_URL ?? DEFAULT_OLLAMA_URL);
1774
529
  const graphCount = await getGraphSnapshotCount(process.cwd());
1775
- console.log(chalk2.bold("\n memory-core status\n"));
530
+ console.log(chalk.bold("\n memory-core status\n"));
1776
531
  printStatusLine("Project", config?.projectName ?? process.cwd().split("/").pop() ?? "unknown");
1777
- printStatusLine("Project type", config?.projectType ?? chalk2.yellow("not initialized"));
532
+ printStatusLine("Project type", config?.projectType ?? chalk.yellow("not initialized"));
1778
533
  printStatusLine("Language", config?.language ?? detectProject().language);
1779
- printStatusLine("Backend arch", config?.backendArchitecture ?? chalk2.gray("\u2014"));
1780
- printStatusLine("Frontend fw", config?.frontendFramework ?? chalk2.gray("\u2014"));
1781
- printStatusLine("Architectures", architectures.length ? architectures.join(", ") : chalk2.gray("none detected"));
1782
- printStatusLine("Agents", config?.agents?.length ? `${config.agents.length} selected` : chalk2.gray("none saved"));
534
+ printStatusLine("Backend arch", config?.backendArchitecture ?? chalk.gray("\u2014"));
535
+ printStatusLine("Frontend fw", config?.frontendFramework ?? chalk.gray("\u2014"));
536
+ printStatusLine("Architectures", architectures.length ? architectures.join(", ") : chalk.gray("none detected"));
537
+ printStatusLine("Agents", config?.agents?.length ? `${config.agents.length} selected` : chalk.gray("none saved"));
1783
538
  printStatusLine("Caveman", config?.caveman?.enabled ? `enabled (${config.caveman.intensity})` : "disabled");
1784
539
  printStatusLine("Auto sync", config?.autoSync === false ? "disabled" : "enabled");
1785
540
  printStatusLine("Allow patterns", String(getAllowPatterns(config).length));
1786
- printStatusLine("Env file", `${existsSync3(envPath) ? "present" : "missing"} (${envPath.split("/").pop()})`);
1787
- printStatusLine("Memory file", existsSync3(memoryFilePath) ? MEMORY_FILE : chalk2.gray("not exported"));
1788
- printStatusLine("Project config", existsSync3(join3(process.cwd(), CONFIG_FILE)) ? CONFIG_FILE : chalk2.gray("missing"));
541
+ printStatusLine("Env file", `${existsSync(envPath) ? "present" : "missing"} (${envPath.split("/").pop()})`);
542
+ printStatusLine("Memory file", existsSync(memoryFilePath) ? MEMORY_FILE : chalk.gray("not exported"));
543
+ printStatusLine("Project config", existsSync(join(process.cwd(), CONFIG_FILE)) ? CONFIG_FILE : chalk.gray("missing"));
1789
544
  printStatusLine("Generated files", String(generatedFiles.length));
1790
- printStatusLine("Hook", existsSync3(hookPath) ? "installed" : "not installed");
1791
- printStatusLine("Stats file", existsSync3(statsPath) ? ".memory-core-stats.json" : chalk2.gray("none"));
545
+ printStatusLine("Hook", existsSync(hookPath) ? "installed" : "not installed");
546
+ printStatusLine("Stats file", existsSync(statsPath) ? ".memory-core-stats.json" : chalk.gray("none"));
1792
547
  printStatusLine("Graph backend", graphBackendLabel(values));
1793
548
  printStatusLine("Graph store", graphStoreFilePath(process.cwd()));
1794
- printStatusLine("Graph snapshots", graphCount === null ? chalk2.gray("unavailable") : String(graphCount));
549
+ printStatusLine("Graph snapshots", graphCount === null ? chalk.gray("unavailable") : String(graphCount));
1795
550
  console.log();
1796
551
  printStatusLine("Database URL", redactDatabaseUrl(values.DATABASE_URL ?? ""));
1797
552
  printStatusLine("Ollama URL", values.OLLAMA_URL ?? DEFAULT_OLLAMA_URL);
@@ -1800,38 +555,38 @@ async function printProjectStatus() {
1800
555
  printStatusLine("Chat model", model2);
1801
556
  console.log();
1802
557
  console.log(
1803
- dbError ? chalk2.red(" \u2717 PostgreSQL ") + chalk2.dim(dbError) : chalk2.green(" \u2713 PostgreSQL ") + chalk2.dim("connected")
558
+ dbError ? chalk.red(" \u2717 PostgreSQL ") + chalk.dim(dbError) : chalk.green(" \u2713 PostgreSQL ") + chalk.dim("connected")
1804
559
  );
1805
560
  console.log(
1806
- ollamaError ? chalk2.red(" \u2717 Ollama ") + chalk2.dim(ollamaError) : chalk2.green(" \u2713 Ollama ") + chalk2.dim("reachable")
561
+ ollamaError ? chalk.red(" \u2717 Ollama ") + chalk.dim(ollamaError) : chalk.green(" \u2713 Ollama ") + chalk.dim("reachable")
1807
562
  );
1808
563
  if (provider2 !== "ollama") {
1809
564
  console.log(
1810
- values.CHAT_API_KEY ? chalk2.green(` \u2713 ${providerLabel(provider2)} API`) + chalk2.dim(" key configured") : chalk2.red(` \u2717 ${providerLabel(provider2)} API`) + chalk2.dim(" CHAT_API_KEY is missing")
565
+ values.CHAT_API_KEY ? chalk.green(` \u2713 ${providerLabel(provider2)} API`) + chalk.dim(" key configured") : chalk.red(` \u2717 ${providerLabel(provider2)} API`) + chalk.dim(" CHAT_API_KEY is missing")
1811
566
  );
1812
567
  }
1813
568
  console.log();
1814
569
  }
1815
570
  function printMemorySelection(selection, limit = 4) {
1816
571
  const active = selection.activeArchitectures.join(", ") || "none detected";
1817
- console.log(chalk2.gray(` Stack filter: ${active}`));
572
+ console.log(chalk.gray(` Stack filter: ${active}`));
1818
573
  const included = selection.decisions.filter((decision) => decision.status === "included");
1819
574
  if (included.length > 0) {
1820
- console.log(chalk2.gray(` Included ${included.length}:`));
575
+ console.log(chalk.gray(` Included ${included.length}:`));
1821
576
  for (const decision of included.slice(0, limit)) {
1822
- console.log(chalk2.gray(` + ${decision.memory.content} (${decision.reason})`));
577
+ console.log(chalk.gray(` + ${decision.memory.content} (${decision.reason})`));
1823
578
  }
1824
579
  if (included.length > limit) {
1825
- console.log(chalk2.gray(` \u2026 ${included.length - limit} more included`));
580
+ console.log(chalk.gray(` \u2026 ${included.length - limit} more included`));
1826
581
  }
1827
582
  }
1828
583
  if (selection.excluded.length > 0) {
1829
- console.log(chalk2.gray(` Excluded ${selection.excluded.length}:`));
584
+ console.log(chalk.gray(` Excluded ${selection.excluded.length}:`));
1830
585
  for (const decision of selection.excluded.slice(0, limit)) {
1831
- console.log(chalk2.gray(` - ${decision.memory.content} (${decision.reason})`));
586
+ console.log(chalk.gray(` - ${decision.memory.content} (${decision.reason})`));
1832
587
  }
1833
588
  if (selection.excluded.length > limit) {
1834
- console.log(chalk2.gray(` \u2026 ${selection.excluded.length - limit} more excluded`));
589
+ console.log(chalk.gray(` \u2026 ${selection.excluded.length - limit} more excluded`));
1835
590
  }
1836
591
  }
1837
592
  }
@@ -1873,22 +628,22 @@ async function syncGeneratedFiles(config, agents, options = {}) {
1873
628
  agents
1874
629
  );
1875
630
  spinner.succeed(
1876
- `Synced \u2014 ${chalk2.green(`${result.written.length} updated`)}, ${chalk2.dim(`${result.skipped.length} already up to date`)}`
631
+ `Synced \u2014 ${chalk.green(`${result.written.length} updated`)}, ${chalk.dim(`${result.skipped.length} already up to date`)}`
1877
632
  );
1878
633
  if (result.written.length > 0) {
1879
- result.written.forEach((file) => console.log(chalk2.gray(` \u2713 ${file}`)));
634
+ result.written.forEach((file) => console.log(chalk.gray(` \u2713 ${file}`)));
1880
635
  }
1881
636
  }
1882
637
  async function autoSyncGeneratedFiles(config, action, enabled = true) {
1883
638
  if (!enabled) {
1884
- console.log(chalk2.gray(" Auto-sync skipped (--no-sync). Run memory-core sync when ready."));
639
+ console.log(chalk.gray(" Auto-sync skipped (--no-sync). Run memory-core sync when ready."));
1885
640
  return;
1886
641
  }
1887
642
  if (!config) {
1888
643
  return;
1889
644
  }
1890
645
  if (config.autoSync === false) {
1891
- console.log(chalk2.gray(" Auto-sync disabled for this project. Run memory-core sync when ready."));
646
+ console.log(chalk.gray(" Auto-sync disabled for this project. Run memory-core sync when ready."));
1892
647
  return;
1893
648
  }
1894
649
  try {
@@ -1896,24 +651,24 @@ async function autoSyncGeneratedFiles(config, action, enabled = true) {
1896
651
  label: `Auto-syncing agent files after ${action}\u2026`
1897
652
  });
1898
653
  } catch (err) {
1899
- console.log(chalk2.yellow(` Auto-sync skipped: ${err.message}`));
1900
- console.log(chalk2.gray(" Run memory-core sync manually when ready."));
654
+ console.log(chalk.yellow(` Auto-sync skipped: ${err.message}`));
655
+ console.log(chalk.gray(" Run memory-core sync manually when ready."));
1901
656
  }
1902
657
  }
1903
658
  var program = new Command();
1904
659
  program.name("memory-core").description("Universal AI memory core \u2014 generate AI context files for all coding agents").version(version);
1905
660
  program.command("init").description("Initialize memory-core in the current project").option("--quick", "Use smart defaults and skip optional prompts").action(async (opts) => {
1906
- console.log(chalk2.bold.cyan("\n memory-core init\n"));
661
+ console.log(chalk.bold.cyan("\n memory-core init\n"));
1907
662
  const detected = detectProject();
1908
663
  const quick = opts.quick ?? false;
1909
664
  let skipEnv = false;
1910
665
  let skipProject = false;
1911
- if (existsSync3(join3(process.cwd(), CONFIG_FILE)) && !quick) {
666
+ if (existsSync(join(process.cwd(), CONFIG_FILE)) && !quick) {
1912
667
  const existing = readProjectConfig();
1913
668
  const envVals = readRuntimeEnv().values;
1914
- console.log(chalk2.dim(` Already initialized: ${existing?.projectName ?? "?"} (${existing?.projectType ?? "?"})`));
1915
- console.log(chalk2.dim(` Provider: ${envVals.CHAT_PROVIDER ?? "ollama"} Model: ${envVals.CHAT_MODEL ?? "llama3.2"}`));
1916
- console.log(chalk2.dim(` Hook: ${existsSync3(join3(".git", "hooks", "pre-commit")) ? "installed" : "not installed"} Agents: ${existing?.agents?.length ?? 0}
669
+ console.log(chalk.dim(` Already initialized: ${existing?.projectName ?? "?"} (${existing?.projectType ?? "?"})`));
670
+ console.log(chalk.dim(` Provider: ${envVals.CHAT_PROVIDER ?? "ollama"} Model: ${envVals.CHAT_MODEL ?? "llama3.2"}`));
671
+ console.log(chalk.dim(` Hook: ${existsSync(join(".git", "hooks", "pre-commit")) ? "installed" : "not installed"} Agents: ${existing?.agents?.length ?? 0}
1917
672
  `));
1918
673
  const reinitChoice = await select({
1919
674
  message: "Already initialized \u2014 what do you want to do?",
@@ -1933,8 +688,8 @@ program.command("init").description("Initialize memory-core in the current proje
1933
688
  }
1934
689
  let pgOk = false;
1935
690
  let ollamaOk = false;
1936
- const envPath = join3(process.cwd(), ".memory-core.env");
1937
- const hasEnv = existsSync3(envPath);
691
+ const envPath = join(process.cwd(), ".memory-core.env");
692
+ const hasEnv = existsSync(envPath);
1938
693
  if (skipEnv) {
1939
694
  try {
1940
695
  const { Pool } = (await import("pg")).default;
@@ -1964,9 +719,9 @@ program.command("init").description("Initialize memory-core in the current proje
1964
719
  writeRuntimeEnv(envValues, envPath);
1965
720
  applyRuntimeEnv(envValues);
1966
721
  appendMissingGitignoreEntries(LOCAL_STATE_FILES, GITIGNORE_HEADING);
1967
- console.log(chalk2.green(" \u2713 .memory-core.env created with local defaults"));
722
+ console.log(chalk.green(" \u2713 .memory-core.env created with local defaults"));
1968
723
  } else if (!hasEnv) {
1969
- console.log(chalk2.dim(" No .memory-core.env found \u2014 let's set up your connection.\n"));
724
+ console.log(chalk.dim(" No .memory-core.env found \u2014 let's set up your connection.\n"));
1970
725
  const dbUser = process.env.USER ?? process.env.USERNAME ?? "postgres";
1971
726
  let dbUrl = "";
1972
727
  while (true) {
@@ -1980,12 +735,12 @@ program.command("init").description("Initialize memory-core in the current proje
1980
735
  const testPool = new Pool({ connectionString: dbUrl, connectionTimeoutMillis: 5e3 });
1981
736
  await testPool.query("SELECT 1");
1982
737
  await testPool.end();
1983
- pgSpinner.succeed(chalk2.green("PostgreSQL connected"));
738
+ pgSpinner.succeed(chalk.green("PostgreSQL connected"));
1984
739
  pgOk = true;
1985
740
  break;
1986
741
  } catch (err) {
1987
- pgSpinner.fail(chalk2.red(`Cannot connect: ${err.message}`));
1988
- console.log(chalk2.yellow(" Please check that PostgreSQL is running and the URL is correct.\n"));
742
+ pgSpinner.fail(chalk.red(`Cannot connect: ${err.message}`));
743
+ console.log(chalk.yellow(" Please check that PostgreSQL is running and the URL is correct.\n"));
1989
744
  }
1990
745
  }
1991
746
  let ollamaUrl = "";
@@ -1998,12 +753,12 @@ program.command("init").description("Initialize memory-core in the current proje
1998
753
  try {
1999
754
  const res = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(5e3) });
2000
755
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
2001
- ollamaSpinner.succeed(chalk2.green("Ollama connected"));
756
+ ollamaSpinner.succeed(chalk.green("Ollama connected"));
2002
757
  ollamaOk = true;
2003
758
  break;
2004
759
  } catch (err) {
2005
- ollamaSpinner.fail(chalk2.red(`Cannot reach Ollama: ${err.message}`));
2006
- console.log(chalk2.yellow(" Make sure Ollama is running: ollama serve\n"));
760
+ ollamaSpinner.fail(chalk.red(`Cannot reach Ollama: ${err.message}`));
761
+ console.log(chalk.yellow(" Make sure Ollama is running: ollama serve\n"));
2007
762
  }
2008
763
  }
2009
764
  const chatProvider = await select({
@@ -2017,7 +772,7 @@ program.command("init").description("Initialize memory-core in the current proje
2017
772
  ]
2018
773
  });
2019
774
  if (chatProvider !== "ollama") {
2020
- console.log(chalk2.dim(" Note: Ollama is still used for search embeddings. Code checking uses the cloud provider above."));
775
+ console.log(chalk.dim(" Note: Ollama is still used for search embeddings. Code checking uses the cloud provider above."));
2021
776
  }
2022
777
  let chatModel = "";
2023
778
  let chatApiKey = "";
@@ -2045,15 +800,15 @@ program.command("init").description("Initialize memory-core in the current proje
2045
800
  const match = exact ?? prefixed;
2046
801
  if (match) {
2047
802
  chatModel = match.name;
2048
- modelSpinner.succeed(chalk2.green(`${chatModel} is installed and ready`));
803
+ modelSpinner.succeed(chalk.green(`${chatModel} is installed and ready`));
2049
804
  break;
2050
805
  } else {
2051
- modelSpinner.fail(chalk2.red(`${chatModel} is not installed in your Ollama`));
2052
- console.log(chalk2.yellow(` Run: ollama pull ${chatModel} \u2014 or pick a different model.
806
+ modelSpinner.fail(chalk.red(`${chatModel} is not installed in your Ollama`));
807
+ console.log(chalk.yellow(` Run: ollama pull ${chatModel} \u2014 or pick a different model.
2053
808
  `));
2054
809
  }
2055
810
  } catch {
2056
- modelSpinner.warn(chalk2.yellow("Could not verify model \u2014 continuing anyway"));
811
+ modelSpinner.warn(chalk.yellow("Could not verify model \u2014 continuing anyway"));
2057
812
  break;
2058
813
  }
2059
814
  }
@@ -2097,7 +852,7 @@ program.command("init").description("Initialize memory-core in the current proje
2097
852
  chatApiKey = await input({
2098
853
  message: `${providerLabel(chatProvider)} API key?`
2099
854
  });
2100
- console.log(chalk2.green(` \u2713 ${chatProvider} / ${chatModel} configured`));
855
+ console.log(chalk.green(` \u2713 ${chatProvider} / ${chatModel} configured`));
2101
856
  }
2102
857
  const envValues = {
2103
858
  DATABASE_URL: dbUrl,
@@ -2112,8 +867,8 @@ program.command("init").description("Initialize memory-core in the current proje
2112
867
  writeRuntimeEnv(envValues, envPath);
2113
868
  applyRuntimeEnv(envValues);
2114
869
  appendMissingGitignoreEntries(LOCAL_STATE_FILES, GITIGNORE_HEADING);
2115
- console.log(chalk2.green("\n \u2713 .memory-core.env created"));
2116
- console.log(chalk2.gray(" Added to .gitignore \u2014 your DB credentials stay local.\n"));
870
+ console.log(chalk.green("\n \u2713 .memory-core.env created"));
871
+ console.log(chalk.gray(" Added to .gitignore \u2014 your DB credentials stay local.\n"));
2117
872
  } else {
2118
873
  try {
2119
874
  const { Pool } = (await import("pg")).default;
@@ -2150,10 +905,13 @@ program.command("init").description("Initialize memory-core in the current proje
2150
905
  const backendProfiles = listProfiles("backend");
2151
906
  backendArchitecture = await select({
2152
907
  message: "Backend architecture?",
2153
- choices: backendProfiles.map((p) => ({
2154
- value: p.name,
2155
- name: `${p.displayName} \u2014 ${p.description}`
2156
- }))
908
+ choices: [
909
+ ...backendProfiles.map((p) => ({
910
+ value: p.name,
911
+ name: `${p.displayName} \u2014 ${p.description}`
912
+ })),
913
+ { value: "custom", name: "Custom / Not defined yet \u2014 I'll add rules with memory-core remember" }
914
+ ]
2157
915
  });
2158
916
  }
2159
917
  }
@@ -2171,10 +929,13 @@ program.command("init").description("Initialize memory-core in the current proje
2171
929
  const frontendProfiles = listProfiles("frontend");
2172
930
  frontendFramework = await select({
2173
931
  message: "Frontend framework?",
2174
- choices: frontendProfiles.map((p) => ({
2175
- value: p.name,
2176
- name: `${p.displayName} \u2014 ${p.description}`
2177
- }))
932
+ choices: [
933
+ ...frontendProfiles.map((p) => ({
934
+ value: p.name,
935
+ name: `${p.displayName} \u2014 ${p.description}`
936
+ })),
937
+ { value: "custom", name: "Custom / Not defined yet \u2014 I'll add rules with memory-core remember" }
938
+ ]
2178
939
  });
2179
940
  }
2180
941
  }
@@ -2230,18 +991,18 @@ program.command("init").description("Initialize memory-core in the current proje
2230
991
  }
2231
992
  if (!quick) {
2232
993
  const envVals = readRuntimeEnv().values;
2233
- console.log(chalk2.bold("\n Ready to initialize\n"));
2234
- console.log(` Project ${chalk2.white(projectName)} (${projectType})`);
2235
- if (backendArchitecture) console.log(` Backend ${chalk2.white(backendArchitecture)}`);
2236
- if (frontendFramework) console.log(` Frontend ${chalk2.white(frontendFramework)}`);
2237
- console.log(` Language ${chalk2.white(language)}`);
2238
- console.log(` Provider ${chalk2.white(envVals.CHAT_PROVIDER ?? "ollama")} / ${chalk2.white(envVals.CHAT_MODEL ?? DEFAULT_CHAT_MODEL)}`);
2239
- console.log(` Agents ${chalk2.white(String(selectedAgents.length))} selected`);
2240
- console.log(` Hook ${chalk2.white(enableHook ? hookAdvisory ? "advisory" : "strict" : "skip")}`);
994
+ console.log(chalk.bold("\n Ready to initialize\n"));
995
+ console.log(` Project ${chalk.white(projectName)} (${projectType})`);
996
+ if (backendArchitecture) console.log(` Backend ${chalk.white(backendArchitecture === "custom" ? "Custom (no profile)" : backendArchitecture)}`);
997
+ if (frontendFramework) console.log(` Frontend ${chalk.white(frontendFramework === "custom" ? "Custom (no profile)" : frontendFramework)}`);
998
+ console.log(` Language ${chalk.white(language)}`);
999
+ console.log(` Provider ${chalk.white(envVals.CHAT_PROVIDER ?? "ollama")} / ${chalk.white(envVals.CHAT_MODEL ?? DEFAULT_CHAT_MODEL)}`);
1000
+ console.log(` Agents ${chalk.white(String(selectedAgents.length))} selected`);
1001
+ console.log(` Hook ${chalk.white(enableHook ? hookAdvisory ? "advisory" : "strict" : "skip")}`);
2241
1002
  console.log();
2242
1003
  const proceed = await confirm({ message: "Generate files?", default: true });
2243
1004
  if (!proceed) {
2244
- console.log(chalk2.yellow(" Cancelled.\n"));
1005
+ console.log(chalk.yellow(" Cancelled.\n"));
2245
1006
  await closePool();
2246
1007
  process.exit(0);
2247
1008
  }
@@ -2260,16 +1021,16 @@ program.command("init").description("Initialize memory-core in the current proje
2260
1021
  };
2261
1022
  let memories = [];
2262
1023
  try {
2263
- const archQuery = [backendArchitecture, frontendFramework, language].filter(Boolean).join(" ");
1024
+ const archQuery = [backendArchitecture, frontendFramework, language].filter(Boolean).filter((a) => a !== "custom").join(" ");
2264
1025
  const selection = await retrieveMemorySelection({
2265
- query: archQuery,
1026
+ query: archQuery || language,
2266
1027
  cwd: process.cwd(),
2267
1028
  config,
2268
1029
  limit: 20
2269
1030
  });
2270
1031
  memories = selection.included;
2271
1032
  if (memories.length > 0) {
2272
- console.log(chalk2.dim(` Found ${memories.length} relevant memories`));
1033
+ console.log(chalk.dim(` Found ${memories.length} relevant memories`));
2273
1034
  printMemorySelection(selection);
2274
1035
  }
2275
1036
  } catch {
@@ -2295,7 +1056,7 @@ program.command("init").description("Initialize memory-core in the current proje
2295
1056
  if (gitignoreEntries.length > 0) {
2296
1057
  const added = appendMissingGitignoreEntries(gitignoreEntries, GITIGNORE_HEADING);
2297
1058
  if (added > 0) {
2298
- console.log(chalk2.green(` \u2713 Added ${added} generated files to .gitignore`));
1059
+ console.log(chalk.green(` \u2713 Added ${added} generated files to .gitignore`));
2299
1060
  }
2300
1061
  }
2301
1062
  if (enableHook) {
@@ -2303,12 +1064,19 @@ program.command("init").description("Initialize memory-core in the current proje
2303
1064
  }
2304
1065
  const chatModelForBanner = process.env.CHAT_MODEL ?? DEFAULT_CHAT_MODEL;
2305
1066
  printBanner(config.projectName, written.written.length, { postgresOk: pgOk, ollamaOk, chatModel: chatModelForBanner });
1067
+ if (backendArchitecture === "custom" || frontendFramework === "custom") {
1068
+ console.log(chalk.yellow("\n Custom architecture \u2014 no profile rules loaded."));
1069
+ console.log(chalk.dim(" Add rules as your architecture takes shape:"));
1070
+ console.log(chalk.dim(' memory-core remember "Your rule" --type rule'));
1071
+ console.log(chalk.dim(" Or load a profile later when you decide:"));
1072
+ console.log(chalk.dim(" memory-core seed --arch clean-architecture\n"));
1073
+ }
2306
1074
  await closePool();
2307
1075
  });
2308
1076
  program.command("sync").description("Re-pull memories and regenerate AI agent files").action(async () => {
2309
1077
  const config = readProjectConfig();
2310
1078
  if (!config) {
2311
- console.error(chalk2.red("No .memory-core.json found. Run: memory-core init"));
1079
+ console.error(chalk.red("No .memory-core.json found. Run: memory-core init"));
2312
1080
  process.exit(1);
2313
1081
  }
2314
1082
  const { checkbox } = await import("@inquirer/prompts");
@@ -2323,7 +1091,7 @@ program.command("sync").description("Re-pull memories and regenerate AI agent fi
2323
1091
  instructions: " (Space to toggle, A to select all, Enter to confirm)"
2324
1092
  });
2325
1093
  if (selectedAgents.length === 0) {
2326
- console.log(chalk2.yellow(" No agents selected \u2014 nothing to sync."));
1094
+ console.log(chalk.yellow(" No agents selected \u2014 nothing to sync."));
2327
1095
  process.exit(0);
2328
1096
  }
2329
1097
  await syncGeneratedFiles(config, [...selectedAgents, "Shared"], { showSelection: true });
@@ -2332,24 +1100,24 @@ program.command("sync").description("Re-pull memories and regenerate AI agent fi
2332
1100
  program.command("auto-sync [mode]").description("Show or change automatic agent file sync (on|off)").action((mode) => {
2333
1101
  const config = readProjectConfig();
2334
1102
  if (!config) {
2335
- console.error(chalk2.red("No .memory-core.json found. Run: memory-core init"));
1103
+ console.error(chalk.red("No .memory-core.json found. Run: memory-core init"));
2336
1104
  process.exit(1);
2337
1105
  }
2338
1106
  const normalized = mode?.trim().toLowerCase();
2339
1107
  if (!normalized || normalized === "status") {
2340
- console.log(chalk2.bold("\n Auto-sync\n"));
2341
- console.log(` Status: ${config.autoSync === false ? chalk2.yellow("disabled") : chalk2.green("enabled")}`);
2342
- console.log(chalk2.gray(" Manual sync is always available: memory-core sync\n"));
1108
+ console.log(chalk.bold("\n Auto-sync\n"));
1109
+ console.log(` Status: ${config.autoSync === false ? chalk.yellow("disabled") : chalk.green("enabled")}`);
1110
+ console.log(chalk.gray(" Manual sync is always available: memory-core sync\n"));
2343
1111
  return;
2344
1112
  }
2345
1113
  if (normalized !== "on" && normalized !== "off") {
2346
- console.error(chalk2.red("Use: memory-core auto-sync [on|off|status]"));
1114
+ console.error(chalk.red("Use: memory-core auto-sync [on|off|status]"));
2347
1115
  process.exit(1);
2348
1116
  }
2349
1117
  const enabled = normalized === "on";
2350
1118
  writeProjectConfig({ ...config, autoSync: enabled });
2351
- console.log(chalk2.green(`Auto-sync ${enabled ? "enabled" : "disabled"}`));
2352
- console.log(chalk2.gray(" Manual sync is always available: memory-core sync"));
1119
+ console.log(chalk.green(`Auto-sync ${enabled ? "enabled" : "disabled"}`));
1120
+ console.log(chalk.gray(" Manual sync is always available: memory-core sync"));
2353
1121
  });
2354
1122
  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) => {
2355
1123
  const config = readProjectConfig();
@@ -2357,7 +1125,7 @@ program.command("remember <text>").description("Save a new memory to the central
2357
1125
  let reason = opts.reason;
2358
1126
  if (!reason) {
2359
1127
  reason = await input({
2360
- message: chalk2.dim("Why should this memory exist?"),
1128
+ message: chalk.dim("Why should this memory exist?"),
2361
1129
  default: ""
2362
1130
  });
2363
1131
  }
@@ -2374,11 +1142,11 @@ program.command("remember <text>").description("Save a new memory to the central
2374
1142
  context: buildMemoryContext(opts),
2375
1143
  tags: parseTags(opts.tags)
2376
1144
  });
2377
- const dbVersionPath = join3(process.cwd(), ".memory-core-db-version");
2378
- writeFileSync3(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2379
- const reasonLine = chalk2.gray(`
1145
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
1146
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1147
+ const reasonLine = chalk.gray(`
2380
1148
  Why: ${storedReason}`);
2381
- spinner.succeed(chalk2.green(`Memory saved: "${text}"`) + reasonLine);
1149
+ spinner.succeed(chalk.green(`Memory saved: "${text}"`) + reasonLine);
2382
1150
  await autoSyncGeneratedFiles(config, "remember", opts.sync);
2383
1151
  } catch (err) {
2384
1152
  spinner.fail(`Failed: ${err.message}`);
@@ -2399,20 +1167,20 @@ program.command("search <query>").description("Search memories using semantic si
2399
1167
  const results = result.items;
2400
1168
  spinner.stop();
2401
1169
  if (results.length === 0) {
2402
- console.log(chalk2.yellow("No memories found."));
1170
+ console.log(chalk.yellow("No memories found."));
2403
1171
  } else {
2404
- console.log(chalk2.bold(`
1172
+ console.log(chalk.bold(`
2405
1173
  ${results.length} results for "${query}"
2406
1174
  `));
2407
1175
  results.forEach((m, i) => {
2408
- const sim = m.similarity ? chalk2.gray(` (${(m.similarity * 100).toFixed(0)}% match)`) : "";
2409
- console.log(chalk2.cyan(` ${i + 1}. [${m.type}] ${m.title ?? ""}`));
2410
- console.log(chalk2.white(` ${m.content}`) + sim);
2411
- if (m.reason) console.log(chalk2.gray(` why: ${m.reason}`));
2412
- if (m.context?.appliesTo?.length) console.log(chalk2.gray(` use when: ${m.context.appliesTo.join("; ")}`));
2413
- if (m.context?.avoidWhen?.length) console.log(chalk2.gray(` avoid when: ${m.context.avoidWhen.join("; ")}`));
2414
- if (m.context?.examples?.length) console.log(chalk2.gray(` examples: ${m.context.examples.join("; ")}`));
2415
- if (m.tags?.length) console.log(chalk2.gray(` tags: ${m.tags.join(", ")}`));
1176
+ const sim = m.similarity ? chalk.gray(` (${(m.similarity * 100).toFixed(0)}% match)`) : "";
1177
+ console.log(chalk.cyan(` ${i + 1}. [${m.type}] ${m.title ?? ""}`));
1178
+ console.log(chalk.white(` ${m.content}`) + sim);
1179
+ if (m.reason) console.log(chalk.gray(` why: ${m.reason}`));
1180
+ if (m.context?.appliesTo?.length) console.log(chalk.gray(` use when: ${m.context.appliesTo.join("; ")}`));
1181
+ if (m.context?.avoidWhen?.length) console.log(chalk.gray(` avoid when: ${m.context.avoidWhen.join("; ")}`));
1182
+ if (m.context?.examples?.length) console.log(chalk.gray(` examples: ${m.context.examples.join("; ")}`));
1183
+ if (m.tags?.length) console.log(chalk.gray(` tags: ${m.tags.join(", ")}`));
2416
1184
  console.log();
2417
1185
  });
2418
1186
  }
@@ -2427,9 +1195,9 @@ program.command("export").description(`Export DB memories to ${MEMORY_FILE}`).op
2427
1195
  try {
2428
1196
  const memories = await phase1.services.memoryEngine.list({ limit: 1e4 });
2429
1197
  const portable = memories.map(toPortableFromRecord);
2430
- const outputPath = opts.output ? join3(process.cwd(), opts.output) : writeMemoryFile(portable);
1198
+ const outputPath = opts.output ? join(process.cwd(), opts.output) : writeMemoryFile(portable);
2431
1199
  if (opts.output) {
2432
- writeFileSync3(outputPath, JSON.stringify(portable, null, 2) + "\n", "utf-8");
1200
+ writeFileSync(outputPath, JSON.stringify(portable, null, 2) + "\n", "utf-8");
2433
1201
  }
2434
1202
  spinner.succeed(`Exported ${portable.length} memories to ${outputPath}`);
2435
1203
  } catch (err) {
@@ -2443,7 +1211,7 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
2443
1211
  const spinner = ora("Reading memories\u2026").start();
2444
1212
  try {
2445
1213
  const config = readProjectConfig();
2446
- const memories = opts.url ? await readMemoryFileFromUrl(opts.url) : opts.file ? parseMemoryFile(readFileSync3(join3(process.cwd(), opts.file), "utf-8")) : readMemoryFile();
1214
+ const memories = opts.url ? await readMemoryFileFromUrl(opts.url) : opts.file ? parseMemoryFile(readFileSync(join(process.cwd(), opts.file), "utf-8")) : readMemoryFile();
2447
1215
  let inserted = 0;
2448
1216
  let skipped = 0;
2449
1217
  spinner.text = `Importing ${memories.length} memories\u2026`;
@@ -2464,8 +1232,8 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
2464
1232
  }
2465
1233
  spinner.succeed(`Imported ${inserted} memories, skipped ${skipped} duplicates`);
2466
1234
  if (inserted > 0) {
2467
- const dbVersionPath = join3(process.cwd(), ".memory-core-db-version");
2468
- writeFileSync3(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1235
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
1236
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2469
1237
  await autoSyncGeneratedFiles(config, "import", opts.sync);
2470
1238
  }
2471
1239
  } catch (err) {
@@ -2491,10 +1259,10 @@ program.command("list").description("List memories from the local database").opt
2491
1259
  const title = opts.all ? "All memories" : `Current project memories${architectures ? ` (${Array.isArray(architectures) ? architectures.join(", ") : architectures})` : ""}`;
2492
1260
  printMemoryTable(memories.map(toMemoryTableRow), title);
2493
1261
  if (!opts.all) {
2494
- console.log(chalk2.gray(" Showing current project context plus shared/global memories. Use --all for the full database.\n"));
1262
+ console.log(chalk.gray(" Showing current project context plus shared/global memories. Use --all for the full database.\n"));
2495
1263
  }
2496
1264
  } catch (err) {
2497
- console.error(chalk2.red(`List failed: ${err.message}`));
1265
+ console.error(chalk.red(`List failed: ${err.message}`));
2498
1266
  process.exit(1);
2499
1267
  } finally {
2500
1268
  await closePool();
@@ -2505,13 +1273,13 @@ program.command("remove <id>").description("Remove a memory by ID").option("--no
2505
1273
  const config = readProjectConfig();
2506
1274
  const deleted = await phase1.services.memoryEngine.removeById(parseInt(id, 10));
2507
1275
  if (!deleted) {
2508
- console.log(chalk2.yellow(`No memory found with ID ${id}`));
1276
+ console.log(chalk.yellow(`No memory found with ID ${id}`));
2509
1277
  process.exit(1);
2510
1278
  }
2511
- console.log(chalk2.green(`Removed memory ${id}`));
1279
+ console.log(chalk.green(`Removed memory ${id}`));
2512
1280
  await autoSyncGeneratedFiles(config, "remove", opts.sync);
2513
1281
  } catch (err) {
2514
- console.error(chalk2.red(`Remove failed: ${err.message}`));
1282
+ console.error(chalk.red(`Remove failed: ${err.message}`));
2515
1283
  process.exit(1);
2516
1284
  } finally {
2517
1285
  await closePool();
@@ -2526,12 +1294,12 @@ program.command("forget").description("Bulk-delete memories by tag, scope, type,
2526
1294
  type: opts.type,
2527
1295
  architecture: opts.arch
2528
1296
  });
2529
- console.log(chalk2.green(`Deleted ${deleted} memories`));
1297
+ console.log(chalk.green(`Deleted ${deleted} memories`));
2530
1298
  if (deleted > 0) {
2531
1299
  await autoSyncGeneratedFiles(config, "forget", opts.sync);
2532
1300
  }
2533
1301
  } catch (err) {
2534
- console.error(chalk2.red(`Forget failed: ${err.message}`));
1302
+ console.error(chalk.red(`Forget failed: ${err.message}`));
2535
1303
  process.exit(1);
2536
1304
  } finally {
2537
1305
  await closePool();
@@ -2543,7 +1311,7 @@ program.command("edit <id>").description("Edit a memory interactively").option("
2543
1311
  const config = readProjectConfig();
2544
1312
  const existing = await phase1.services.memoryEngine.getById(memoryId);
2545
1313
  if (!existing) {
2546
- console.log(chalk2.yellow(`No memory found with ID ${id}`));
1314
+ console.log(chalk.yellow(`No memory found with ID ${id}`));
2547
1315
  process.exit(1);
2548
1316
  }
2549
1317
  const type = await input({ message: "Type?", default: existing.type });
@@ -2565,10 +1333,10 @@ program.command("edit <id>").description("Edit a memory interactively").option("
2565
1333
  context: buildMemoryContext({ appliesTo, avoidWhen, example: examples, source }),
2566
1334
  tags: parseTags(tags)
2567
1335
  });
2568
- console.log(chalk2.green(`Updated memory ${id}`));
1336
+ console.log(chalk.green(`Updated memory ${id}`));
2569
1337
  await autoSyncGeneratedFiles(config, "edit", opts.sync);
2570
1338
  } catch (err) {
2571
- console.error(chalk2.red(`Edit failed: ${err.message}`));
1339
+ console.error(chalk.red(`Edit failed: ${err.message}`));
2572
1340
  process.exit(1);
2573
1341
  } finally {
2574
1342
  await closePool();
@@ -2585,15 +1353,15 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
2585
1353
  if (opts.remove) {
2586
1354
  const deleted = await phase1.services.memoryEngine.removeById(parseInt(opts.remove, 10));
2587
1355
  if (!deleted) {
2588
- console.log(chalk2.yellow(`No ignore pattern found with ID ${opts.remove}`));
1356
+ console.log(chalk.yellow(`No ignore pattern found with ID ${opts.remove}`));
2589
1357
  process.exit(1);
2590
1358
  }
2591
- console.log(chalk2.green(`Removed ignore pattern ${opts.remove}`));
1359
+ console.log(chalk.green(`Removed ignore pattern ${opts.remove}`));
2592
1360
  await autoSyncGeneratedFiles(config, "ignore remove", opts.sync);
2593
1361
  return;
2594
1362
  }
2595
1363
  if (!pattern) {
2596
- console.error(chalk2.red("Provide a pattern, --list, or --remove <id>"));
1364
+ console.error(chalk.red("Provide a pattern, --list, or --remove <id>"));
2597
1365
  process.exit(1);
2598
1366
  }
2599
1367
  await phase1.services.memoryEngine.remember({
@@ -2604,12 +1372,12 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
2604
1372
  content: pattern,
2605
1373
  tags: ["ignore"]
2606
1374
  });
2607
- const dbVersionPath = join3(process.cwd(), ".memory-core-db-version");
2608
- writeFileSync3(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2609
- console.log(chalk2.green(`Ignored pattern saved: "${pattern}"`));
1375
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
1376
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1377
+ console.log(chalk.green(`Ignored pattern saved: "${pattern}"`));
2610
1378
  await autoSyncGeneratedFiles(config, "ignore", opts.sync);
2611
1379
  } catch (err) {
2612
- console.error(chalk2.red(`Ignore failed: ${err.message}`));
1380
+ console.error(chalk.red(`Ignore failed: ${err.message}`));
2613
1381
  process.exit(1);
2614
1382
  } finally {
2615
1383
  await closePool();
@@ -2619,10 +1387,10 @@ program.command("allow [pattern]").description("Manage project allow patterns in
2619
1387
  if (opts.list) {
2620
1388
  const patterns = getAllowPatterns(readProjectConfig());
2621
1389
  if (patterns.length === 0) {
2622
- console.log(chalk2.yellow("\n No allow patterns configured.\n"));
1390
+ console.log(chalk.yellow("\n No allow patterns configured.\n"));
2623
1391
  return;
2624
1392
  }
2625
- console.log(chalk2.bold("\n Allow patterns\n"));
1393
+ console.log(chalk.bold("\n Allow patterns\n"));
2626
1394
  patterns.forEach((entry, index) => console.log(` ${index + 1}. ${entry}`));
2627
1395
  console.log();
2628
1396
  return;
@@ -2632,35 +1400,35 @@ program.command("allow [pattern]").description("Manage project allow patterns in
2632
1400
  ...config,
2633
1401
  allowPatterns: getAllowPatterns(config).filter((entry) => entry !== opts.remove)
2634
1402
  }));
2635
- console.log(chalk2.green(`Removed allow pattern: "${opts.remove}"`));
1403
+ console.log(chalk.green(`Removed allow pattern: "${opts.remove}"`));
2636
1404
  return;
2637
1405
  }
2638
1406
  if (!pattern) {
2639
- console.error(chalk2.red("Provide a pattern, --list, or --remove <pattern>"));
1407
+ console.error(chalk.red("Provide a pattern, --list, or --remove <pattern>"));
2640
1408
  process.exit(1);
2641
1409
  }
2642
1410
  updateProjectConfig((config) => ({
2643
1411
  ...config,
2644
1412
  allowPatterns: [.../* @__PURE__ */ new Set([...getAllowPatterns(config), pattern])]
2645
1413
  }));
2646
- console.log(chalk2.green(`Allow pattern saved: "${pattern}"`));
1414
+ console.log(chalk.green(`Allow pattern saved: "${pattern}"`));
2647
1415
  });
2648
1416
  program.command("commit-rules [pattern]").description("Manage commit message rules in .memory-core.json").option("--message <msg>", "Error message shown when rule is violated (required when adding)").option("--negate", "Pattern must NOT match (default: must match)").option("--advisory", "Warn only \u2014 do not block commit").option("--list", "List current commit rules").option("--remove <pattern>", "Remove a commit rule by pattern").action((pattern, opts) => {
2649
1417
  if (opts.list) {
2650
1418
  const rules = readProjectConfig()?.commitRules ?? [];
2651
1419
  if (rules.length === 0) {
2652
- console.log(chalk2.yellow("\n No commit rules configured.\n"));
1420
+ console.log(chalk.yellow("\n No commit rules configured.\n"));
2653
1421
  return;
2654
1422
  }
2655
- console.log(chalk2.bold("\n Commit message rules\n"));
1423
+ console.log(chalk.bold("\n Commit message rules\n"));
2656
1424
  rules.forEach((rule, i) => {
2657
1425
  const flags = [
2658
1426
  rule.negate ? "must NOT match" : "must match",
2659
1427
  rule.advisory ? "advisory" : "blocking"
2660
1428
  ].join(", ");
2661
1429
  console.log(` ${i + 1}. ${rule.pattern}`);
2662
- console.log(chalk2.dim(` Message: ${rule.message}`));
2663
- console.log(chalk2.dim(` Flags: ${flags}`));
1430
+ console.log(chalk.dim(` Message: ${rule.message}`));
1431
+ console.log(chalk.dim(` Flags: ${flags}`));
2664
1432
  console.log();
2665
1433
  });
2666
1434
  return;
@@ -2670,21 +1438,21 @@ program.command("commit-rules [pattern]").description("Manage commit message rul
2670
1438
  ...config,
2671
1439
  commitRules: (config.commitRules ?? []).filter((r) => r.pattern !== opts.remove)
2672
1440
  }));
2673
- console.log(chalk2.green(`Commit rule removed: "${opts.remove}"`));
1441
+ console.log(chalk.green(`Commit rule removed: "${opts.remove}"`));
2674
1442
  return;
2675
1443
  }
2676
1444
  if (!pattern) {
2677
- console.error(chalk2.red("Provide a pattern, --list, or --remove <pattern>"));
1445
+ console.error(chalk.red("Provide a pattern, --list, or --remove <pattern>"));
2678
1446
  process.exit(1);
2679
1447
  }
2680
1448
  if (!opts.message) {
2681
- console.error(chalk2.red("--message is required when adding a commit rule"));
1449
+ console.error(chalk.red("--message is required when adding a commit rule"));
2682
1450
  process.exit(1);
2683
1451
  }
2684
1452
  try {
2685
1453
  new RegExp(pattern);
2686
1454
  } catch {
2687
- console.error(chalk2.red(`Invalid regex pattern: "${pattern}"`));
1455
+ console.error(chalk.red(`Invalid regex pattern: "${pattern}"`));
2688
1456
  process.exit(1);
2689
1457
  }
2690
1458
  const newRule = {
@@ -2697,13 +1465,13 @@ program.command("commit-rules [pattern]").description("Manage commit message rul
2697
1465
  ...config,
2698
1466
  commitRules: [...(config.commitRules ?? []).filter((r) => r.pattern !== pattern), newRule]
2699
1467
  }));
2700
- console.log(chalk2.green(`Commit rule saved: "${pattern}"`));
2701
- console.log(chalk2.dim(" Run: memory-core commit-rules --list to see all rules"));
1468
+ console.log(chalk.green(`Commit rule saved: "${pattern}"`));
1469
+ console.log(chalk.dim(" Run: memory-core commit-rules --list to see all rules"));
2702
1470
  });
2703
1471
  program.command("ci-setup").description("Generate GitHub Actions workflow for memory-core").action(() => {
2704
- const workflowPath = join3(process.cwd(), ".github", "workflows", "memory-core.yml");
1472
+ const workflowPath = join(process.cwd(), ".github", "workflows", "memory-core.yml");
2705
1473
  mkdirSync(dirname(workflowPath), { recursive: true });
2706
- writeFileSync3(workflowPath, `name: memory-core
1474
+ writeFileSync(workflowPath, `name: memory-core
2707
1475
  on: [pull_request]
2708
1476
  jobs:
2709
1477
  check:
@@ -2714,7 +1482,7 @@ jobs:
2714
1482
  fetch-depth: 0
2715
1483
  - run: npx @shahmilsaari/memory-core check --ci
2716
1484
  `, "utf-8");
2717
- console.log(chalk2.green(`Generated ${workflowPath}`));
1485
+ console.log(chalk.green(`Generated ${workflowPath}`));
2718
1486
  });
2719
1487
  program.command("reset").description("Remove memory-core generated files and local project config").option("--soft", "Only remove generated files; keep config and DB").option("--db", "Also drop the memories table after confirmation").action(async (opts) => {
2720
1488
  let removed = removeProjectFiles(OUTPUT_FILES.map((file) => file.path)).length;
@@ -2730,14 +1498,14 @@ program.command("reset").description("Remove memory-core generated files and loc
2730
1498
  if (ok) {
2731
1499
  await getPool().query("DROP TABLE IF EXISTS memories");
2732
1500
  await closePool();
2733
- console.log(chalk2.yellow("Dropped memories table"));
1501
+ console.log(chalk.yellow("Dropped memories table"));
2734
1502
  }
2735
1503
  }
2736
- console.log(chalk2.green(`Reset complete. Removed ${removed} files.`));
1504
+ console.log(chalk.green(`Reset complete. Removed ${removed} files.`));
2737
1505
  });
2738
1506
  program.command("uninstall").description("Remove memory-core from the current project").option("--db", "Also drop the memories table after confirmation").action(async (opts) => {
2739
1507
  const generatedFiles = OUTPUT_FILES.map((file) => file.path);
2740
- const gitignoreEntries = [...generatedFiles, ...LOCAL_GENERATED_FILES];
1508
+ const gitignoreEntries = [...generatedFiles, ...LOCAL_STATE_FILES];
2741
1509
  const removed = removeProjectFiles([
2742
1510
  ...generatedFiles,
2743
1511
  ...LOCAL_STATE_FILES,
@@ -2753,19 +1521,19 @@ program.command("uninstall").description("Remove memory-core from the current pr
2753
1521
  if (ok) {
2754
1522
  await getPool().query("DROP TABLE IF EXISTS memories");
2755
1523
  await closePool();
2756
- console.log(chalk2.yellow("Dropped memories table"));
1524
+ console.log(chalk.yellow("Dropped memories table"));
2757
1525
  }
2758
1526
  }
2759
- console.log(chalk2.green(`Uninstall complete. Removed ${removed.length} files.`));
1527
+ console.log(chalk.green(`Uninstall complete. Removed ${removed.length} files.`));
2760
1528
  if (removed.length > 0) {
2761
- removed.forEach((file) => console.log(chalk2.gray(` \u2713 ${file}`)));
1529
+ removed.forEach((file) => console.log(chalk.gray(` \u2713 ${file}`)));
2762
1530
  }
2763
1531
  if (cleanedGitignore) {
2764
- console.log(chalk2.gray(" \u2713 cleaned .gitignore memory-core block"));
1532
+ console.log(chalk.gray(" \u2713 cleaned .gitignore memory-core block"));
2765
1533
  }
2766
1534
  });
2767
1535
  program.command("stats").description("Show violation counters recorded by check and watch").option("--reset", "Reset violation counters and recent history").option("--tune", "Show only noisy rules (>40% false-positive rate) with disable commands").action((opts) => {
2768
- const statsPath = join3(process.cwd(), ".memory-core-stats.json");
1536
+ const statsPath = join(process.cwd(), ".memory-core-stats.json");
2769
1537
  if (opts.reset) {
2770
1538
  const emptyStats = {
2771
1539
  rules: {},
@@ -2777,27 +1545,27 @@ program.command("stats").description("Show violation counters recorded by check
2777
1545
  },
2778
1546
  recentViolations: []
2779
1547
  };
2780
- writeFileSync3(statsPath, JSON.stringify(emptyStats, null, 2) + "\n", "utf-8");
2781
- console.log(chalk2.green("\n Violation stats reset.\n"));
1548
+ writeFileSync(statsPath, JSON.stringify(emptyStats, null, 2) + "\n", "utf-8");
1549
+ console.log(chalk.green("\n Violation stats reset.\n"));
2782
1550
  return;
2783
1551
  }
2784
- if (!existsSync3(statsPath)) {
2785
- console.log(chalk2.yellow("\n No violation stats recorded yet.\n"));
1552
+ if (!existsSync(statsPath)) {
1553
+ console.log(chalk.yellow("\n No violation stats recorded yet.\n"));
2786
1554
  return;
2787
1555
  }
2788
- const stats = JSON.parse(readFileSync3(statsPath, "utf-8"));
1556
+ const stats = JSON.parse(readFileSync(statsPath, "utf-8"));
2789
1557
  const toEntry = (raw) => {
2790
1558
  if (raw === void 0) return { count: 0, falsePositives: 0 };
2791
1559
  if (typeof raw === "number") return { count: raw, falsePositives: 0 };
2792
1560
  return raw;
2793
1561
  };
2794
1562
  const printTop = (label, values = {}) => {
2795
- console.log(chalk2.bold(`
1563
+ console.log(chalk.bold(`
2796
1564
  ${label}
2797
1565
  `));
2798
1566
  Object.entries(values).map(([name, raw]) => ({ name, entry: toEntry(raw) })).sort((a, b) => b.entry.count - a.entry.count).slice(0, 10).forEach(({ name, entry }, index) => {
2799
1567
  const rate = entry.count > 0 ? Math.round(entry.falsePositives / entry.count * 100) : 0;
2800
- const fpHint = rate > 0 ? chalk2.dim(` \u2014 ${rate}% false-positive rate`) + (rate > 25 ? " \u26A0\uFE0F" : "") : "";
1568
+ const fpHint = rate > 0 ? chalk.dim(` \u2014 ${rate}% false-positive rate`) + (rate > 25 ? " \u26A0\uFE0F" : "") : "";
2801
1569
  console.log(` ${index + 1}. ${truncate(name, 44).padEnd(46)} ${entry.count} hits${fpHint}`);
2802
1570
  });
2803
1571
  };
@@ -2810,18 +1578,18 @@ program.command("stats").description("Show violation counters recorded by check
2810
1578
  rate: entry.count > 0 ? Math.round(entry.falsePositives / entry.count * 100) : 0
2811
1579
  })).filter((r) => r.rate > tuneThreshold && r.count >= tuneMinCount).sort((a, b) => b.rate - a.rate);
2812
1580
  if (noisy.length === 0) {
2813
- console.log(chalk2.green(`
1581
+ console.log(chalk.green(`
2814
1582
  \u2713 No noisy rules found (threshold: ${tuneThreshold}%, min hits: ${tuneMinCount})
2815
1583
  `));
2816
1584
  return;
2817
1585
  }
2818
- console.log(chalk2.bold(`
1586
+ console.log(chalk.bold(`
2819
1587
  Noisy rules (>${tuneThreshold}% false-positive rate, \u2265${tuneMinCount} hits)
2820
1588
  `));
2821
1589
  noisy.forEach(({ name, count, rate }, i) => {
2822
1590
  console.log(` ${i + 1}. ${truncate(name, 50).padEnd(52)} ${count} hits \u2014 ${rate}% \u26A0\uFE0F`);
2823
- console.log(chalk2.dim(` To disable: memory-core allow "${name}"`));
2824
- console.log(chalk2.dim(` Interactive: memory-core tune`));
1591
+ console.log(chalk.dim(` To disable: memory-core allow "${name}"`));
1592
+ console.log(chalk.dim(` Interactive: memory-core tune`));
2825
1593
  console.log();
2826
1594
  });
2827
1595
  return;
@@ -2839,20 +1607,20 @@ program.command("stats").description("Show violation counters recorded by check
2839
1607
  hasLiveState ? liveFiles : stats.files
2840
1608
  );
2841
1609
  if (!hasLiveState) {
2842
- console.log(chalk2.dim("\n Note: these counters are historical events, not live current code state."));
2843
- console.log(chalk2.dim(" Start watch for live counters, or reset with: memory-core stats --reset\n"));
1610
+ console.log(chalk.dim("\n Note: these counters are historical events, not live current code state."));
1611
+ console.log(chalk.dim(" Start watch for live counters, or reset with: memory-core stats --reset\n"));
2844
1612
  } else {
2845
1613
  if (hasLiveViolations) {
2846
- console.log(chalk2.dim("\n Live counters auto-refresh while watch is running.\n"));
1614
+ console.log(chalk.dim("\n Live counters auto-refresh while watch is running.\n"));
2847
1615
  } else {
2848
- console.log(chalk2.dim("\n Current live state has no violations.\n"));
1616
+ console.log(chalk.dim("\n Current live state has no violations.\n"));
2849
1617
  }
2850
1618
  }
2851
1619
  });
2852
1620
  program.command("tune").description("Review and disable noisy rules with high false-positive rates").option("--threshold <percent>", "False-positive rate % above which a rule is noisy (default: 40)", "40").option("--min-count <n>", "Minimum hit count required to consider a rule (default: 5)", "5").option("--yes", "Disable all noisy rules without prompting").action(async (opts) => {
2853
- const statsPath = join3(process.cwd(), ".memory-core-stats.json");
2854
- if (!existsSync3(statsPath)) {
2855
- console.log(chalk2.yellow("\n No violation stats yet. Run some commits first.\n"));
1621
+ const statsPath = join(process.cwd(), ".memory-core-stats.json");
1622
+ if (!existsSync(statsPath)) {
1623
+ console.log(chalk.yellow("\n No violation stats yet. Run some commits first.\n"));
2856
1624
  return;
2857
1625
  }
2858
1626
  const threshold = Math.max(0, Math.min(100, parseInt(opts.threshold, 10) || 40));
@@ -2862,19 +1630,19 @@ program.command("tune").description("Review and disable noisy rules with high fa
2862
1630
  if (typeof raw === "number") return { count: raw, falsePositives: 0 };
2863
1631
  return raw;
2864
1632
  };
2865
- const stats = JSON.parse(readFileSync3(statsPath, "utf-8"));
1633
+ const stats = JSON.parse(readFileSync(statsPath, "utf-8"));
2866
1634
  const noisy = Object.entries(stats.rules ?? {}).map(([rule, raw]) => {
2867
1635
  const entry = toEntry(raw);
2868
1636
  const rate = entry.count > 0 ? Math.round(entry.falsePositives / entry.count * 100) : 0;
2869
1637
  return { rule, count: entry.count, rate };
2870
1638
  }).filter((r) => r.rate > threshold && r.count >= minCount).sort((a, b) => b.rate - a.rate);
2871
1639
  if (noisy.length === 0) {
2872
- console.log(chalk2.green(`
1640
+ console.log(chalk.green(`
2873
1641
  \u2713 All rules within acceptable noise (threshold: ${threshold}%, min hits: ${minCount})
2874
1642
  `));
2875
1643
  return;
2876
1644
  }
2877
- console.log(chalk2.bold(`
1645
+ console.log(chalk.bold(`
2878
1646
  Found ${noisy.length} noisy rule${noisy.length > 1 ? "s" : ""} (>${threshold}% false-positive rate, \u2265${minCount} hits)
2879
1647
  `));
2880
1648
  const existingAllows = new Set(
@@ -2885,22 +1653,22 @@ program.command("tune").description("Review and disable noisy rules with high fa
2885
1653
  for (const { rule, count, rate } of noisy) {
2886
1654
  const key = rule.toLowerCase();
2887
1655
  if (existingAllows.has(key)) {
2888
- console.log(chalk2.dim(` \u2022 "${truncate(rule, 56)}" \u2014 already disabled`));
1656
+ console.log(chalk.dim(` \u2022 "${truncate(rule, 56)}" \u2014 already disabled`));
2889
1657
  continue;
2890
1658
  }
2891
1659
  toAdd.add(key);
2892
- console.log(chalk2.green(` \u2713 "${truncate(rule, 56)}"`) + chalk2.dim(` \u2014 ${count} hits, ${rate}% FP rate`));
1660
+ console.log(chalk.green(` \u2713 "${truncate(rule, 56)}"`) + chalk.dim(` \u2014 ${count} hits, ${rate}% FP rate`));
2893
1661
  }
2894
1662
  } else {
2895
1663
  const { select: select2 } = await import("@inquirer/prompts");
2896
1664
  for (let i = 0; i < noisy.length; i++) {
2897
1665
  const { rule, count, rate } = noisy[i];
2898
1666
  const key = rule.toLowerCase();
2899
- console.log(chalk2.bold(`
1667
+ console.log(chalk.bold(`
2900
1668
  [${i + 1}/${noisy.length}] "${truncate(rule, 60)}"`));
2901
- console.log(chalk2.dim(` ${count} hits \u2014 ${rate}% false-positive rate \u26A0\uFE0F`));
1669
+ console.log(chalk.dim(` ${count} hits \u2014 ${rate}% false-positive rate \u26A0\uFE0F`));
2902
1670
  if (existingAllows.has(key)) {
2903
- console.log(chalk2.dim(" Already in allow patterns \u2014 skipping"));
1671
+ console.log(chalk.dim(" Already in allow patterns \u2014 skipping"));
2904
1672
  continue;
2905
1673
  }
2906
1674
  const choice = await select2({
@@ -2914,7 +1682,7 @@ program.command("tune").description("Review and disable noisy rules with high fa
2914
1682
  if (choice === "quit") break;
2915
1683
  if (choice === "disable") {
2916
1684
  toAdd.add(key);
2917
- console.log(chalk2.green(" \u2713 Marked for disable"));
1685
+ console.log(chalk.green(" \u2713 Marked for disable"));
2918
1686
  }
2919
1687
  }
2920
1688
  }
@@ -2923,11 +1691,11 @@ program.command("tune").description("Review and disable noisy rules with high fa
2923
1691
  ...config,
2924
1692
  allowPatterns: [.../* @__PURE__ */ new Set([...config.allowPatterns ?? [], ...toAdd])]
2925
1693
  }));
2926
- console.log(chalk2.green(`
1694
+ console.log(chalk.green(`
2927
1695
  \u2713 Saved ${toAdd.size} allow pattern${toAdd.size > 1 ? "s" : ""} to .memory-core.json`));
2928
- console.log(chalk2.dim(" These rules will no longer block commits.\n"));
1696
+ console.log(chalk.dim(" These rules will no longer block commits.\n"));
2929
1697
  } else {
2930
- console.log(chalk2.dim("\n No changes made.\n"));
1698
+ console.log(chalk.dim("\n No changes made.\n"));
2931
1699
  }
2932
1700
  });
2933
1701
  program.command("dashboard").description("Start the live Svelte dashboard with WebSocket watch events").option("-p, --port <port>", "Dashboard port", "5178").option("--path <dir>", "Directory to watch (default: current directory)").option("--no-watch", "Serve the dashboard without starting file watch").action(async (opts) => {
@@ -2947,7 +1715,7 @@ program.command("dashboard").description("Start the live Svelte dashboard with W
2947
1715
  }
2948
1716
  return void 0;
2949
1717
  };
2950
- const { startDashboard } = await import("./dashboard-server-VOT2ZRVN.js");
1718
+ const { startDashboard } = await import("./dashboard-server-SSYZLQKB.js");
2951
1719
  await startDashboard({
2952
1720
  port: parseInt(opts.port, 10),
2953
1721
  path: resolveDashboardPath(),
@@ -2957,7 +1725,7 @@ program.command("dashboard").description("Start the live Svelte dashboard with W
2957
1725
  program.command("seed").description("Load all predefined memories into the database").option("--arch <architecture>", "Only seed a specific architecture (e.g. clean-architecture)").option("--force", "Re-seed even if memories already exist", false).action(async (opts) => {
2958
1726
  await runMigrations();
2959
1727
  const filtered = opts.arch ? seeds.filter((s) => s.architecture === opts.arch || s.architecture === "global") : seeds;
2960
- console.log(chalk2.bold.cyan(`
1728
+ console.log(chalk.bold.cyan(`
2961
1729
  Seeding ${filtered.length} memories\u2026
2962
1730
  `));
2963
1731
  let saved = 0;
@@ -2977,15 +1745,15 @@ program.command("seed").description("Load all predefined memories into the datab
2977
1745
  if (opts.force) {
2978
1746
  await phase1.services.memoryEngine.rememberForce(payload);
2979
1747
  saved++;
2980
- spinner.succeed(chalk2.gray(`[${seed.architecture}] ${seed.title}`));
1748
+ spinner.succeed(chalk.gray(`[${seed.architecture}] ${seed.title}`));
2981
1749
  } else {
2982
1750
  const result = await phase1.services.memoryEngine.remember(payload);
2983
1751
  if (result === "inserted") {
2984
1752
  saved++;
2985
- spinner.succeed(chalk2.gray(`[${seed.architecture}] ${seed.title}`));
1753
+ spinner.succeed(chalk.gray(`[${seed.architecture}] ${seed.title}`));
2986
1754
  } else {
2987
1755
  skipped++;
2988
- spinner.info(chalk2.gray(`Already exists \u2014 [${seed.architecture}] ${seed.title}`));
1756
+ spinner.info(chalk.gray(`Already exists \u2014 [${seed.architecture}] ${seed.title}`));
2989
1757
  }
2990
1758
  }
2991
1759
  } catch (err) {
@@ -2994,10 +1762,10 @@ program.command("seed").description("Load all predefined memories into the datab
2994
1762
  }
2995
1763
  }
2996
1764
  if (saved > 0) {
2997
- const dbVersionPath = join3(process.cwd(), ".memory-core-db-version");
2998
- writeFileSync3(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1765
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
1766
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2999
1767
  }
3000
- console.log(chalk2.bold.green(`
1768
+ console.log(chalk.bold.green(`
3001
1769
  Done. ${saved} memories seeded, ${skipped} skipped.
3002
1770
  `));
3003
1771
  await closePool();
@@ -3006,21 +1774,21 @@ program.command("global").description("Sync your memory into every AI agent glob
3006
1774
  const home = homedir();
3007
1775
  const GLOBAL_TARGETS = [
3008
1776
  // Claude Code
3009
- { label: "Claude Code", path: join3(home, ".claude/CLAUDE.md"), type: "md" },
1777
+ { label: "Claude Code", path: join(home, ".claude/CLAUDE.md"), type: "md" },
3010
1778
  // GitHub Copilot (VS Code)
3011
- { label: "Copilot", path: join3(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-copilot" },
1779
+ { label: "Copilot", path: join(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-copilot" },
3012
1780
  // Cursor global rules
3013
- { label: "Cursor", path: join3(home, ".cursor/rules/memory-core.mdc"), type: "md" },
1781
+ { label: "Cursor", path: join(home, ".cursor/rules/memory-core.mdc"), type: "md" },
3014
1782
  // Cline (VS Code)
3015
- { label: "Cline", path: join3(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-cline" },
1783
+ { label: "Cline", path: join(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-cline" },
3016
1784
  // Continue.dev global config
3017
- { label: "Continue.dev", path: join3(home, ".continue/config.json"), type: "continue" },
1785
+ { label: "Continue.dev", path: join(home, ".continue/config.json"), type: "continue" },
3018
1786
  // Aider global config
3019
- { label: "Aider", path: join3(home, ".aider.conf.yml"), type: "aider" },
1787
+ { label: "Aider", path: join(home, ".aider.conf.yml"), type: "aider" },
3020
1788
  // Zed global settings
3021
- { label: "Zed AI", path: join3(home, ".config/zed/settings.json"), type: "zed" },
1789
+ { label: "Zed AI", path: join(home, ".config/zed/settings.json"), type: "zed" },
3022
1790
  // Windsurf global rules
3023
- { label: "Windsurf", path: join3(home, ".windsurf/rules/memory-core.md"), type: "md" }
1791
+ { label: "Windsurf", path: join(home, ".windsurf/rules/memory-core.md"), type: "md" }
3024
1792
  ];
3025
1793
  const spinner = ora("Fetching global memories\u2026").start();
3026
1794
  let memories = [];
@@ -3050,12 +1818,12 @@ ${rulesText}
3050
1818
  const skipped = [];
3051
1819
  const writeFile = (filePath, content) => {
3052
1820
  mkdirSync(dirname(filePath), { recursive: true });
3053
- writeFileSync3(filePath, content, "utf-8");
1821
+ writeFileSync(filePath, content, "utf-8");
3054
1822
  };
3055
1823
  const readJson = (filePath) => {
3056
- if (!existsSync3(filePath)) return {};
1824
+ if (!existsSync(filePath)) return {};
3057
1825
  try {
3058
- return JSON.parse(readFileSync3(filePath, "utf-8"));
1826
+ return JSON.parse(readFileSync(filePath, "utf-8"));
3059
1827
  } catch {
3060
1828
  return {};
3061
1829
  }
@@ -3103,14 +1871,14 @@ read:
3103
1871
  skipped.push(target.label);
3104
1872
  }
3105
1873
  }
3106
- spinner.succeed(chalk2.green(`Synced ${memories.length} memories \u2192 ${written.length} agents`));
3107
- console.log(chalk2.green("\n Updated:"));
3108
- written.forEach((l) => console.log(chalk2.gray(` \u2713 ${l}`)));
1874
+ spinner.succeed(chalk.green(`Synced ${memories.length} memories \u2192 ${written.length} agents`));
1875
+ console.log(chalk.green("\n Updated:"));
1876
+ written.forEach((l) => console.log(chalk.gray(` \u2713 ${l}`)));
3109
1877
  if (skipped.length) {
3110
- console.log(chalk2.yellow("\n Skipped (not installed):"));
3111
- skipped.forEach((l) => console.log(chalk2.gray(` \u2717 ${l}`)));
1878
+ console.log(chalk.yellow("\n Skipped (not installed):"));
1879
+ skipped.forEach((l) => console.log(chalk.gray(` \u2717 ${l}`)));
3112
1880
  }
3113
- console.log(chalk2.bold("\n Every AI agent now follows your memory globally.\n"));
1881
+ console.log(chalk.bold("\n Every AI agent now follows your memory globally.\n"));
3114
1882
  await closePool();
3115
1883
  });
3116
1884
  var provider = program.command("provider").description("Manage the code-checking provider configuration");
@@ -3156,10 +1924,10 @@ provider.command("set <name>").description("Set the code-checking provider (olla
3156
1924
  writeRuntimeEnv(values, runtimeEnv.envPath);
3157
1925
  applyRuntimeEnv(values);
3158
1926
  appendMissingGitignoreEntries(LOCAL_STATE_FILES, GITIGNORE_HEADING);
3159
- console.log(chalk2.green(`Updated provider: ${providerName}`));
3160
- console.log(chalk2.gray(` Chat model: ${getConfiguredChatModel(values)}`));
1927
+ console.log(chalk.green(`Updated provider: ${providerName}`));
1928
+ console.log(chalk.gray(` Chat model: ${getConfiguredChatModel(values)}`));
3161
1929
  } catch (err) {
3162
- console.error(chalk2.red(`Provider update failed: ${err.message}`));
1930
+ console.error(chalk.red(`Provider update failed: ${err.message}`));
3163
1931
  process.exit(1);
3164
1932
  }
3165
1933
  });
@@ -3181,10 +1949,10 @@ model.command("set <name>").description("Set the chat model used for code checki
3181
1949
  writeRuntimeEnv(values, runtimeEnv.envPath);
3182
1950
  applyRuntimeEnv(values);
3183
1951
  appendMissingGitignoreEntries(LOCAL_STATE_FILES, GITIGNORE_HEADING);
3184
- console.log(chalk2.green(`Updated ${opts.embedding ? "embedding" : "chat"} model: ${name}`));
3185
- console.log(chalk2.gray(` Provider: ${providerName}`));
1952
+ console.log(chalk.green(`Updated ${opts.embedding ? "embedding" : "chat"} model: ${name}`));
1953
+ console.log(chalk.gray(` Provider: ${providerName}`));
3186
1954
  } catch (err) {
3187
- console.error(chalk2.red(`Model update failed: ${err.message}`));
1955
+ console.error(chalk.red(`Model update failed: ${err.message}`));
3188
1956
  process.exit(1);
3189
1957
  }
3190
1958
  });
@@ -3196,7 +1964,7 @@ program.command("status").description("Show the current memory-core project and
3196
1964
  try {
3197
1965
  await printProjectStatus();
3198
1966
  } catch (err) {
3199
- console.error(chalk2.red(`Status failed: ${err.message}`));
1967
+ console.error(chalk.red(`Status failed: ${err.message}`));
3200
1968
  process.exit(1);
3201
1969
  }
3202
1970
  });
@@ -3204,7 +1972,7 @@ var graph = program.command("graph").description("Build and inspect dependency g
3204
1972
  graph.command("migrate").description("Create or update PostgreSQL graph snapshot schema").action(async () => {
3205
1973
  const { values } = readRuntimeEnv();
3206
1974
  if (!values.DATABASE_URL) {
3207
- console.error(chalk2.red("Graph migration requires DATABASE_URL. Configure it in .memory-core.env or .env."));
1975
+ console.error(chalk.red("Graph migration requires DATABASE_URL. Configure it in .memory-core.env or .env."));
3208
1976
  process.exit(1);
3209
1977
  }
3210
1978
  const spinner = ora("Migrating graph snapshot schema\u2026").start();
@@ -3222,34 +1990,34 @@ graph.command("doctor").description("Inspect graph storage backend health (Postg
3222
1990
  const filePath = graphStoreFilePath(cwd);
3223
1991
  const usesPostgres = Boolean(values.DATABASE_URL);
3224
1992
  let ok = true;
3225
- console.log(chalk2.bold("\n graph doctor\n"));
1993
+ console.log(chalk.bold("\n graph doctor\n"));
3226
1994
  printStatusLine("Project path", cwd);
3227
1995
  printStatusLine("Configured backend", usesPostgres ? "postgres + file fallback" : "file");
3228
1996
  printStatusLine("File store", filePath);
3229
1997
  console.log();
3230
1998
  if (!usesPostgres) {
3231
- console.log(chalk2.yellow(" \u26A0 DATABASE_URL not set \u2014 using file backend only."));
1999
+ console.log(chalk.yellow(" \u26A0 DATABASE_URL not set \u2014 using file backend only."));
3232
2000
  } else {
3233
2001
  try {
3234
2002
  await migrateGraphSnapshots();
3235
2003
  const probe = await probeGraphSnapshotStore(cwd);
3236
- console.log(chalk2.green(" \u2713 Graph PostgreSQL") + chalk2.dim(` ready (${probe.snapshotCount} snapshot records for this root)`));
2004
+ console.log(chalk.green(" \u2713 Graph PostgreSQL") + chalk.dim(` ready (${probe.snapshotCount} snapshot records for this root)`));
3237
2005
  } catch (err) {
3238
2006
  ok = false;
3239
- console.log(chalk2.red(" \u2717 Graph PostgreSQL") + chalk2.dim(` ${err.message}`));
2007
+ console.log(chalk.red(" \u2717 Graph PostgreSQL") + chalk.dim(` ${err.message}`));
3240
2008
  }
3241
2009
  }
3242
2010
  try {
3243
2011
  const count = await getGraphSnapshotCount(cwd);
3244
- console.log(chalk2.green(" \u2713 Graph service") + chalk2.dim(` readable (${count ?? 0} snapshots visible)`));
2012
+ console.log(chalk.green(" \u2713 Graph service") + chalk.dim(` readable (${count ?? 0} snapshots visible)`));
3245
2013
  } catch (err) {
3246
2014
  ok = false;
3247
- console.log(chalk2.red(" \u2717 Graph service") + chalk2.dim(` ${err.message}`));
2015
+ console.log(chalk.red(" \u2717 Graph service") + chalk.dim(` ${err.message}`));
3248
2016
  }
3249
- if (existsSync3(filePath)) {
3250
- console.log(chalk2.green(" \u2713 File fallback") + chalk2.dim(" store exists"));
2017
+ if (existsSync(filePath)) {
2018
+ console.log(chalk.green(" \u2713 File fallback") + chalk.dim(" store exists"));
3251
2019
  } else {
3252
- console.log(chalk2.yellow(" \u26A0 File fallback") + chalk2.dim(" store not created yet (run graph build once)"));
2020
+ console.log(chalk.yellow(" \u26A0 File fallback") + chalk.dim(" store not created yet (run graph build once)"));
3253
2021
  }
3254
2022
  console.log();
3255
2023
  if (!ok) process.exit(1);
@@ -3260,9 +2028,9 @@ graph.command("build").description("Build a dependency graph snapshot and persis
3260
2028
  try {
3261
2029
  const { snapshot } = await phase1.services.graphEngine.buildAndStoreSnapshot({ cwd });
3262
2030
  spinner.succeed(`Graph snapshot saved: ${snapshot.id}`);
3263
- console.log(chalk2.gray(` Root: ${snapshot.rootPath}`));
3264
- console.log(chalk2.gray(` Nodes: ${snapshot.nodes.length}`));
3265
- console.log(chalk2.gray(` Edges: ${snapshot.edges.length}
2031
+ console.log(chalk.gray(` Root: ${snapshot.rootPath}`));
2032
+ console.log(chalk.gray(` Nodes: ${snapshot.nodes.length}`));
2033
+ console.log(chalk.gray(` Edges: ${snapshot.edges.length}
3266
2034
  `));
3267
2035
  } catch (err) {
3268
2036
  spinner.fail(`Graph build failed: ${err.message}`);
@@ -3274,16 +2042,16 @@ graph.command("list").description("List saved dependency graph snapshots").optio
3274
2042
  try {
3275
2043
  const snapshots = await phase1.services.graphEngine.listSnapshots(cwd, parseInt(opts.limit, 10));
3276
2044
  if (snapshots.length === 0) {
3277
- console.log(chalk2.yellow("\n No graph snapshots found. Run: memory-core graph build\n"));
2045
+ console.log(chalk.yellow("\n No graph snapshots found. Run: memory-core graph build\n"));
3278
2046
  return;
3279
2047
  }
3280
- console.log(chalk2.bold("\n Graph snapshots\n"));
2048
+ console.log(chalk.bold("\n Graph snapshots\n"));
3281
2049
  snapshots.forEach((snapshot, index) => {
3282
- console.log(` ${index + 1}. ${snapshot.id} ${chalk2.dim(snapshot.createdAt)} nodes=${snapshot.nodes.length} edges=${snapshot.edges.length}`);
2050
+ console.log(` ${index + 1}. ${snapshot.id} ${chalk.dim(snapshot.createdAt)} nodes=${snapshot.nodes.length} edges=${snapshot.edges.length}`);
3283
2051
  });
3284
2052
  console.log();
3285
2053
  } catch (err) {
3286
- console.error(chalk2.red(`Graph list failed: ${err.message}`));
2054
+ console.error(chalk.red(`Graph list failed: ${err.message}`));
3287
2055
  process.exit(1);
3288
2056
  }
3289
2057
  });
@@ -3292,30 +2060,30 @@ graph.command("show [snapshotId]").description("Show a saved graph snapshot (lat
3292
2060
  try {
3293
2061
  const snapshot = snapshotId ? await phase1.services.graphEngine.getSnapshot(cwd, snapshotId) : await phase1.services.graphEngine.latest(cwd);
3294
2062
  if (!snapshot) {
3295
- console.log(chalk2.yellow("\n No matching graph snapshot found. Run: memory-core graph build\n"));
2063
+ console.log(chalk.yellow("\n No matching graph snapshot found. Run: memory-core graph build\n"));
3296
2064
  return;
3297
2065
  }
3298
- console.log(chalk2.bold(`
2066
+ console.log(chalk.bold(`
3299
2067
  Graph ${snapshot.id ?? "(latest)"}
3300
2068
  `));
3301
- console.log(chalk2.gray(` Root: ${snapshot.rootPath}`));
3302
- console.log(chalk2.gray(` Created: ${snapshot.createdAt ?? "unknown"}`));
3303
- console.log(chalk2.gray(` Nodes: ${snapshot.nodes.length}`));
3304
- console.log(chalk2.gray(` Edges: ${snapshot.edges.length}
2069
+ console.log(chalk.gray(` Root: ${snapshot.rootPath}`));
2070
+ console.log(chalk.gray(` Created: ${snapshot.createdAt ?? "unknown"}`));
2071
+ console.log(chalk.gray(` Nodes: ${snapshot.nodes.length}`));
2072
+ console.log(chalk.gray(` Edges: ${snapshot.edges.length}
3305
2073
  `));
3306
2074
  const limit = parseInt(opts.edges, 10);
3307
2075
  snapshot.edges.slice(0, limit).forEach((edge, index) => {
3308
- console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2076
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
3309
2077
  });
3310
2078
  if (snapshot.edges.length > limit) {
3311
- console.log(chalk2.dim(`
2079
+ console.log(chalk.dim(`
3312
2080
  \u2026 ${snapshot.edges.length - limit} more edges not shown
3313
2081
  `));
3314
2082
  } else {
3315
2083
  console.log();
3316
2084
  }
3317
2085
  } catch (err) {
3318
- console.error(chalk2.red(`Graph show failed: ${err.message}`));
2086
+ console.error(chalk.red(`Graph show failed: ${err.message}`));
3319
2087
  process.exit(1);
3320
2088
  }
3321
2089
  });
@@ -3324,37 +2092,37 @@ graph.command("diff <leftSnapshotId> [rightSnapshotId]").description("Diff two s
3324
2092
  try {
3325
2093
  const left = await phase1.services.graphEngine.getSnapshot(cwd, leftSnapshotId);
3326
2094
  if (!left) {
3327
- console.error(chalk2.red(`Left snapshot not found: ${leftSnapshotId}`));
2095
+ console.error(chalk.red(`Left snapshot not found: ${leftSnapshotId}`));
3328
2096
  process.exit(1);
3329
2097
  }
3330
2098
  const right = rightSnapshotId ? await phase1.services.graphEngine.getSnapshot(cwd, rightSnapshotId) : await phase1.services.graphEngine.latest(cwd);
3331
2099
  if (!right) {
3332
- console.error(chalk2.red(`Right snapshot not found${rightSnapshotId ? `: ${rightSnapshotId}` : ""}`));
2100
+ console.error(chalk.red(`Right snapshot not found${rightSnapshotId ? `: ${rightSnapshotId}` : ""}`));
3333
2101
  process.exit(1);
3334
2102
  }
3335
2103
  const diff = phase1.services.graphEngine.diffGraphs(left, right);
3336
- console.log(chalk2.bold("\n Graph diff\n"));
3337
- console.log(chalk2.gray(` Left: ${left.id}`));
3338
- console.log(chalk2.gray(` Right: ${right.id}`));
3339
- console.log(chalk2.green(` + Nodes: ${diff.addedNodes.length}`));
3340
- console.log(chalk2.red(` - Nodes: ${diff.removedNodes.length}`));
3341
- console.log(chalk2.green(` + Edges: ${diff.addedEdges.length}`));
3342
- console.log(chalk2.red(` - Edges: ${diff.removedEdges.length}`));
2104
+ console.log(chalk.bold("\n Graph diff\n"));
2105
+ console.log(chalk.gray(` Left: ${left.id}`));
2106
+ console.log(chalk.gray(` Right: ${right.id}`));
2107
+ console.log(chalk.green(` + Nodes: ${diff.addedNodes.length}`));
2108
+ console.log(chalk.red(` - Nodes: ${diff.removedNodes.length}`));
2109
+ console.log(chalk.green(` + Edges: ${diff.addedEdges.length}`));
2110
+ console.log(chalk.red(` - Edges: ${diff.removedEdges.length}`));
3343
2111
  if (diff.addedEdges.length > 0) {
3344
- console.log(chalk2.green("\n Added edges"));
2112
+ console.log(chalk.green("\n Added edges"));
3345
2113
  diff.addedEdges.slice(0, 20).forEach((edge, index) => {
3346
- console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2114
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
3347
2115
  });
3348
2116
  }
3349
2117
  if (diff.removedEdges.length > 0) {
3350
- console.log(chalk2.red("\n Removed edges"));
2118
+ console.log(chalk.red("\n Removed edges"));
3351
2119
  diff.removedEdges.slice(0, 20).forEach((edge, index) => {
3352
- console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2120
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
3353
2121
  });
3354
2122
  }
3355
2123
  console.log();
3356
2124
  } catch (err) {
3357
- console.error(chalk2.red(`Graph diff failed: ${err.message}`));
2125
+ console.error(chalk.red(`Graph diff failed: ${err.message}`));
3358
2126
  process.exit(1);
3359
2127
  }
3360
2128
  });
@@ -3366,15 +2134,68 @@ hook.command("install").description("Install pre-commit hook (advisory mode by d
3366
2134
  hook.command("uninstall").description("Remove the pre-commit hook").action(() => {
3367
2135
  uninstallHook();
3368
2136
  });
3369
- program.command("check").description("Check staged changes against architecture rules (used by pre-commit hook)").option("--staged", "Check git staged diff (default behaviour)").option("--ci", `Check CI diff using ${MEMORY_FILE}`).option("--all", "Check all tracked source files, including already-committed files").option("--path <dir>", "Directory to check for --all mode (default: current directory)").option("--commit-msg [file]", "Check commit message (defaults to .git/COMMIT_EDITMSG)").option("--verbose", "Show model and diff details").option("--debug", "Show prompt, diff, and raw model response").option("--fast", "Skip AI and memory retrieval; run deterministic checks only").action(async (opts) => {
2137
+ hook.command("bypass-prompt").description("Prompt developer for a bypass reason and save it as a rule (called automatically by hook)").action(async () => {
2138
+ if (!process.stdin.isTTY) return;
2139
+ const bypassStats = readBypassStats();
2140
+ const unrecorded = bypassStats.withoutReason;
2141
+ if (unrecorded > 0) {
2142
+ console.log(
2143
+ chalk.yellow(`
2144
+ \u26A0 ${unrecorded} bypass${unrecorded > 1 ? "es" : ""} in this project had no reason recorded \u2014 lost signal.`)
2145
+ );
2146
+ }
2147
+ const config = readProjectConfig();
2148
+ let reason = await input({
2149
+ message: chalk.yellow("Why are you bypassing the hook?"),
2150
+ default: ""
2151
+ });
2152
+ if (!reason.trim()) {
2153
+ reason = await input({
2154
+ message: chalk.dim(" Skipping loses signal. Enter a reason or leave blank to proceed:"),
2155
+ default: ""
2156
+ });
2157
+ }
2158
+ const hadReason = reason.trim().length > 0;
2159
+ const stats = recordBypass(hadReason);
2160
+ if (!hadReason) {
2161
+ console.log(
2162
+ chalk.dim(` Bypass recorded without reason. (${stats.withoutReason} total unrecorded \u2014 run: memory-core stats)
2163
+ `)
2164
+ );
2165
+ await closePool();
2166
+ return;
2167
+ }
2168
+ try {
2169
+ await phase1.services.memoryEngine.remember({
2170
+ type: "rule",
2171
+ scope: "project",
2172
+ architecture: config?.backendArchitecture ?? config?.frontendFramework,
2173
+ projectName: config?.projectName,
2174
+ content: reason.trim(),
2175
+ reason: "Captured automatically from hook bypass"
2176
+ });
2177
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
2178
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2179
+ console.log(chalk.green(` \u2713 Bypass reason saved: "${reason.trim()}"
2180
+ `));
2181
+ } catch {
2182
+ }
2183
+ await closePool();
2184
+ });
2185
+ program.command("check").description("Check staged changes against architecture rules (used by pre-commit hook)").option("--staged", "Check git staged diff (default behaviour)").option("--ci", `Check CI diff using ${MEMORY_FILE}`).option("--all", "Check all tracked source files, including already-committed files").option("--path <dir>", "Directory to check for --all mode (default: current directory)").option("--file <path>", "Check a specific file retroactively against all rules").option("--commit-msg [file]", "Check commit message (defaults to .git/COMMIT_EDITMSG)").option("--verbose", "Show model and diff details").option("--debug", "Show prompt, diff, and raw model response").option("--fast", "Skip AI and memory retrieval; run deterministic checks only").option("--dry-run", "Show what would be flagged without blocking the commit").action(async (opts) => {
2186
+ if (opts.file) {
2187
+ await checkFile(opts.file, { verbose: opts.verbose ?? false, debug: opts.debug ?? false, fast: opts.fast ?? false, dryRun: opts.dryRun ?? false });
2188
+ await closePool();
2189
+ return;
2190
+ }
3370
2191
  if (opts.commitMsg !== void 0) {
3371
- const msgFile = typeof opts.commitMsg === "string" ? opts.commitMsg : join3(process.cwd(), ".git", "COMMIT_EDITMSG");
2192
+ const msgFile = typeof opts.commitMsg === "string" ? opts.commitMsg : join(process.cwd(), ".git", "COMMIT_EDITMSG");
3372
2193
  await checkCommitMsg(msgFile, { verbose: opts.verbose ?? false, debug: opts.debug ?? false });
3373
2194
  await closePool();
3374
2195
  return;
3375
2196
  }
3376
2197
  if (opts.ci && opts.all) {
3377
- console.error(chalk2.red("\n Choose one mode: --ci or --all.\n"));
2198
+ console.error(chalk.red("\n Choose one mode: --ci or --all.\n"));
3378
2199
  process.exit(1);
3379
2200
  }
3380
2201
  if (opts.ci) {
@@ -3390,12 +2211,80 @@ program.command("check").description("Check staged changes against architecture
3390
2211
  if (summary.violations > 0) process.exit(1);
3391
2212
  return;
3392
2213
  }
3393
- await checkStaged({ verbose: opts.verbose ?? false, debug: opts.debug ?? false, fast: opts.fast ?? false });
2214
+ await checkStaged({ verbose: opts.verbose ?? false, debug: opts.debug ?? false, fast: opts.fast ?? false, dryRun: opts.dryRun ?? false });
2215
+ });
2216
+ var schema = program.command("schema").description("Manage schema alignment rules between TypeScript and Go files");
2217
+ schema.command("add").description("Track schema alignment between a TypeScript file and a Go file").requiredOption("--ts <file>", "Path to TypeScript file (relative to project root)").requiredOption("--go <file>", "Path to Go file (relative to project root)").action(async (opts) => {
2218
+ const config = readProjectConfig();
2219
+ const content = JSON.stringify({ tsFile: opts.ts, goFile: opts.go });
2220
+ const spinner = ora("Saving schema rule\u2026").start();
2221
+ try {
2222
+ await phase1.services.memoryEngine.remember({
2223
+ type: "schema",
2224
+ scope: "project",
2225
+ architecture: config?.backendArchitecture ?? config?.frontendFramework,
2226
+ projectName: config?.projectName,
2227
+ content,
2228
+ reason: "Schema alignment rule \u2014 TypeScript and Go structs must stay in sync"
2229
+ });
2230
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
2231
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2232
+ spinner.succeed(chalk.green(`Schema rule saved: ${opts.ts} \u2194 ${opts.go}`));
2233
+ } catch (err) {
2234
+ spinner.fail(`Failed: ${err.message}`);
2235
+ process.exit(1);
2236
+ }
2237
+ await closePool();
2238
+ });
2239
+ schema.command("list").description("List all schema alignment rules").action(async () => {
2240
+ const memories = await phase1.services.memoryEngine.list({ type: "schema", limit: 100 });
2241
+ if (memories.length === 0) {
2242
+ console.log(chalk.dim("\n No schema rules defined. Use: memory-core schema add --ts <file> --go <file>\n"));
2243
+ await closePool();
2244
+ return;
2245
+ }
2246
+ console.log(chalk.cyan(`
2247
+ ${memories.length} schema rule${memories.length > 1 ? "s" : ""}:
2248
+ `));
2249
+ for (const m of memories) {
2250
+ try {
2251
+ const rule = JSON.parse(m.content);
2252
+ console.log(chalk.white(` ${rule.tsFile}`) + chalk.dim(" \u2194 ") + chalk.white(rule.goFile));
2253
+ } catch {
2254
+ console.log(chalk.dim(` ${m.content}`));
2255
+ }
2256
+ }
2257
+ console.log();
2258
+ await closePool();
2259
+ });
2260
+ schema.command("check").description("Run schema alignment checks against all tracked file pairs").action(async () => {
2261
+ const app = getDefaultApplicationContainer();
2262
+ const violations = await findSchemaViolations({
2263
+ cwd: process.cwd(),
2264
+ memoryEngine: app.services.memoryEngine
2265
+ });
2266
+ if (violations.length === 0) {
2267
+ console.log(chalk.green("\n \u2713 All schema rules pass \u2014 TypeScript and Go are in sync.\n"));
2268
+ } else {
2269
+ console.log(chalk.red.bold(`
2270
+ \u2717 ${violations.length} schema violation${violations.length > 1 ? "s" : ""} found
2271
+ `));
2272
+ for (const v of violations) {
2273
+ console.log(chalk.bold(` ${v.file}`));
2274
+ console.log(chalk.yellow(" Rule: ") + v.rule);
2275
+ console.log(chalk.red(" Issue: ") + v.issue);
2276
+ if (v.suggestion) console.log(chalk.green(" Fix: ") + v.suggestion);
2277
+ console.log();
2278
+ }
2279
+ process.exit(1);
2280
+ }
2281
+ await closePool();
3394
2282
  });
3395
- program.command("watch").description("Watch source files and check violations in real-time on every save").option("--path <dir>", "Directory to watch (default: current directory)").option("--scan-on-start", "Run an initial full snapshot scan before watching file changes").option("--verbose", "Show diff size and model details per file").option("--debug", "Show prompt, diff, and raw model response").action(async (opts) => {
2283
+ program.command("watch").description("Watch source files and check violations in real-time on every save").option("--path <dir>", "Directory to watch (default: current directory)").option("--scan-on-start", "Run an initial full snapshot scan before watching file changes").option("--auto-fix", "Automatically rewrite files that violate rules using AI").option("--verbose", "Show diff size and model details per file").option("--debug", "Show prompt, diff, and raw model response").action(async (opts) => {
3396
2284
  await phase1.providers.watchService.start({
3397
2285
  path: opts.path,
3398
2286
  scanOnStart: opts.scanOnStart,
2287
+ autoFix: opts.autoFix,
3399
2288
  verbose: opts.verbose,
3400
2289
  debug: opts.debug
3401
2290
  });