@shahmilsaari/memory-core 1.0.22 → 1.0.26

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,53 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AGENT_NAMES,
4
+ MEMORY_FILE,
4
5
  OUTPUT_FILES,
5
- buildContextQuery,
6
- callChatModel,
7
- closePool,
6
+ checkCi,
7
+ checkCommitMsg,
8
+ checkFile,
9
+ checkStaged,
8
10
  detectProject,
9
- findAstDeterministicViolationsForDiff,
11
+ findSchemaViolations,
10
12
  generate,
11
13
  getAllowPatterns,
12
- getChatProviderLabel,
13
14
  getDefaultApplicationContainer,
14
- getPool,
15
15
  inferProjectArchitectures,
16
+ installHook,
16
17
  listProfiles,
17
18
  migrateGraphSnapshots,
19
+ parseMemoryFile,
18
20
  probeGraphSnapshotStore,
19
- retrieveContextualMemories,
21
+ readBypassStats,
22
+ readMemoryFile,
23
+ readMemoryFileFromUrl,
24
+ recordBypass,
20
25
  retrieveMemorySelection,
21
- runMigrations,
22
- seeds
23
- } from "./chunk-UZDALJVQ.js";
26
+ seeds,
27
+ toPortableMemory,
28
+ uninstallHook,
29
+ writeMemoryFile
30
+ } from "./chunk-3XTHE74V.js";
31
+ import "./chunk-PQBWHAZN.js";
32
+ import {
33
+ closePool,
34
+ getPool,
35
+ runMigrations
36
+ } from "./chunk-M7NKSXFS.js";
37
+ import "./chunk-ZZBQEXEO.js";
24
38
 
25
39
  // src/cli.ts
26
40
  import { Command } from "commander";
27
41
  import { input, select, confirm } from "@inquirer/prompts";
28
- import chalk2 from "chalk";
42
+ import chalk from "chalk";
29
43
  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";
44
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync, rmSync } from "fs";
45
+ import { join, dirname, resolve } from "path";
32
46
  import { homedir } from "os";
33
-
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
- }
47
+ import { spawnSync as spawnSync2 } from "child_process";
1288
48
 
1289
49
  // src/remote-install.ts
1290
- import { spawnSync as spawnSync2 } from "child_process";
50
+ import { spawnSync } from "child_process";
1291
51
  var CAVEMAN_INSTALL_URL = "https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh";
1292
52
  var MAX_INSTALLER_BYTES = 2e5;
1293
53
  var TRUSTED_INSTALL_HOSTS = /* @__PURE__ */ new Set(["raw.githubusercontent.com"]);
@@ -1301,7 +61,7 @@ function assertTrustedInstallerUrl(url) {
1301
61
  }
1302
62
  }
1303
63
  function defaultRunScript(script) {
1304
- return spawnSync2("bash", ["-s"], {
64
+ return spawnSync("bash", ["-s"], {
1305
65
  input: script,
1306
66
  encoding: "utf-8",
1307
67
  stdio: ["pipe", "pipe", "pipe"]
@@ -1337,38 +97,38 @@ async function installCavemanTokenSaver(options = {}) {
1337
97
 
1338
98
  // src/cli.ts
1339
99
  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;
100
+ 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");
101
+ 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
102
  const lines = [
1343
103
  "",
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"),
104
+ 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 "),
105
+ 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"),
106
+ 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"),
107
+ 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"),
108
+ 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"),
109
+ 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
110
  "",
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"),
111
+ 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
112
  "",
1353
- chalk2.green(` \u2713 Project `) + chalk2.bold(projectName),
1354
- chalk2.green(` \u2713 Agents `) + chalk2.bold(`${agentCount} AI agents configured`),
113
+ chalk.green(` \u2713 Project `) + chalk.bold(projectName),
114
+ chalk.green(` \u2713 Agents `) + chalk.bold(`${agentCount} AI agents configured`),
1355
115
  pg,
1356
116
  ...ol ? [ol] : [],
1357
117
  "",
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"),
118
+ 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"),
119
+ chalk.dim(" Built by ") + chalk.bold.white("Shahmil Saari"),
1360
120
  "",
1361
- chalk2.bold(" Every AI agent in this project now follows your rules."),
121
+ chalk.bold(" Every AI agent in this project now follows your rules."),
1362
122
  "",
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"),
123
+ chalk.gray(" Next steps:"),
124
+ chalk.gray(' memory-core remember "Your architectural decision"'),
125
+ chalk.gray(' memory-core search "query"'),
126
+ chalk.gray(" memory-core sync"),
1367
127
  ""
1368
128
  ];
1369
129
  lines.forEach((l) => console.log(l));
1370
130
  }
1371
- var { version } = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf-8"));
131
+ var { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
1372
132
  var CONFIG_FILE = ".memory-core.json";
1373
133
  var LOCAL_GENERATED_FILES = [".memory-core-stats.json"];
1374
134
  var LOCAL_STATE_FILES = [CONFIG_FILE, ".memory-core.env", ...LOCAL_GENERATED_FILES];
@@ -1379,13 +139,10 @@ var DEFAULT_EMBEDDING_MODEL = "nomic-embed-text";
1379
139
  var DEFAULT_CHAT_MODEL = "llama3.2";
1380
140
  var phase1 = getDefaultApplicationContainer();
1381
141
  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;
142
+ return join(process.cwd(), ".memory-core.env");
1386
143
  }
1387
144
  function getWriteEnvPath() {
1388
- return join3(process.cwd(), ".memory-core.env");
145
+ return join(process.cwd(), ".memory-core.env");
1389
146
  }
1390
147
  function parseEnvFile(raw) {
1391
148
  const lines = raw.split(/\r?\n/);
@@ -1403,7 +160,7 @@ function parseEnvFile(raw) {
1403
160
  }
1404
161
  function readRuntimeEnv() {
1405
162
  const envPath = getEnvPath();
1406
- const fileValues = existsSync3(envPath) ? parseEnvFile(readFileSync3(envPath, "utf-8")) : {};
163
+ const fileValues = existsSync(envPath) ? parseEnvFile(readFileSync(envPath, "utf-8")) : {};
1407
164
  const values = {
1408
165
  ...fileValues
1409
166
  };
@@ -1437,7 +194,7 @@ function writeRuntimeEnv(values, envPath = getWriteEnvPath()) {
1437
194
  const value = values[key];
1438
195
  if (value) lines.push(`${key}=${value}`);
1439
196
  }
1440
- writeFileSync3(envPath, `${lines.join("\n")}
197
+ writeFileSync(envPath, `${lines.join("\n")}
1441
198
  `, "utf-8");
1442
199
  }
1443
200
  function applyRuntimeEnv(values) {
@@ -1446,8 +203,8 @@ function applyRuntimeEnv(values) {
1446
203
  }
1447
204
  }
1448
205
  function appendMissingGitignoreEntries(entries, heading) {
1449
- const gitignorePath = join3(process.cwd(), ".gitignore");
1450
- const existing = existsSync3(gitignorePath) ? readFileSync3(gitignorePath, "utf-8") : "";
206
+ const gitignorePath = join(process.cwd(), ".gitignore");
207
+ const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : "";
1451
208
  const existingEntries = new Set(
1452
209
  existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
1453
210
  );
@@ -1462,9 +219,9 @@ ${toAdd.join("\n")}
1462
219
  return toAdd.length;
1463
220
  }
1464
221
  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");
222
+ const gitignorePath = join(process.cwd(), ".gitignore");
223
+ if (!existsSync(gitignorePath)) return false;
224
+ const existing = readFileSync(gitignorePath, "utf-8");
1468
225
  const entrySet = new Set(entries);
1469
226
  const lines = existing.split(/\r?\n/);
1470
227
  const kept = [];
@@ -1493,15 +250,15 @@ function removeMemoryCoreGitignoreBlock(entries, heading = GITIGNORE_HEADING) {
1493
250
  }
1494
251
  if (!changed) return false;
1495
252
  const content = kept.join("\n").replace(/\n{3,}/g, "\n\n").replace(/\s+$/u, "");
1496
- writeFileSync3(gitignorePath, content ? `${content}
253
+ writeFileSync(gitignorePath, content ? `${content}
1497
254
  ` : "", "utf-8");
1498
255
  return true;
1499
256
  }
1500
257
  function removeProjectFiles(relativePaths) {
1501
258
  const removed = [];
1502
259
  for (const relativePath of [...new Set(relativePaths)]) {
1503
- const target = join3(process.cwd(), relativePath);
1504
- if (!existsSync3(target)) continue;
260
+ const target = join(process.cwd(), relativePath);
261
+ if (!existsSync(target)) continue;
1505
262
  rmSync(target, { force: true, recursive: true });
1506
263
  removed.push(relativePath);
1507
264
  }
@@ -1568,16 +325,16 @@ async function verifyOllamaConnection(ollamaUrl) {
1568
325
  }
1569
326
  }
1570
327
  function readProjectConfig() {
1571
- const path = join3(process.cwd(), CONFIG_FILE);
1572
- if (!existsSync3(path)) return null;
328
+ const path = join(process.cwd(), CONFIG_FILE);
329
+ if (!existsSync(path)) return null;
1573
330
  try {
1574
- return JSON.parse(readFileSync3(path, "utf-8"));
331
+ return JSON.parse(readFileSync(path, "utf-8"));
1575
332
  } catch {
1576
333
  return null;
1577
334
  }
1578
335
  }
1579
336
  function writeProjectConfig(config) {
1580
- writeFileSync3(join3(process.cwd(), CONFIG_FILE), JSON.stringify(config, null, 2));
337
+ writeFileSync(join(process.cwd(), CONFIG_FILE), JSON.stringify(config, null, 2));
1581
338
  }
1582
339
  function updateProjectConfig(mutator) {
1583
340
  const current = readProjectConfig() ?? {
@@ -1649,11 +406,11 @@ function toPortableFromRecord(memory) {
1649
406
  });
1650
407
  }
1651
408
  function printMemoryTable(memories, title = "Rules in memory") {
1652
- console.log(chalk2.bold(`
409
+ console.log(chalk.bold(`
1653
410
  ${title} (${memories.length} total)
1654
411
  `));
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"));
412
+ console.log(chalk.dim(" ID Type Scope Title / Content"));
413
+ 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
414
  memories.forEach((memory) => {
1658
415
  const id = String(memory.id).padEnd(4);
1659
416
  const type = memory.type.padEnd(10);
@@ -1661,19 +418,19 @@ function printMemoryTable(memories, title = "Rules in memory") {
1661
418
  const label = truncate(memory.title || memory.content, 64);
1662
419
  console.log(` ${id} ${type} ${scope} ${label}`);
1663
420
  });
1664
- console.log(chalk2.gray("\n Use: memory-core remove <id> | memory-core edit <id>\n"));
421
+ console.log(chalk.gray("\n Use: memory-core remove <id> | memory-core edit <id>\n"));
1665
422
  }
1666
423
  function getCurrentListArchitectures(config) {
1667
424
  return inferProjectArchitectures(process.cwd(), config).filter((architecture) => architecture !== "global");
1668
425
  }
1669
426
  function printStatusLine(label, value) {
1670
- console.log(` ${chalk2.dim(label.padEnd(18))} ${value}`);
427
+ console.log(` ${chalk.dim(label.padEnd(18))} ${value}`);
1671
428
  }
1672
429
  function abbreviate(value, max = 96) {
1673
430
  return value.length > max ? `${value.slice(0, max - 1)}\u2026` : value;
1674
431
  }
1675
432
  function graphStoreFilePath(cwd = process.cwd()) {
1676
- return join3(cwd, ".memory-core", "graph-snapshots.json");
433
+ return join(cwd, ".memory-core", "graph-snapshots.json");
1677
434
  }
1678
435
  function graphBackendLabel(values) {
1679
436
  return values.DATABASE_URL ? "postgres (file fallback enabled)" : "file";
@@ -1693,8 +450,8 @@ async function runModelDoctor() {
1693
450
  const ollamaUrl = values.OLLAMA_URL ?? DEFAULT_OLLAMA_URL;
1694
451
  const embeddingModel = values.OLLAMA_MODEL ?? DEFAULT_EMBEDDING_MODEL;
1695
452
  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)")}`);
453
+ console.log(chalk.bold("\n memory-core model doctor\n"));
454
+ printStatusLine("Env file", existsSync(envPath) ? envPath : `${envPath} ${chalk.yellow("(will be created on first write)")}`);
1698
455
  printStatusLine("Provider", provider2);
1699
456
  printStatusLine("Chat model", model2);
1700
457
  printStatusLine("Embedding model", embeddingModel);
@@ -1704,56 +461,56 @@ async function runModelDoctor() {
1704
461
  const dbError = await verifyDatabaseConnection(dbUrl);
1705
462
  if (dbError) {
1706
463
  ok = false;
1707
- console.log(chalk2.red(" \u2717 PostgreSQL ") + chalk2.dim(dbError));
464
+ console.log(chalk.red(" \u2717 PostgreSQL ") + chalk.dim(dbError));
1708
465
  } else {
1709
- console.log(chalk2.green(" \u2713 PostgreSQL ") + chalk2.dim("connected"));
466
+ console.log(chalk.green(" \u2713 PostgreSQL ") + chalk.dim("connected"));
1710
467
  }
1711
468
  const ollamaError = await verifyOllamaConnection(ollamaUrl);
1712
469
  if (ollamaError) {
1713
470
  ok = false;
1714
- console.log(chalk2.red(" \u2717 Ollama ") + chalk2.dim(ollamaError));
471
+ console.log(chalk.red(" \u2717 Ollama ") + chalk.dim(ollamaError));
1715
472
  } else {
1716
- console.log(chalk2.green(" \u2713 Ollama ") + chalk2.dim("reachable"));
473
+ console.log(chalk.green(" \u2713 Ollama ") + chalk.dim("reachable"));
1717
474
  }
1718
475
  if (!ollamaError) {
1719
476
  try {
1720
477
  const installedEmbeddingModel = await resolveOllamaInstalledModel(ollamaUrl, embeddingModel);
1721
478
  if (installedEmbeddingModel) {
1722
- console.log(chalk2.green(" \u2713 Embedding ") + chalk2.dim(`${installedEmbeddingModel} installed`));
479
+ console.log(chalk.green(" \u2713 Embedding ") + chalk.dim(`${installedEmbeddingModel} installed`));
1723
480
  } else {
1724
481
  ok = false;
1725
- console.log(chalk2.red(" \u2717 Embedding ") + chalk2.dim(`${embeddingModel} not installed in Ollama`));
482
+ console.log(chalk.red(" \u2717 Embedding ") + chalk.dim(`${embeddingModel} not installed in Ollama`));
1726
483
  }
1727
484
  } catch (err) {
1728
485
  ok = false;
1729
- console.log(chalk2.red(" \u2717 Embedding ") + chalk2.dim(err.message));
486
+ console.log(chalk.red(" \u2717 Embedding ") + chalk.dim(err.message));
1730
487
  }
1731
488
  }
1732
489
  if (provider2 === "ollama") {
1733
490
  if (ollamaError) {
1734
491
  ok = false;
1735
- console.log(chalk2.red(" \u2717 Chat model ") + chalk2.dim("cannot verify while Ollama is unreachable"));
492
+ console.log(chalk.red(" \u2717 Chat model ") + chalk.dim("cannot verify while Ollama is unreachable"));
1736
493
  } else {
1737
494
  try {
1738
495
  const installedChatModel = await resolveOllamaInstalledModel(ollamaUrl, model2);
1739
496
  if (installedChatModel) {
1740
- console.log(chalk2.green(" \u2713 Chat model ") + chalk2.dim(`${installedChatModel} installed`));
497
+ console.log(chalk.green(" \u2713 Chat model ") + chalk.dim(`${installedChatModel} installed`));
1741
498
  } else {
1742
499
  ok = false;
1743
- console.log(chalk2.red(" \u2717 Chat model ") + chalk2.dim(`${model2} not installed in Ollama`));
500
+ console.log(chalk.red(" \u2717 Chat model ") + chalk.dim(`${model2} not installed in Ollama`));
1744
501
  }
1745
502
  } catch (err) {
1746
503
  ok = false;
1747
- console.log(chalk2.red(" \u2717 Chat model ") + chalk2.dim(err.message));
504
+ console.log(chalk.red(" \u2717 Chat model ") + chalk.dim(err.message));
1748
505
  }
1749
506
  }
1750
507
  } else {
1751
508
  if (!values.CHAT_API_KEY) {
1752
509
  ok = false;
1753
- console.log(chalk2.red(` \u2717 ${providerLabel(provider2)} API`) + chalk2.dim(" CHAT_API_KEY is missing"));
510
+ console.log(chalk.red(` \u2717 ${providerLabel(provider2)} API`) + chalk.dim(" CHAT_API_KEY is missing"));
1754
511
  } 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."));
512
+ console.log(chalk.green(` \u2713 ${providerLabel(provider2)} API`) + chalk.dim(" key configured"));
513
+ console.log(chalk.gray(" Remote provider connectivity is not verified live by doctor."));
1757
514
  }
1758
515
  }
1759
516
  console.log();
@@ -1765,33 +522,33 @@ async function printProjectStatus() {
1765
522
  const provider2 = getConfiguredProvider(values);
1766
523
  const model2 = getConfiguredChatModel(values);
1767
524
  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");
525
+ const generatedFiles = OUTPUT_FILES.map((entry) => entry.path).filter((relativePath) => existsSync(join(process.cwd(), relativePath)));
526
+ const hookPath = join(process.cwd(), ".git", "hooks", "pre-commit");
527
+ const memoryFilePath = join(process.cwd(), MEMORY_FILE);
528
+ const statsPath = join(process.cwd(), ".memory-core-stats.json");
1772
529
  const dbError = await verifyDatabaseConnection(values.DATABASE_URL ?? "");
1773
530
  const ollamaError = await verifyOllamaConnection(values.OLLAMA_URL ?? DEFAULT_OLLAMA_URL);
1774
531
  const graphCount = await getGraphSnapshotCount(process.cwd());
1775
- console.log(chalk2.bold("\n memory-core status\n"));
532
+ console.log(chalk.bold("\n memory-core status\n"));
1776
533
  printStatusLine("Project", config?.projectName ?? process.cwd().split("/").pop() ?? "unknown");
1777
- printStatusLine("Project type", config?.projectType ?? chalk2.yellow("not initialized"));
534
+ printStatusLine("Project type", config?.projectType ?? chalk.yellow("not initialized"));
1778
535
  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"));
536
+ printStatusLine("Backend arch", config?.backendArchitecture ?? chalk.gray("\u2014"));
537
+ printStatusLine("Frontend fw", config?.frontendFramework ?? chalk.gray("\u2014"));
538
+ printStatusLine("Architectures", architectures.length ? architectures.join(", ") : chalk.gray("none detected"));
539
+ printStatusLine("Agents", config?.agents?.length ? `${config.agents.length} selected` : chalk.gray("none saved"));
1783
540
  printStatusLine("Caveman", config?.caveman?.enabled ? `enabled (${config.caveman.intensity})` : "disabled");
1784
541
  printStatusLine("Auto sync", config?.autoSync === false ? "disabled" : "enabled");
1785
542
  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"));
543
+ printStatusLine("Env file", `${existsSync(envPath) ? "present" : "missing"} (${envPath.split("/").pop()})`);
544
+ printStatusLine("Memory file", existsSync(memoryFilePath) ? MEMORY_FILE : chalk.gray("not exported"));
545
+ printStatusLine("Project config", existsSync(join(process.cwd(), CONFIG_FILE)) ? CONFIG_FILE : chalk.gray("missing"));
1789
546
  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"));
547
+ printStatusLine("Hook", existsSync(hookPath) ? "installed" : "not installed");
548
+ printStatusLine("Stats file", existsSync(statsPath) ? ".memory-core-stats.json" : chalk.gray("none"));
1792
549
  printStatusLine("Graph backend", graphBackendLabel(values));
1793
550
  printStatusLine("Graph store", graphStoreFilePath(process.cwd()));
1794
- printStatusLine("Graph snapshots", graphCount === null ? chalk2.gray("unavailable") : String(graphCount));
551
+ printStatusLine("Graph snapshots", graphCount === null ? chalk.gray("unavailable") : String(graphCount));
1795
552
  console.log();
1796
553
  printStatusLine("Database URL", redactDatabaseUrl(values.DATABASE_URL ?? ""));
1797
554
  printStatusLine("Ollama URL", values.OLLAMA_URL ?? DEFAULT_OLLAMA_URL);
@@ -1800,38 +557,38 @@ async function printProjectStatus() {
1800
557
  printStatusLine("Chat model", model2);
1801
558
  console.log();
1802
559
  console.log(
1803
- dbError ? chalk2.red(" \u2717 PostgreSQL ") + chalk2.dim(dbError) : chalk2.green(" \u2713 PostgreSQL ") + chalk2.dim("connected")
560
+ dbError ? chalk.red(" \u2717 PostgreSQL ") + chalk.dim(dbError) : chalk.green(" \u2713 PostgreSQL ") + chalk.dim("connected")
1804
561
  );
1805
562
  console.log(
1806
- ollamaError ? chalk2.red(" \u2717 Ollama ") + chalk2.dim(ollamaError) : chalk2.green(" \u2713 Ollama ") + chalk2.dim("reachable")
563
+ ollamaError ? chalk.red(" \u2717 Ollama ") + chalk.dim(ollamaError) : chalk.green(" \u2713 Ollama ") + chalk.dim("reachable")
1807
564
  );
1808
565
  if (provider2 !== "ollama") {
1809
566
  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")
567
+ 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
568
  );
1812
569
  }
1813
570
  console.log();
1814
571
  }
1815
572
  function printMemorySelection(selection, limit = 4) {
1816
573
  const active = selection.activeArchitectures.join(", ") || "none detected";
1817
- console.log(chalk2.gray(` Stack filter: ${active}`));
574
+ console.log(chalk.gray(` Stack filter: ${active}`));
1818
575
  const included = selection.decisions.filter((decision) => decision.status === "included");
1819
576
  if (included.length > 0) {
1820
- console.log(chalk2.gray(` Included ${included.length}:`));
577
+ console.log(chalk.gray(` Included ${included.length}:`));
1821
578
  for (const decision of included.slice(0, limit)) {
1822
- console.log(chalk2.gray(` + ${decision.memory.content} (${decision.reason})`));
579
+ console.log(chalk.gray(` + ${decision.memory.content} (${decision.reason})`));
1823
580
  }
1824
581
  if (included.length > limit) {
1825
- console.log(chalk2.gray(` \u2026 ${included.length - limit} more included`));
582
+ console.log(chalk.gray(` \u2026 ${included.length - limit} more included`));
1826
583
  }
1827
584
  }
1828
585
  if (selection.excluded.length > 0) {
1829
- console.log(chalk2.gray(` Excluded ${selection.excluded.length}:`));
586
+ console.log(chalk.gray(` Excluded ${selection.excluded.length}:`));
1830
587
  for (const decision of selection.excluded.slice(0, limit)) {
1831
- console.log(chalk2.gray(` - ${decision.memory.content} (${decision.reason})`));
588
+ console.log(chalk.gray(` - ${decision.memory.content} (${decision.reason})`));
1832
589
  }
1833
590
  if (selection.excluded.length > limit) {
1834
- console.log(chalk2.gray(` \u2026 ${selection.excluded.length - limit} more excluded`));
591
+ console.log(chalk.gray(` \u2026 ${selection.excluded.length - limit} more excluded`));
1835
592
  }
1836
593
  }
1837
594
  }
@@ -1873,22 +630,22 @@ async function syncGeneratedFiles(config, agents, options = {}) {
1873
630
  agents
1874
631
  );
1875
632
  spinner.succeed(
1876
- `Synced \u2014 ${chalk2.green(`${result.written.length} updated`)}, ${chalk2.dim(`${result.skipped.length} already up to date`)}`
633
+ `Synced \u2014 ${chalk.green(`${result.written.length} updated`)}, ${chalk.dim(`${result.skipped.length} already up to date`)}`
1877
634
  );
1878
635
  if (result.written.length > 0) {
1879
- result.written.forEach((file) => console.log(chalk2.gray(` \u2713 ${file}`)));
636
+ result.written.forEach((file) => console.log(chalk.gray(` \u2713 ${file}`)));
1880
637
  }
1881
638
  }
1882
639
  async function autoSyncGeneratedFiles(config, action, enabled = true) {
1883
640
  if (!enabled) {
1884
- console.log(chalk2.gray(" Auto-sync skipped (--no-sync). Run memory-core sync when ready."));
641
+ console.log(chalk.gray(" Auto-sync skipped (--no-sync). Run memory-core sync when ready."));
1885
642
  return;
1886
643
  }
1887
644
  if (!config) {
1888
645
  return;
1889
646
  }
1890
647
  if (config.autoSync === false) {
1891
- console.log(chalk2.gray(" Auto-sync disabled for this project. Run memory-core sync when ready."));
648
+ console.log(chalk.gray(" Auto-sync disabled for this project. Run memory-core sync when ready."));
1892
649
  return;
1893
650
  }
1894
651
  try {
@@ -1896,24 +653,24 @@ async function autoSyncGeneratedFiles(config, action, enabled = true) {
1896
653
  label: `Auto-syncing agent files after ${action}\u2026`
1897
654
  });
1898
655
  } catch (err) {
1899
- console.log(chalk2.yellow(` Auto-sync skipped: ${err.message}`));
1900
- console.log(chalk2.gray(" Run memory-core sync manually when ready."));
656
+ console.log(chalk.yellow(` Auto-sync skipped: ${err.message}`));
657
+ console.log(chalk.gray(" Run memory-core sync manually when ready."));
1901
658
  }
1902
659
  }
1903
660
  var program = new Command();
1904
661
  program.name("memory-core").description("Universal AI memory core \u2014 generate AI context files for all coding agents").version(version);
1905
662
  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"));
663
+ console.log(chalk.bold.cyan("\n memory-core init\n"));
1907
664
  const detected = detectProject();
1908
665
  const quick = opts.quick ?? false;
1909
666
  let skipEnv = false;
1910
667
  let skipProject = false;
1911
- if (existsSync3(join3(process.cwd(), CONFIG_FILE)) && !quick) {
668
+ if (existsSync(join(process.cwd(), CONFIG_FILE)) && !quick) {
1912
669
  const existing = readProjectConfig();
1913
670
  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}
671
+ console.log(chalk.dim(` Already initialized: ${existing?.projectName ?? "?"} (${existing?.projectType ?? "?"})`));
672
+ console.log(chalk.dim(` Provider: ${envVals.CHAT_PROVIDER ?? "ollama"} Model: ${envVals.CHAT_MODEL ?? "llama3.2"}`));
673
+ console.log(chalk.dim(` Hook: ${existsSync(join(".git", "hooks", "pre-commit")) ? "installed" : "not installed"} Agents: ${existing?.agents?.length ?? 0}
1917
674
  `));
1918
675
  const reinitChoice = await select({
1919
676
  message: "Already initialized \u2014 what do you want to do?",
@@ -1933,8 +690,8 @@ program.command("init").description("Initialize memory-core in the current proje
1933
690
  }
1934
691
  let pgOk = false;
1935
692
  let ollamaOk = false;
1936
- const envPath = join3(process.cwd(), ".memory-core.env");
1937
- const hasEnv = existsSync3(envPath);
693
+ const envPath = join(process.cwd(), ".memory-core.env");
694
+ const hasEnv = existsSync(envPath);
1938
695
  if (skipEnv) {
1939
696
  try {
1940
697
  const { Pool } = (await import("pg")).default;
@@ -1964,9 +721,9 @@ program.command("init").description("Initialize memory-core in the current proje
1964
721
  writeRuntimeEnv(envValues, envPath);
1965
722
  applyRuntimeEnv(envValues);
1966
723
  appendMissingGitignoreEntries(LOCAL_STATE_FILES, GITIGNORE_HEADING);
1967
- console.log(chalk2.green(" \u2713 .memory-core.env created with local defaults"));
724
+ console.log(chalk.green(" \u2713 .memory-core.env created with local defaults"));
1968
725
  } else if (!hasEnv) {
1969
- console.log(chalk2.dim(" No .memory-core.env found \u2014 let's set up your connection.\n"));
726
+ console.log(chalk.dim(" No .memory-core.env found \u2014 let's set up your connection.\n"));
1970
727
  const dbUser = process.env.USER ?? process.env.USERNAME ?? "postgres";
1971
728
  let dbUrl = "";
1972
729
  while (true) {
@@ -1980,12 +737,12 @@ program.command("init").description("Initialize memory-core in the current proje
1980
737
  const testPool = new Pool({ connectionString: dbUrl, connectionTimeoutMillis: 5e3 });
1981
738
  await testPool.query("SELECT 1");
1982
739
  await testPool.end();
1983
- pgSpinner.succeed(chalk2.green("PostgreSQL connected"));
740
+ pgSpinner.succeed(chalk.green("PostgreSQL connected"));
1984
741
  pgOk = true;
1985
742
  break;
1986
743
  } 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"));
744
+ pgSpinner.fail(chalk.red(`Cannot connect: ${err.message}`));
745
+ console.log(chalk.yellow(" Please check that PostgreSQL is running and the URL is correct.\n"));
1989
746
  }
1990
747
  }
1991
748
  let ollamaUrl = "";
@@ -1998,12 +755,12 @@ program.command("init").description("Initialize memory-core in the current proje
1998
755
  try {
1999
756
  const res = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(5e3) });
2000
757
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
2001
- ollamaSpinner.succeed(chalk2.green("Ollama connected"));
758
+ ollamaSpinner.succeed(chalk.green("Ollama connected"));
2002
759
  ollamaOk = true;
2003
760
  break;
2004
761
  } 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"));
762
+ ollamaSpinner.fail(chalk.red(`Cannot reach Ollama: ${err.message}`));
763
+ console.log(chalk.yellow(" Make sure Ollama is running: ollama serve\n"));
2007
764
  }
2008
765
  }
2009
766
  const chatProvider = await select({
@@ -2017,7 +774,7 @@ program.command("init").description("Initialize memory-core in the current proje
2017
774
  ]
2018
775
  });
2019
776
  if (chatProvider !== "ollama") {
2020
- console.log(chalk2.dim(" Note: Ollama is still used for search embeddings. Code checking uses the cloud provider above."));
777
+ console.log(chalk.dim(" Note: Ollama is still used for search embeddings. Code checking uses the cloud provider above."));
2021
778
  }
2022
779
  let chatModel = "";
2023
780
  let chatApiKey = "";
@@ -2045,15 +802,15 @@ program.command("init").description("Initialize memory-core in the current proje
2045
802
  const match = exact ?? prefixed;
2046
803
  if (match) {
2047
804
  chatModel = match.name;
2048
- modelSpinner.succeed(chalk2.green(`${chatModel} is installed and ready`));
805
+ modelSpinner.succeed(chalk.green(`${chatModel} is installed and ready`));
2049
806
  break;
2050
807
  } 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.
808
+ modelSpinner.fail(chalk.red(`${chatModel} is not installed in your Ollama`));
809
+ console.log(chalk.yellow(` Run: ollama pull ${chatModel} \u2014 or pick a different model.
2053
810
  `));
2054
811
  }
2055
812
  } catch {
2056
- modelSpinner.warn(chalk2.yellow("Could not verify model \u2014 continuing anyway"));
813
+ modelSpinner.warn(chalk.yellow("Could not verify model \u2014 continuing anyway"));
2057
814
  break;
2058
815
  }
2059
816
  }
@@ -2097,7 +854,7 @@ program.command("init").description("Initialize memory-core in the current proje
2097
854
  chatApiKey = await input({
2098
855
  message: `${providerLabel(chatProvider)} API key?`
2099
856
  });
2100
- console.log(chalk2.green(` \u2713 ${chatProvider} / ${chatModel} configured`));
857
+ console.log(chalk.green(` \u2713 ${chatProvider} / ${chatModel} configured`));
2101
858
  }
2102
859
  const envValues = {
2103
860
  DATABASE_URL: dbUrl,
@@ -2112,8 +869,8 @@ program.command("init").description("Initialize memory-core in the current proje
2112
869
  writeRuntimeEnv(envValues, envPath);
2113
870
  applyRuntimeEnv(envValues);
2114
871
  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"));
872
+ console.log(chalk.green("\n \u2713 .memory-core.env created"));
873
+ console.log(chalk.gray(" Added to .gitignore \u2014 your DB credentials stay local.\n"));
2117
874
  } else {
2118
875
  try {
2119
876
  const { Pool } = (await import("pg")).default;
@@ -2150,10 +907,13 @@ program.command("init").description("Initialize memory-core in the current proje
2150
907
  const backendProfiles = listProfiles("backend");
2151
908
  backendArchitecture = await select({
2152
909
  message: "Backend architecture?",
2153
- choices: backendProfiles.map((p) => ({
2154
- value: p.name,
2155
- name: `${p.displayName} \u2014 ${p.description}`
2156
- }))
910
+ choices: [
911
+ ...backendProfiles.map((p) => ({
912
+ value: p.name,
913
+ name: `${p.displayName} \u2014 ${p.description}`
914
+ })),
915
+ { value: "custom", name: "Custom / Not defined yet \u2014 I'll add rules with memory-core remember" }
916
+ ]
2157
917
  });
2158
918
  }
2159
919
  }
@@ -2171,10 +931,13 @@ program.command("init").description("Initialize memory-core in the current proje
2171
931
  const frontendProfiles = listProfiles("frontend");
2172
932
  frontendFramework = await select({
2173
933
  message: "Frontend framework?",
2174
- choices: frontendProfiles.map((p) => ({
2175
- value: p.name,
2176
- name: `${p.displayName} \u2014 ${p.description}`
2177
- }))
934
+ choices: [
935
+ ...frontendProfiles.map((p) => ({
936
+ value: p.name,
937
+ name: `${p.displayName} \u2014 ${p.description}`
938
+ })),
939
+ { value: "custom", name: "Custom / Not defined yet \u2014 I'll add rules with memory-core remember" }
940
+ ]
2178
941
  });
2179
942
  }
2180
943
  }
@@ -2230,18 +993,18 @@ program.command("init").description("Initialize memory-core in the current proje
2230
993
  }
2231
994
  if (!quick) {
2232
995
  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")}`);
996
+ console.log(chalk.bold("\n Ready to initialize\n"));
997
+ console.log(` Project ${chalk.white(projectName)} (${projectType})`);
998
+ if (backendArchitecture) console.log(` Backend ${chalk.white(backendArchitecture === "custom" ? "Custom (no profile)" : backendArchitecture)}`);
999
+ if (frontendFramework) console.log(` Frontend ${chalk.white(frontendFramework === "custom" ? "Custom (no profile)" : frontendFramework)}`);
1000
+ console.log(` Language ${chalk.white(language)}`);
1001
+ console.log(` Provider ${chalk.white(envVals.CHAT_PROVIDER ?? "ollama")} / ${chalk.white(envVals.CHAT_MODEL ?? DEFAULT_CHAT_MODEL)}`);
1002
+ console.log(` Agents ${chalk.white(String(selectedAgents.length))} selected`);
1003
+ console.log(` Hook ${chalk.white(enableHook ? hookAdvisory ? "advisory" : "strict" : "skip")}`);
2241
1004
  console.log();
2242
1005
  const proceed = await confirm({ message: "Generate files?", default: true });
2243
1006
  if (!proceed) {
2244
- console.log(chalk2.yellow(" Cancelled.\n"));
1007
+ console.log(chalk.yellow(" Cancelled.\n"));
2245
1008
  await closePool();
2246
1009
  process.exit(0);
2247
1010
  }
@@ -2260,16 +1023,16 @@ program.command("init").description("Initialize memory-core in the current proje
2260
1023
  };
2261
1024
  let memories = [];
2262
1025
  try {
2263
- const archQuery = [backendArchitecture, frontendFramework, language].filter(Boolean).join(" ");
1026
+ const archQuery = [backendArchitecture, frontendFramework, language].filter(Boolean).filter((a) => a !== "custom").join(" ");
2264
1027
  const selection = await retrieveMemorySelection({
2265
- query: archQuery,
1028
+ query: archQuery || language,
2266
1029
  cwd: process.cwd(),
2267
1030
  config,
2268
1031
  limit: 20
2269
1032
  });
2270
1033
  memories = selection.included;
2271
1034
  if (memories.length > 0) {
2272
- console.log(chalk2.dim(` Found ${memories.length} relevant memories`));
1035
+ console.log(chalk.dim(` Found ${memories.length} relevant memories`));
2273
1036
  printMemorySelection(selection);
2274
1037
  }
2275
1038
  } catch {
@@ -2291,11 +1054,39 @@ program.command("init").description("Initialize memory-core in the current proje
2291
1054
  );
2292
1055
  writeProjectConfig(config);
2293
1056
  spinner.succeed(`Generated ${written.written.length} files`);
1057
+ try {
1058
+ const archmindDir = join(process.cwd(), ".archmind");
1059
+ mkdirSync(archmindDir, { recursive: true });
1060
+ const layersPath = join(archmindDir, "layers.json");
1061
+ const rulesPath = join(archmindDir, "rules.json");
1062
+ const archmindGitignorePath = join(archmindDir, ".gitignore");
1063
+ if (!existsSync(layersPath)) {
1064
+ const arch2 = backendArchitecture ?? frontendFramework ?? "custom";
1065
+ const layersTemplate = buildLayersTemplate(arch2);
1066
+ writeFileSync(layersPath, JSON.stringify(layersTemplate, null, 2) + "\n", "utf-8");
1067
+ console.log(chalk.green(" \u2713 Created .archmind/layers.json"));
1068
+ }
1069
+ if (!existsSync(rulesPath)) {
1070
+ writeFileSync(rulesPath, JSON.stringify(DEFAULT_ARCH_RULES, null, 2) + "\n", "utf-8");
1071
+ console.log(chalk.green(" \u2713 Created .archmind/rules.json"));
1072
+ }
1073
+ if (!existsSync(archmindGitignorePath)) {
1074
+ writeFileSync(archmindGitignorePath, "approval-queue.json\nwatch-errors.json\n", "utf-8");
1075
+ }
1076
+ } catch {
1077
+ }
1078
+ try {
1079
+ const graphSpinner = ora(" Building dependency graph snapshot\u2026").start();
1080
+ const { snapshot } = await phase1.services.graphEngine.buildAndStoreSnapshot({ cwd: process.cwd() });
1081
+ graphSpinner.succeed(` Graph snapshot ready (${snapshot.nodes.length} nodes, ${snapshot.edges.length} edges)`);
1082
+ } catch {
1083
+ console.log(chalk.dim(" (graph build skipped \u2014 run `memory-core graph build` once manually)"));
1084
+ }
2294
1085
  const gitignoreEntries = [...written.written, ...LOCAL_GENERATED_FILES];
2295
1086
  if (gitignoreEntries.length > 0) {
2296
1087
  const added = appendMissingGitignoreEntries(gitignoreEntries, GITIGNORE_HEADING);
2297
1088
  if (added > 0) {
2298
- console.log(chalk2.green(` \u2713 Added ${added} generated files to .gitignore`));
1089
+ console.log(chalk.green(` \u2713 Added ${added} generated files to .gitignore`));
2299
1090
  }
2300
1091
  }
2301
1092
  if (enableHook) {
@@ -2303,12 +1094,19 @@ program.command("init").description("Initialize memory-core in the current proje
2303
1094
  }
2304
1095
  const chatModelForBanner = process.env.CHAT_MODEL ?? DEFAULT_CHAT_MODEL;
2305
1096
  printBanner(config.projectName, written.written.length, { postgresOk: pgOk, ollamaOk, chatModel: chatModelForBanner });
1097
+ if (backendArchitecture === "custom" || frontendFramework === "custom") {
1098
+ console.log(chalk.yellow("\n Custom architecture \u2014 no profile rules loaded."));
1099
+ console.log(chalk.dim(" Add rules as your architecture takes shape:"));
1100
+ console.log(chalk.dim(' memory-core remember "Your rule" --type rule'));
1101
+ console.log(chalk.dim(" Or load a profile later when you decide:"));
1102
+ console.log(chalk.dim(" memory-core seed --arch clean-architecture\n"));
1103
+ }
2306
1104
  await closePool();
2307
1105
  });
2308
1106
  program.command("sync").description("Re-pull memories and regenerate AI agent files").action(async () => {
2309
1107
  const config = readProjectConfig();
2310
1108
  if (!config) {
2311
- console.error(chalk2.red("No .memory-core.json found. Run: memory-core init"));
1109
+ console.error(chalk.red("No .memory-core.json found. Run: memory-core init"));
2312
1110
  process.exit(1);
2313
1111
  }
2314
1112
  const { checkbox } = await import("@inquirer/prompts");
@@ -2323,7 +1121,7 @@ program.command("sync").description("Re-pull memories and regenerate AI agent fi
2323
1121
  instructions: " (Space to toggle, A to select all, Enter to confirm)"
2324
1122
  });
2325
1123
  if (selectedAgents.length === 0) {
2326
- console.log(chalk2.yellow(" No agents selected \u2014 nothing to sync."));
1124
+ console.log(chalk.yellow(" No agents selected \u2014 nothing to sync."));
2327
1125
  process.exit(0);
2328
1126
  }
2329
1127
  await syncGeneratedFiles(config, [...selectedAgents, "Shared"], { showSelection: true });
@@ -2332,24 +1130,24 @@ program.command("sync").description("Re-pull memories and regenerate AI agent fi
2332
1130
  program.command("auto-sync [mode]").description("Show or change automatic agent file sync (on|off)").action((mode) => {
2333
1131
  const config = readProjectConfig();
2334
1132
  if (!config) {
2335
- console.error(chalk2.red("No .memory-core.json found. Run: memory-core init"));
1133
+ console.error(chalk.red("No .memory-core.json found. Run: memory-core init"));
2336
1134
  process.exit(1);
2337
1135
  }
2338
1136
  const normalized = mode?.trim().toLowerCase();
2339
1137
  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"));
1138
+ console.log(chalk.bold("\n Auto-sync\n"));
1139
+ console.log(` Status: ${config.autoSync === false ? chalk.yellow("disabled") : chalk.green("enabled")}`);
1140
+ console.log(chalk.gray(" Manual sync is always available: memory-core sync\n"));
2343
1141
  return;
2344
1142
  }
2345
1143
  if (normalized !== "on" && normalized !== "off") {
2346
- console.error(chalk2.red("Use: memory-core auto-sync [on|off|status]"));
1144
+ console.error(chalk.red("Use: memory-core auto-sync [on|off|status]"));
2347
1145
  process.exit(1);
2348
1146
  }
2349
1147
  const enabled = normalized === "on";
2350
1148
  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"));
1149
+ console.log(chalk.green(`Auto-sync ${enabled ? "enabled" : "disabled"}`));
1150
+ console.log(chalk.gray(" Manual sync is always available: memory-core sync"));
2353
1151
  });
2354
1152
  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
1153
  const config = readProjectConfig();
@@ -2357,7 +1155,7 @@ program.command("remember <text>").description("Save a new memory to the central
2357
1155
  let reason = opts.reason;
2358
1156
  if (!reason) {
2359
1157
  reason = await input({
2360
- message: chalk2.dim("Why should this memory exist?"),
1158
+ message: chalk.dim("Why should this memory exist?"),
2361
1159
  default: ""
2362
1160
  });
2363
1161
  }
@@ -2374,11 +1172,11 @@ program.command("remember <text>").description("Save a new memory to the central
2374
1172
  context: buildMemoryContext(opts),
2375
1173
  tags: parseTags(opts.tags)
2376
1174
  });
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(`
1175
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
1176
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1177
+ const reasonLine = chalk.gray(`
2380
1178
  Why: ${storedReason}`);
2381
- spinner.succeed(chalk2.green(`Memory saved: "${text}"`) + reasonLine);
1179
+ spinner.succeed(chalk.green(`Memory saved: "${text}"`) + reasonLine);
2382
1180
  await autoSyncGeneratedFiles(config, "remember", opts.sync);
2383
1181
  } catch (err) {
2384
1182
  spinner.fail(`Failed: ${err.message}`);
@@ -2399,20 +1197,20 @@ program.command("search <query>").description("Search memories using semantic si
2399
1197
  const results = result.items;
2400
1198
  spinner.stop();
2401
1199
  if (results.length === 0) {
2402
- console.log(chalk2.yellow("No memories found."));
1200
+ console.log(chalk.yellow("No memories found."));
2403
1201
  } else {
2404
- console.log(chalk2.bold(`
1202
+ console.log(chalk.bold(`
2405
1203
  ${results.length} results for "${query}"
2406
1204
  `));
2407
1205
  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(", ")}`));
1206
+ const sim = m.similarity ? chalk.gray(` (${(m.similarity * 100).toFixed(0)}% match)`) : "";
1207
+ console.log(chalk.cyan(` ${i + 1}. [${m.type}] ${m.title ?? ""}`));
1208
+ console.log(chalk.white(` ${m.content}`) + sim);
1209
+ if (m.reason) console.log(chalk.gray(` why: ${m.reason}`));
1210
+ if (m.context?.appliesTo?.length) console.log(chalk.gray(` use when: ${m.context.appliesTo.join("; ")}`));
1211
+ if (m.context?.avoidWhen?.length) console.log(chalk.gray(` avoid when: ${m.context.avoidWhen.join("; ")}`));
1212
+ if (m.context?.examples?.length) console.log(chalk.gray(` examples: ${m.context.examples.join("; ")}`));
1213
+ if (m.tags?.length) console.log(chalk.gray(` tags: ${m.tags.join(", ")}`));
2416
1214
  console.log();
2417
1215
  });
2418
1216
  }
@@ -2427,9 +1225,9 @@ program.command("export").description(`Export DB memories to ${MEMORY_FILE}`).op
2427
1225
  try {
2428
1226
  const memories = await phase1.services.memoryEngine.list({ limit: 1e4 });
2429
1227
  const portable = memories.map(toPortableFromRecord);
2430
- const outputPath = opts.output ? join3(process.cwd(), opts.output) : writeMemoryFile(portable);
1228
+ const outputPath = opts.output ? join(process.cwd(), opts.output) : writeMemoryFile(portable);
2431
1229
  if (opts.output) {
2432
- writeFileSync3(outputPath, JSON.stringify(portable, null, 2) + "\n", "utf-8");
1230
+ writeFileSync(outputPath, JSON.stringify(portable, null, 2) + "\n", "utf-8");
2433
1231
  }
2434
1232
  spinner.succeed(`Exported ${portable.length} memories to ${outputPath}`);
2435
1233
  } catch (err) {
@@ -2443,7 +1241,7 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
2443
1241
  const spinner = ora("Reading memories\u2026").start();
2444
1242
  try {
2445
1243
  const config = readProjectConfig();
2446
- const memories = opts.url ? await readMemoryFileFromUrl(opts.url) : opts.file ? parseMemoryFile(readFileSync3(join3(process.cwd(), opts.file), "utf-8")) : readMemoryFile();
1244
+ const memories = opts.url ? await readMemoryFileFromUrl(opts.url) : opts.file ? parseMemoryFile(readFileSync(join(process.cwd(), opts.file), "utf-8")) : readMemoryFile();
2447
1245
  let inserted = 0;
2448
1246
  let skipped = 0;
2449
1247
  spinner.text = `Importing ${memories.length} memories\u2026`;
@@ -2464,8 +1262,8 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
2464
1262
  }
2465
1263
  spinner.succeed(`Imported ${inserted} memories, skipped ${skipped} duplicates`);
2466
1264
  if (inserted > 0) {
2467
- const dbVersionPath = join3(process.cwd(), ".memory-core-db-version");
2468
- writeFileSync3(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1265
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
1266
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2469
1267
  await autoSyncGeneratedFiles(config, "import", opts.sync);
2470
1268
  }
2471
1269
  } catch (err) {
@@ -2491,10 +1289,10 @@ program.command("list").description("List memories from the local database").opt
2491
1289
  const title = opts.all ? "All memories" : `Current project memories${architectures ? ` (${Array.isArray(architectures) ? architectures.join(", ") : architectures})` : ""}`;
2492
1290
  printMemoryTable(memories.map(toMemoryTableRow), title);
2493
1291
  if (!opts.all) {
2494
- console.log(chalk2.gray(" Showing current project context plus shared/global memories. Use --all for the full database.\n"));
1292
+ console.log(chalk.gray(" Showing current project context plus shared/global memories. Use --all for the full database.\n"));
2495
1293
  }
2496
1294
  } catch (err) {
2497
- console.error(chalk2.red(`List failed: ${err.message}`));
1295
+ console.error(chalk.red(`List failed: ${err.message}`));
2498
1296
  process.exit(1);
2499
1297
  } finally {
2500
1298
  await closePool();
@@ -2505,13 +1303,13 @@ program.command("remove <id>").description("Remove a memory by ID").option("--no
2505
1303
  const config = readProjectConfig();
2506
1304
  const deleted = await phase1.services.memoryEngine.removeById(parseInt(id, 10));
2507
1305
  if (!deleted) {
2508
- console.log(chalk2.yellow(`No memory found with ID ${id}`));
1306
+ console.log(chalk.yellow(`No memory found with ID ${id}`));
2509
1307
  process.exit(1);
2510
1308
  }
2511
- console.log(chalk2.green(`Removed memory ${id}`));
1309
+ console.log(chalk.green(`Removed memory ${id}`));
2512
1310
  await autoSyncGeneratedFiles(config, "remove", opts.sync);
2513
1311
  } catch (err) {
2514
- console.error(chalk2.red(`Remove failed: ${err.message}`));
1312
+ console.error(chalk.red(`Remove failed: ${err.message}`));
2515
1313
  process.exit(1);
2516
1314
  } finally {
2517
1315
  await closePool();
@@ -2526,12 +1324,12 @@ program.command("forget").description("Bulk-delete memories by tag, scope, type,
2526
1324
  type: opts.type,
2527
1325
  architecture: opts.arch
2528
1326
  });
2529
- console.log(chalk2.green(`Deleted ${deleted} memories`));
1327
+ console.log(chalk.green(`Deleted ${deleted} memories`));
2530
1328
  if (deleted > 0) {
2531
1329
  await autoSyncGeneratedFiles(config, "forget", opts.sync);
2532
1330
  }
2533
1331
  } catch (err) {
2534
- console.error(chalk2.red(`Forget failed: ${err.message}`));
1332
+ console.error(chalk.red(`Forget failed: ${err.message}`));
2535
1333
  process.exit(1);
2536
1334
  } finally {
2537
1335
  await closePool();
@@ -2543,7 +1341,7 @@ program.command("edit <id>").description("Edit a memory interactively").option("
2543
1341
  const config = readProjectConfig();
2544
1342
  const existing = await phase1.services.memoryEngine.getById(memoryId);
2545
1343
  if (!existing) {
2546
- console.log(chalk2.yellow(`No memory found with ID ${id}`));
1344
+ console.log(chalk.yellow(`No memory found with ID ${id}`));
2547
1345
  process.exit(1);
2548
1346
  }
2549
1347
  const type = await input({ message: "Type?", default: existing.type });
@@ -2565,10 +1363,10 @@ program.command("edit <id>").description("Edit a memory interactively").option("
2565
1363
  context: buildMemoryContext({ appliesTo, avoidWhen, example: examples, source }),
2566
1364
  tags: parseTags(tags)
2567
1365
  });
2568
- console.log(chalk2.green(`Updated memory ${id}`));
1366
+ console.log(chalk.green(`Updated memory ${id}`));
2569
1367
  await autoSyncGeneratedFiles(config, "edit", opts.sync);
2570
1368
  } catch (err) {
2571
- console.error(chalk2.red(`Edit failed: ${err.message}`));
1369
+ console.error(chalk.red(`Edit failed: ${err.message}`));
2572
1370
  process.exit(1);
2573
1371
  } finally {
2574
1372
  await closePool();
@@ -2585,15 +1383,15 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
2585
1383
  if (opts.remove) {
2586
1384
  const deleted = await phase1.services.memoryEngine.removeById(parseInt(opts.remove, 10));
2587
1385
  if (!deleted) {
2588
- console.log(chalk2.yellow(`No ignore pattern found with ID ${opts.remove}`));
1386
+ console.log(chalk.yellow(`No ignore pattern found with ID ${opts.remove}`));
2589
1387
  process.exit(1);
2590
1388
  }
2591
- console.log(chalk2.green(`Removed ignore pattern ${opts.remove}`));
1389
+ console.log(chalk.green(`Removed ignore pattern ${opts.remove}`));
2592
1390
  await autoSyncGeneratedFiles(config, "ignore remove", opts.sync);
2593
1391
  return;
2594
1392
  }
2595
1393
  if (!pattern) {
2596
- console.error(chalk2.red("Provide a pattern, --list, or --remove <id>"));
1394
+ console.error(chalk.red("Provide a pattern, --list, or --remove <id>"));
2597
1395
  process.exit(1);
2598
1396
  }
2599
1397
  await phase1.services.memoryEngine.remember({
@@ -2604,12 +1402,12 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
2604
1402
  content: pattern,
2605
1403
  tags: ["ignore"]
2606
1404
  });
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}"`));
1405
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
1406
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1407
+ console.log(chalk.green(`Ignored pattern saved: "${pattern}"`));
2610
1408
  await autoSyncGeneratedFiles(config, "ignore", opts.sync);
2611
1409
  } catch (err) {
2612
- console.error(chalk2.red(`Ignore failed: ${err.message}`));
1410
+ console.error(chalk.red(`Ignore failed: ${err.message}`));
2613
1411
  process.exit(1);
2614
1412
  } finally {
2615
1413
  await closePool();
@@ -2619,10 +1417,10 @@ program.command("allow [pattern]").description("Manage project allow patterns in
2619
1417
  if (opts.list) {
2620
1418
  const patterns = getAllowPatterns(readProjectConfig());
2621
1419
  if (patterns.length === 0) {
2622
- console.log(chalk2.yellow("\n No allow patterns configured.\n"));
1420
+ console.log(chalk.yellow("\n No allow patterns configured.\n"));
2623
1421
  return;
2624
1422
  }
2625
- console.log(chalk2.bold("\n Allow patterns\n"));
1423
+ console.log(chalk.bold("\n Allow patterns\n"));
2626
1424
  patterns.forEach((entry, index) => console.log(` ${index + 1}. ${entry}`));
2627
1425
  console.log();
2628
1426
  return;
@@ -2632,35 +1430,35 @@ program.command("allow [pattern]").description("Manage project allow patterns in
2632
1430
  ...config,
2633
1431
  allowPatterns: getAllowPatterns(config).filter((entry) => entry !== opts.remove)
2634
1432
  }));
2635
- console.log(chalk2.green(`Removed allow pattern: "${opts.remove}"`));
1433
+ console.log(chalk.green(`Removed allow pattern: "${opts.remove}"`));
2636
1434
  return;
2637
1435
  }
2638
1436
  if (!pattern) {
2639
- console.error(chalk2.red("Provide a pattern, --list, or --remove <pattern>"));
1437
+ console.error(chalk.red("Provide a pattern, --list, or --remove <pattern>"));
2640
1438
  process.exit(1);
2641
1439
  }
2642
1440
  updateProjectConfig((config) => ({
2643
1441
  ...config,
2644
1442
  allowPatterns: [.../* @__PURE__ */ new Set([...getAllowPatterns(config), pattern])]
2645
1443
  }));
2646
- console.log(chalk2.green(`Allow pattern saved: "${pattern}"`));
1444
+ console.log(chalk.green(`Allow pattern saved: "${pattern}"`));
2647
1445
  });
2648
1446
  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
1447
  if (opts.list) {
2650
1448
  const rules = readProjectConfig()?.commitRules ?? [];
2651
1449
  if (rules.length === 0) {
2652
- console.log(chalk2.yellow("\n No commit rules configured.\n"));
1450
+ console.log(chalk.yellow("\n No commit rules configured.\n"));
2653
1451
  return;
2654
1452
  }
2655
- console.log(chalk2.bold("\n Commit message rules\n"));
1453
+ console.log(chalk.bold("\n Commit message rules\n"));
2656
1454
  rules.forEach((rule, i) => {
2657
1455
  const flags = [
2658
1456
  rule.negate ? "must NOT match" : "must match",
2659
1457
  rule.advisory ? "advisory" : "blocking"
2660
1458
  ].join(", ");
2661
1459
  console.log(` ${i + 1}. ${rule.pattern}`);
2662
- console.log(chalk2.dim(` Message: ${rule.message}`));
2663
- console.log(chalk2.dim(` Flags: ${flags}`));
1460
+ console.log(chalk.dim(` Message: ${rule.message}`));
1461
+ console.log(chalk.dim(` Flags: ${flags}`));
2664
1462
  console.log();
2665
1463
  });
2666
1464
  return;
@@ -2670,21 +1468,21 @@ program.command("commit-rules [pattern]").description("Manage commit message rul
2670
1468
  ...config,
2671
1469
  commitRules: (config.commitRules ?? []).filter((r) => r.pattern !== opts.remove)
2672
1470
  }));
2673
- console.log(chalk2.green(`Commit rule removed: "${opts.remove}"`));
1471
+ console.log(chalk.green(`Commit rule removed: "${opts.remove}"`));
2674
1472
  return;
2675
1473
  }
2676
1474
  if (!pattern) {
2677
- console.error(chalk2.red("Provide a pattern, --list, or --remove <pattern>"));
1475
+ console.error(chalk.red("Provide a pattern, --list, or --remove <pattern>"));
2678
1476
  process.exit(1);
2679
1477
  }
2680
1478
  if (!opts.message) {
2681
- console.error(chalk2.red("--message is required when adding a commit rule"));
1479
+ console.error(chalk.red("--message is required when adding a commit rule"));
2682
1480
  process.exit(1);
2683
1481
  }
2684
1482
  try {
2685
1483
  new RegExp(pattern);
2686
1484
  } catch {
2687
- console.error(chalk2.red(`Invalid regex pattern: "${pattern}"`));
1485
+ console.error(chalk.red(`Invalid regex pattern: "${pattern}"`));
2688
1486
  process.exit(1);
2689
1487
  }
2690
1488
  const newRule = {
@@ -2697,13 +1495,13 @@ program.command("commit-rules [pattern]").description("Manage commit message rul
2697
1495
  ...config,
2698
1496
  commitRules: [...(config.commitRules ?? []).filter((r) => r.pattern !== pattern), newRule]
2699
1497
  }));
2700
- console.log(chalk2.green(`Commit rule saved: "${pattern}"`));
2701
- console.log(chalk2.dim(" Run: memory-core commit-rules --list to see all rules"));
1498
+ console.log(chalk.green(`Commit rule saved: "${pattern}"`));
1499
+ console.log(chalk.dim(" Run: memory-core commit-rules --list to see all rules"));
2702
1500
  });
2703
1501
  program.command("ci-setup").description("Generate GitHub Actions workflow for memory-core").action(() => {
2704
- const workflowPath = join3(process.cwd(), ".github", "workflows", "memory-core.yml");
1502
+ const workflowPath = join(process.cwd(), ".github", "workflows", "memory-core.yml");
2705
1503
  mkdirSync(dirname(workflowPath), { recursive: true });
2706
- writeFileSync3(workflowPath, `name: memory-core
1504
+ writeFileSync(workflowPath, `name: memory-core
2707
1505
  on: [pull_request]
2708
1506
  jobs:
2709
1507
  check:
@@ -2714,7 +1512,7 @@ jobs:
2714
1512
  fetch-depth: 0
2715
1513
  - run: npx @shahmilsaari/memory-core check --ci
2716
1514
  `, "utf-8");
2717
- console.log(chalk2.green(`Generated ${workflowPath}`));
1515
+ console.log(chalk.green(`Generated ${workflowPath}`));
2718
1516
  });
2719
1517
  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
1518
  let removed = removeProjectFiles(OUTPUT_FILES.map((file) => file.path)).length;
@@ -2730,14 +1528,14 @@ program.command("reset").description("Remove memory-core generated files and loc
2730
1528
  if (ok) {
2731
1529
  await getPool().query("DROP TABLE IF EXISTS memories");
2732
1530
  await closePool();
2733
- console.log(chalk2.yellow("Dropped memories table"));
1531
+ console.log(chalk.yellow("Dropped memories table"));
2734
1532
  }
2735
1533
  }
2736
- console.log(chalk2.green(`Reset complete. Removed ${removed} files.`));
1534
+ console.log(chalk.green(`Reset complete. Removed ${removed} files.`));
2737
1535
  });
2738
1536
  program.command("uninstall").description("Remove memory-core from the current project").option("--db", "Also drop the memories table after confirmation").action(async (opts) => {
2739
1537
  const generatedFiles = OUTPUT_FILES.map((file) => file.path);
2740
- const gitignoreEntries = [...generatedFiles, ...LOCAL_GENERATED_FILES];
1538
+ const gitignoreEntries = [...generatedFiles, ...LOCAL_STATE_FILES];
2741
1539
  const removed = removeProjectFiles([
2742
1540
  ...generatedFiles,
2743
1541
  ...LOCAL_STATE_FILES,
@@ -2753,19 +1551,19 @@ program.command("uninstall").description("Remove memory-core from the current pr
2753
1551
  if (ok) {
2754
1552
  await getPool().query("DROP TABLE IF EXISTS memories");
2755
1553
  await closePool();
2756
- console.log(chalk2.yellow("Dropped memories table"));
1554
+ console.log(chalk.yellow("Dropped memories table"));
2757
1555
  }
2758
1556
  }
2759
- console.log(chalk2.green(`Uninstall complete. Removed ${removed.length} files.`));
1557
+ console.log(chalk.green(`Uninstall complete. Removed ${removed.length} files.`));
2760
1558
  if (removed.length > 0) {
2761
- removed.forEach((file) => console.log(chalk2.gray(` \u2713 ${file}`)));
1559
+ removed.forEach((file) => console.log(chalk.gray(` \u2713 ${file}`)));
2762
1560
  }
2763
1561
  if (cleanedGitignore) {
2764
- console.log(chalk2.gray(" \u2713 cleaned .gitignore memory-core block"));
1562
+ console.log(chalk.gray(" \u2713 cleaned .gitignore memory-core block"));
2765
1563
  }
2766
1564
  });
2767
1565
  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");
1566
+ const statsPath = join(process.cwd(), ".memory-core-stats.json");
2769
1567
  if (opts.reset) {
2770
1568
  const emptyStats = {
2771
1569
  rules: {},
@@ -2777,27 +1575,27 @@ program.command("stats").description("Show violation counters recorded by check
2777
1575
  },
2778
1576
  recentViolations: []
2779
1577
  };
2780
- writeFileSync3(statsPath, JSON.stringify(emptyStats, null, 2) + "\n", "utf-8");
2781
- console.log(chalk2.green("\n Violation stats reset.\n"));
1578
+ writeFileSync(statsPath, JSON.stringify(emptyStats, null, 2) + "\n", "utf-8");
1579
+ console.log(chalk.green("\n Violation stats reset.\n"));
2782
1580
  return;
2783
1581
  }
2784
- if (!existsSync3(statsPath)) {
2785
- console.log(chalk2.yellow("\n No violation stats recorded yet.\n"));
1582
+ if (!existsSync(statsPath)) {
1583
+ console.log(chalk.yellow("\n No violation stats recorded yet.\n"));
2786
1584
  return;
2787
1585
  }
2788
- const stats = JSON.parse(readFileSync3(statsPath, "utf-8"));
1586
+ const stats = JSON.parse(readFileSync(statsPath, "utf-8"));
2789
1587
  const toEntry = (raw) => {
2790
1588
  if (raw === void 0) return { count: 0, falsePositives: 0 };
2791
1589
  if (typeof raw === "number") return { count: raw, falsePositives: 0 };
2792
1590
  return raw;
2793
1591
  };
2794
1592
  const printTop = (label, values = {}) => {
2795
- console.log(chalk2.bold(`
1593
+ console.log(chalk.bold(`
2796
1594
  ${label}
2797
1595
  `));
2798
1596
  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
1597
  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" : "") : "";
1598
+ const fpHint = rate > 0 ? chalk.dim(` \u2014 ${rate}% false-positive rate`) + (rate > 25 ? " \u26A0\uFE0F" : "") : "";
2801
1599
  console.log(` ${index + 1}. ${truncate(name, 44).padEnd(46)} ${entry.count} hits${fpHint}`);
2802
1600
  });
2803
1601
  };
@@ -2810,18 +1608,18 @@ program.command("stats").description("Show violation counters recorded by check
2810
1608
  rate: entry.count > 0 ? Math.round(entry.falsePositives / entry.count * 100) : 0
2811
1609
  })).filter((r) => r.rate > tuneThreshold && r.count >= tuneMinCount).sort((a, b) => b.rate - a.rate);
2812
1610
  if (noisy.length === 0) {
2813
- console.log(chalk2.green(`
1611
+ console.log(chalk.green(`
2814
1612
  \u2713 No noisy rules found (threshold: ${tuneThreshold}%, min hits: ${tuneMinCount})
2815
1613
  `));
2816
1614
  return;
2817
1615
  }
2818
- console.log(chalk2.bold(`
1616
+ console.log(chalk.bold(`
2819
1617
  Noisy rules (>${tuneThreshold}% false-positive rate, \u2265${tuneMinCount} hits)
2820
1618
  `));
2821
1619
  noisy.forEach(({ name, count, rate }, i) => {
2822
1620
  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`));
1621
+ console.log(chalk.dim(` To disable: memory-core allow "${name}"`));
1622
+ console.log(chalk.dim(` Interactive: memory-core tune`));
2825
1623
  console.log();
2826
1624
  });
2827
1625
  return;
@@ -2839,20 +1637,20 @@ program.command("stats").description("Show violation counters recorded by check
2839
1637
  hasLiveState ? liveFiles : stats.files
2840
1638
  );
2841
1639
  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"));
1640
+ console.log(chalk.dim("\n Note: these counters are historical events, not live current code state."));
1641
+ console.log(chalk.dim(" Start watch for live counters, or reset with: memory-core stats --reset\n"));
2844
1642
  } else {
2845
1643
  if (hasLiveViolations) {
2846
- console.log(chalk2.dim("\n Live counters auto-refresh while watch is running.\n"));
1644
+ console.log(chalk.dim("\n Live counters auto-refresh while watch is running.\n"));
2847
1645
  } else {
2848
- console.log(chalk2.dim("\n Current live state has no violations.\n"));
1646
+ console.log(chalk.dim("\n Current live state has no violations.\n"));
2849
1647
  }
2850
1648
  }
2851
1649
  });
2852
1650
  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"));
1651
+ const statsPath = join(process.cwd(), ".memory-core-stats.json");
1652
+ if (!existsSync(statsPath)) {
1653
+ console.log(chalk.yellow("\n No violation stats yet. Run some commits first.\n"));
2856
1654
  return;
2857
1655
  }
2858
1656
  const threshold = Math.max(0, Math.min(100, parseInt(opts.threshold, 10) || 40));
@@ -2862,19 +1660,19 @@ program.command("tune").description("Review and disable noisy rules with high fa
2862
1660
  if (typeof raw === "number") return { count: raw, falsePositives: 0 };
2863
1661
  return raw;
2864
1662
  };
2865
- const stats = JSON.parse(readFileSync3(statsPath, "utf-8"));
1663
+ const stats = JSON.parse(readFileSync(statsPath, "utf-8"));
2866
1664
  const noisy = Object.entries(stats.rules ?? {}).map(([rule, raw]) => {
2867
1665
  const entry = toEntry(raw);
2868
1666
  const rate = entry.count > 0 ? Math.round(entry.falsePositives / entry.count * 100) : 0;
2869
1667
  return { rule, count: entry.count, rate };
2870
1668
  }).filter((r) => r.rate > threshold && r.count >= minCount).sort((a, b) => b.rate - a.rate);
2871
1669
  if (noisy.length === 0) {
2872
- console.log(chalk2.green(`
1670
+ console.log(chalk.green(`
2873
1671
  \u2713 All rules within acceptable noise (threshold: ${threshold}%, min hits: ${minCount})
2874
1672
  `));
2875
1673
  return;
2876
1674
  }
2877
- console.log(chalk2.bold(`
1675
+ console.log(chalk.bold(`
2878
1676
  Found ${noisy.length} noisy rule${noisy.length > 1 ? "s" : ""} (>${threshold}% false-positive rate, \u2265${minCount} hits)
2879
1677
  `));
2880
1678
  const existingAllows = new Set(
@@ -2885,22 +1683,22 @@ program.command("tune").description("Review and disable noisy rules with high fa
2885
1683
  for (const { rule, count, rate } of noisy) {
2886
1684
  const key = rule.toLowerCase();
2887
1685
  if (existingAllows.has(key)) {
2888
- console.log(chalk2.dim(` \u2022 "${truncate(rule, 56)}" \u2014 already disabled`));
1686
+ console.log(chalk.dim(` \u2022 "${truncate(rule, 56)}" \u2014 already disabled`));
2889
1687
  continue;
2890
1688
  }
2891
1689
  toAdd.add(key);
2892
- console.log(chalk2.green(` \u2713 "${truncate(rule, 56)}"`) + chalk2.dim(` \u2014 ${count} hits, ${rate}% FP rate`));
1690
+ console.log(chalk.green(` \u2713 "${truncate(rule, 56)}"`) + chalk.dim(` \u2014 ${count} hits, ${rate}% FP rate`));
2893
1691
  }
2894
1692
  } else {
2895
1693
  const { select: select2 } = await import("@inquirer/prompts");
2896
1694
  for (let i = 0; i < noisy.length; i++) {
2897
1695
  const { rule, count, rate } = noisy[i];
2898
1696
  const key = rule.toLowerCase();
2899
- console.log(chalk2.bold(`
1697
+ console.log(chalk.bold(`
2900
1698
  [${i + 1}/${noisy.length}] "${truncate(rule, 60)}"`));
2901
- console.log(chalk2.dim(` ${count} hits \u2014 ${rate}% false-positive rate \u26A0\uFE0F`));
1699
+ console.log(chalk.dim(` ${count} hits \u2014 ${rate}% false-positive rate \u26A0\uFE0F`));
2902
1700
  if (existingAllows.has(key)) {
2903
- console.log(chalk2.dim(" Already in allow patterns \u2014 skipping"));
1701
+ console.log(chalk.dim(" Already in allow patterns \u2014 skipping"));
2904
1702
  continue;
2905
1703
  }
2906
1704
  const choice = await select2({
@@ -2914,7 +1712,7 @@ program.command("tune").description("Review and disable noisy rules with high fa
2914
1712
  if (choice === "quit") break;
2915
1713
  if (choice === "disable") {
2916
1714
  toAdd.add(key);
2917
- console.log(chalk2.green(" \u2713 Marked for disable"));
1715
+ console.log(chalk.green(" \u2713 Marked for disable"));
2918
1716
  }
2919
1717
  }
2920
1718
  }
@@ -2923,11 +1721,11 @@ program.command("tune").description("Review and disable noisy rules with high fa
2923
1721
  ...config,
2924
1722
  allowPatterns: [.../* @__PURE__ */ new Set([...config.allowPatterns ?? [], ...toAdd])]
2925
1723
  }));
2926
- console.log(chalk2.green(`
1724
+ console.log(chalk.green(`
2927
1725
  \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"));
1726
+ console.log(chalk.dim(" These rules will no longer block commits.\n"));
2929
1727
  } else {
2930
- console.log(chalk2.dim("\n No changes made.\n"));
1728
+ console.log(chalk.dim("\n No changes made.\n"));
2931
1729
  }
2932
1730
  });
2933
1731
  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 +1745,7 @@ program.command("dashboard").description("Start the live Svelte dashboard with W
2947
1745
  }
2948
1746
  return void 0;
2949
1747
  };
2950
- const { startDashboard } = await import("./dashboard-server-VOT2ZRVN.js");
1748
+ const { startDashboard } = await import("./dashboard-server-EEFNE6NX.js");
2951
1749
  await startDashboard({
2952
1750
  port: parseInt(opts.port, 10),
2953
1751
  path: resolveDashboardPath(),
@@ -2957,7 +1755,7 @@ program.command("dashboard").description("Start the live Svelte dashboard with W
2957
1755
  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
1756
  await runMigrations();
2959
1757
  const filtered = opts.arch ? seeds.filter((s) => s.architecture === opts.arch || s.architecture === "global") : seeds;
2960
- console.log(chalk2.bold.cyan(`
1758
+ console.log(chalk.bold.cyan(`
2961
1759
  Seeding ${filtered.length} memories\u2026
2962
1760
  `));
2963
1761
  let saved = 0;
@@ -2977,15 +1775,15 @@ program.command("seed").description("Load all predefined memories into the datab
2977
1775
  if (opts.force) {
2978
1776
  await phase1.services.memoryEngine.rememberForce(payload);
2979
1777
  saved++;
2980
- spinner.succeed(chalk2.gray(`[${seed.architecture}] ${seed.title}`));
1778
+ spinner.succeed(chalk.gray(`[${seed.architecture}] ${seed.title}`));
2981
1779
  } else {
2982
1780
  const result = await phase1.services.memoryEngine.remember(payload);
2983
1781
  if (result === "inserted") {
2984
1782
  saved++;
2985
- spinner.succeed(chalk2.gray(`[${seed.architecture}] ${seed.title}`));
1783
+ spinner.succeed(chalk.gray(`[${seed.architecture}] ${seed.title}`));
2986
1784
  } else {
2987
1785
  skipped++;
2988
- spinner.info(chalk2.gray(`Already exists \u2014 [${seed.architecture}] ${seed.title}`));
1786
+ spinner.info(chalk.gray(`Already exists \u2014 [${seed.architecture}] ${seed.title}`));
2989
1787
  }
2990
1788
  }
2991
1789
  } catch (err) {
@@ -2994,10 +1792,10 @@ program.command("seed").description("Load all predefined memories into the datab
2994
1792
  }
2995
1793
  }
2996
1794
  if (saved > 0) {
2997
- const dbVersionPath = join3(process.cwd(), ".memory-core-db-version");
2998
- writeFileSync3(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
1795
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
1796
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2999
1797
  }
3000
- console.log(chalk2.bold.green(`
1798
+ console.log(chalk.bold.green(`
3001
1799
  Done. ${saved} memories seeded, ${skipped} skipped.
3002
1800
  `));
3003
1801
  await closePool();
@@ -3006,21 +1804,21 @@ program.command("global").description("Sync your memory into every AI agent glob
3006
1804
  const home = homedir();
3007
1805
  const GLOBAL_TARGETS = [
3008
1806
  // Claude Code
3009
- { label: "Claude Code", path: join3(home, ".claude/CLAUDE.md"), type: "md" },
1807
+ { label: "Claude Code", path: join(home, ".claude/CLAUDE.md"), type: "md" },
3010
1808
  // GitHub Copilot (VS Code)
3011
- { label: "Copilot", path: join3(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-copilot" },
1809
+ { label: "Copilot", path: join(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-copilot" },
3012
1810
  // Cursor global rules
3013
- { label: "Cursor", path: join3(home, ".cursor/rules/memory-core.mdc"), type: "md" },
1811
+ { label: "Cursor", path: join(home, ".cursor/rules/memory-core.mdc"), type: "md" },
3014
1812
  // Cline (VS Code)
3015
- { label: "Cline", path: join3(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-cline" },
1813
+ { label: "Cline", path: join(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-cline" },
3016
1814
  // Continue.dev global config
3017
- { label: "Continue.dev", path: join3(home, ".continue/config.json"), type: "continue" },
1815
+ { label: "Continue.dev", path: join(home, ".continue/config.json"), type: "continue" },
3018
1816
  // Aider global config
3019
- { label: "Aider", path: join3(home, ".aider.conf.yml"), type: "aider" },
1817
+ { label: "Aider", path: join(home, ".aider.conf.yml"), type: "aider" },
3020
1818
  // Zed global settings
3021
- { label: "Zed AI", path: join3(home, ".config/zed/settings.json"), type: "zed" },
1819
+ { label: "Zed AI", path: join(home, ".config/zed/settings.json"), type: "zed" },
3022
1820
  // Windsurf global rules
3023
- { label: "Windsurf", path: join3(home, ".windsurf/rules/memory-core.md"), type: "md" }
1821
+ { label: "Windsurf", path: join(home, ".windsurf/rules/memory-core.md"), type: "md" }
3024
1822
  ];
3025
1823
  const spinner = ora("Fetching global memories\u2026").start();
3026
1824
  let memories = [];
@@ -3050,12 +1848,12 @@ ${rulesText}
3050
1848
  const skipped = [];
3051
1849
  const writeFile = (filePath, content) => {
3052
1850
  mkdirSync(dirname(filePath), { recursive: true });
3053
- writeFileSync3(filePath, content, "utf-8");
1851
+ writeFileSync(filePath, content, "utf-8");
3054
1852
  };
3055
1853
  const readJson = (filePath) => {
3056
- if (!existsSync3(filePath)) return {};
1854
+ if (!existsSync(filePath)) return {};
3057
1855
  try {
3058
- return JSON.parse(readFileSync3(filePath, "utf-8"));
1856
+ return JSON.parse(readFileSync(filePath, "utf-8"));
3059
1857
  } catch {
3060
1858
  return {};
3061
1859
  }
@@ -3103,14 +1901,14 @@ read:
3103
1901
  skipped.push(target.label);
3104
1902
  }
3105
1903
  }
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}`)));
1904
+ spinner.succeed(chalk.green(`Synced ${memories.length} memories \u2192 ${written.length} agents`));
1905
+ console.log(chalk.green("\n Updated:"));
1906
+ written.forEach((l) => console.log(chalk.gray(` \u2713 ${l}`)));
3109
1907
  if (skipped.length) {
3110
- console.log(chalk2.yellow("\n Skipped (not installed):"));
3111
- skipped.forEach((l) => console.log(chalk2.gray(` \u2717 ${l}`)));
1908
+ console.log(chalk.yellow("\n Skipped (not installed):"));
1909
+ skipped.forEach((l) => console.log(chalk.gray(` \u2717 ${l}`)));
3112
1910
  }
3113
- console.log(chalk2.bold("\n Every AI agent now follows your memory globally.\n"));
1911
+ console.log(chalk.bold("\n Every AI agent now follows your memory globally.\n"));
3114
1912
  await closePool();
3115
1913
  });
3116
1914
  var provider = program.command("provider").description("Manage the code-checking provider configuration");
@@ -3156,10 +1954,10 @@ provider.command("set <name>").description("Set the code-checking provider (olla
3156
1954
  writeRuntimeEnv(values, runtimeEnv.envPath);
3157
1955
  applyRuntimeEnv(values);
3158
1956
  appendMissingGitignoreEntries(LOCAL_STATE_FILES, GITIGNORE_HEADING);
3159
- console.log(chalk2.green(`Updated provider: ${providerName}`));
3160
- console.log(chalk2.gray(` Chat model: ${getConfiguredChatModel(values)}`));
1957
+ console.log(chalk.green(`Updated provider: ${providerName}`));
1958
+ console.log(chalk.gray(` Chat model: ${getConfiguredChatModel(values)}`));
3161
1959
  } catch (err) {
3162
- console.error(chalk2.red(`Provider update failed: ${err.message}`));
1960
+ console.error(chalk.red(`Provider update failed: ${err.message}`));
3163
1961
  process.exit(1);
3164
1962
  }
3165
1963
  });
@@ -3181,10 +1979,10 @@ model.command("set <name>").description("Set the chat model used for code checki
3181
1979
  writeRuntimeEnv(values, runtimeEnv.envPath);
3182
1980
  applyRuntimeEnv(values);
3183
1981
  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}`));
1982
+ console.log(chalk.green(`Updated ${opts.embedding ? "embedding" : "chat"} model: ${name}`));
1983
+ console.log(chalk.gray(` Provider: ${providerName}`));
3186
1984
  } catch (err) {
3187
- console.error(chalk2.red(`Model update failed: ${err.message}`));
1985
+ console.error(chalk.red(`Model update failed: ${err.message}`));
3188
1986
  process.exit(1);
3189
1987
  }
3190
1988
  });
@@ -3196,7 +1994,7 @@ program.command("status").description("Show the current memory-core project and
3196
1994
  try {
3197
1995
  await printProjectStatus();
3198
1996
  } catch (err) {
3199
- console.error(chalk2.red(`Status failed: ${err.message}`));
1997
+ console.error(chalk.red(`Status failed: ${err.message}`));
3200
1998
  process.exit(1);
3201
1999
  }
3202
2000
  });
@@ -3204,7 +2002,7 @@ var graph = program.command("graph").description("Build and inspect dependency g
3204
2002
  graph.command("migrate").description("Create or update PostgreSQL graph snapshot schema").action(async () => {
3205
2003
  const { values } = readRuntimeEnv();
3206
2004
  if (!values.DATABASE_URL) {
3207
- console.error(chalk2.red("Graph migration requires DATABASE_URL. Configure it in .memory-core.env or .env."));
2005
+ console.error(chalk.red("Graph migration requires DATABASE_URL. Configure it in .memory-core.env or .env."));
3208
2006
  process.exit(1);
3209
2007
  }
3210
2008
  const spinner = ora("Migrating graph snapshot schema\u2026").start();
@@ -3222,34 +2020,34 @@ graph.command("doctor").description("Inspect graph storage backend health (Postg
3222
2020
  const filePath = graphStoreFilePath(cwd);
3223
2021
  const usesPostgres = Boolean(values.DATABASE_URL);
3224
2022
  let ok = true;
3225
- console.log(chalk2.bold("\n graph doctor\n"));
2023
+ console.log(chalk.bold("\n graph doctor\n"));
3226
2024
  printStatusLine("Project path", cwd);
3227
2025
  printStatusLine("Configured backend", usesPostgres ? "postgres + file fallback" : "file");
3228
2026
  printStatusLine("File store", filePath);
3229
2027
  console.log();
3230
2028
  if (!usesPostgres) {
3231
- console.log(chalk2.yellow(" \u26A0 DATABASE_URL not set \u2014 using file backend only."));
2029
+ console.log(chalk.yellow(" \u26A0 DATABASE_URL not set \u2014 using file backend only."));
3232
2030
  } else {
3233
2031
  try {
3234
2032
  await migrateGraphSnapshots();
3235
2033
  const probe = await probeGraphSnapshotStore(cwd);
3236
- console.log(chalk2.green(" \u2713 Graph PostgreSQL") + chalk2.dim(` ready (${probe.snapshotCount} snapshot records for this root)`));
2034
+ console.log(chalk.green(" \u2713 Graph PostgreSQL") + chalk.dim(` ready (${probe.snapshotCount} snapshot records for this root)`));
3237
2035
  } catch (err) {
3238
2036
  ok = false;
3239
- console.log(chalk2.red(" \u2717 Graph PostgreSQL") + chalk2.dim(` ${err.message}`));
2037
+ console.log(chalk.red(" \u2717 Graph PostgreSQL") + chalk.dim(` ${err.message}`));
3240
2038
  }
3241
2039
  }
3242
2040
  try {
3243
2041
  const count = await getGraphSnapshotCount(cwd);
3244
- console.log(chalk2.green(" \u2713 Graph service") + chalk2.dim(` readable (${count ?? 0} snapshots visible)`));
2042
+ console.log(chalk.green(" \u2713 Graph service") + chalk.dim(` readable (${count ?? 0} snapshots visible)`));
3245
2043
  } catch (err) {
3246
2044
  ok = false;
3247
- console.log(chalk2.red(" \u2717 Graph service") + chalk2.dim(` ${err.message}`));
2045
+ console.log(chalk.red(" \u2717 Graph service") + chalk.dim(` ${err.message}`));
3248
2046
  }
3249
- if (existsSync3(filePath)) {
3250
- console.log(chalk2.green(" \u2713 File fallback") + chalk2.dim(" store exists"));
2047
+ if (existsSync(filePath)) {
2048
+ console.log(chalk.green(" \u2713 File fallback") + chalk.dim(" store exists"));
3251
2049
  } else {
3252
- console.log(chalk2.yellow(" \u26A0 File fallback") + chalk2.dim(" store not created yet (run graph build once)"));
2050
+ console.log(chalk.yellow(" \u26A0 File fallback") + chalk.dim(" store not created yet (run graph build once)"));
3253
2051
  }
3254
2052
  console.log();
3255
2053
  if (!ok) process.exit(1);
@@ -3260,9 +2058,9 @@ graph.command("build").description("Build a dependency graph snapshot and persis
3260
2058
  try {
3261
2059
  const { snapshot } = await phase1.services.graphEngine.buildAndStoreSnapshot({ cwd });
3262
2060
  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}
2061
+ console.log(chalk.gray(` Root: ${snapshot.rootPath}`));
2062
+ console.log(chalk.gray(` Nodes: ${snapshot.nodes.length}`));
2063
+ console.log(chalk.gray(` Edges: ${snapshot.edges.length}
3266
2064
  `));
3267
2065
  } catch (err) {
3268
2066
  spinner.fail(`Graph build failed: ${err.message}`);
@@ -3274,16 +2072,16 @@ graph.command("list").description("List saved dependency graph snapshots").optio
3274
2072
  try {
3275
2073
  const snapshots = await phase1.services.graphEngine.listSnapshots(cwd, parseInt(opts.limit, 10));
3276
2074
  if (snapshots.length === 0) {
3277
- console.log(chalk2.yellow("\n No graph snapshots found. Run: memory-core graph build\n"));
2075
+ console.log(chalk.yellow("\n No graph snapshots found. Run: memory-core graph build\n"));
3278
2076
  return;
3279
2077
  }
3280
- console.log(chalk2.bold("\n Graph snapshots\n"));
2078
+ console.log(chalk.bold("\n Graph snapshots\n"));
3281
2079
  snapshots.forEach((snapshot, index) => {
3282
- console.log(` ${index + 1}. ${snapshot.id} ${chalk2.dim(snapshot.createdAt)} nodes=${snapshot.nodes.length} edges=${snapshot.edges.length}`);
2080
+ console.log(` ${index + 1}. ${snapshot.id} ${chalk.dim(snapshot.createdAt)} nodes=${snapshot.nodes.length} edges=${snapshot.edges.length}`);
3283
2081
  });
3284
2082
  console.log();
3285
2083
  } catch (err) {
3286
- console.error(chalk2.red(`Graph list failed: ${err.message}`));
2084
+ console.error(chalk.red(`Graph list failed: ${err.message}`));
3287
2085
  process.exit(1);
3288
2086
  }
3289
2087
  });
@@ -3292,30 +2090,30 @@ graph.command("show [snapshotId]").description("Show a saved graph snapshot (lat
3292
2090
  try {
3293
2091
  const snapshot = snapshotId ? await phase1.services.graphEngine.getSnapshot(cwd, snapshotId) : await phase1.services.graphEngine.latest(cwd);
3294
2092
  if (!snapshot) {
3295
- console.log(chalk2.yellow("\n No matching graph snapshot found. Run: memory-core graph build\n"));
2093
+ console.log(chalk.yellow("\n No matching graph snapshot found. Run: memory-core graph build\n"));
3296
2094
  return;
3297
2095
  }
3298
- console.log(chalk2.bold(`
2096
+ console.log(chalk.bold(`
3299
2097
  Graph ${snapshot.id ?? "(latest)"}
3300
2098
  `));
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}
2099
+ console.log(chalk.gray(` Root: ${snapshot.rootPath}`));
2100
+ console.log(chalk.gray(` Created: ${snapshot.createdAt ?? "unknown"}`));
2101
+ console.log(chalk.gray(` Nodes: ${snapshot.nodes.length}`));
2102
+ console.log(chalk.gray(` Edges: ${snapshot.edges.length}
3305
2103
  `));
3306
2104
  const limit = parseInt(opts.edges, 10);
3307
2105
  snapshot.edges.slice(0, limit).forEach((edge, index) => {
3308
- console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2106
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
3309
2107
  });
3310
2108
  if (snapshot.edges.length > limit) {
3311
- console.log(chalk2.dim(`
2109
+ console.log(chalk.dim(`
3312
2110
  \u2026 ${snapshot.edges.length - limit} more edges not shown
3313
2111
  `));
3314
2112
  } else {
3315
2113
  console.log();
3316
2114
  }
3317
2115
  } catch (err) {
3318
- console.error(chalk2.red(`Graph show failed: ${err.message}`));
2116
+ console.error(chalk.red(`Graph show failed: ${err.message}`));
3319
2117
  process.exit(1);
3320
2118
  }
3321
2119
  });
@@ -3324,37 +2122,37 @@ graph.command("diff <leftSnapshotId> [rightSnapshotId]").description("Diff two s
3324
2122
  try {
3325
2123
  const left = await phase1.services.graphEngine.getSnapshot(cwd, leftSnapshotId);
3326
2124
  if (!left) {
3327
- console.error(chalk2.red(`Left snapshot not found: ${leftSnapshotId}`));
2125
+ console.error(chalk.red(`Left snapshot not found: ${leftSnapshotId}`));
3328
2126
  process.exit(1);
3329
2127
  }
3330
2128
  const right = rightSnapshotId ? await phase1.services.graphEngine.getSnapshot(cwd, rightSnapshotId) : await phase1.services.graphEngine.latest(cwd);
3331
2129
  if (!right) {
3332
- console.error(chalk2.red(`Right snapshot not found${rightSnapshotId ? `: ${rightSnapshotId}` : ""}`));
2130
+ console.error(chalk.red(`Right snapshot not found${rightSnapshotId ? `: ${rightSnapshotId}` : ""}`));
3333
2131
  process.exit(1);
3334
2132
  }
3335
2133
  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}`));
2134
+ console.log(chalk.bold("\n Graph diff\n"));
2135
+ console.log(chalk.gray(` Left: ${left.id}`));
2136
+ console.log(chalk.gray(` Right: ${right.id}`));
2137
+ console.log(chalk.green(` + Nodes: ${diff.addedNodes.length}`));
2138
+ console.log(chalk.red(` - Nodes: ${diff.removedNodes.length}`));
2139
+ console.log(chalk.green(` + Edges: ${diff.addedEdges.length}`));
2140
+ console.log(chalk.red(` - Edges: ${diff.removedEdges.length}`));
3343
2141
  if (diff.addedEdges.length > 0) {
3344
- console.log(chalk2.green("\n Added edges"));
2142
+ console.log(chalk.green("\n Added edges"));
3345
2143
  diff.addedEdges.slice(0, 20).forEach((edge, index) => {
3346
- console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2144
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
3347
2145
  });
3348
2146
  }
3349
2147
  if (diff.removedEdges.length > 0) {
3350
- console.log(chalk2.red("\n Removed edges"));
2148
+ console.log(chalk.red("\n Removed edges"));
3351
2149
  diff.removedEdges.slice(0, 20).forEach((edge, index) => {
3352
- console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2150
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
3353
2151
  });
3354
2152
  }
3355
2153
  console.log();
3356
2154
  } catch (err) {
3357
- console.error(chalk2.red(`Graph diff failed: ${err.message}`));
2155
+ console.error(chalk.red(`Graph diff failed: ${err.message}`));
3358
2156
  process.exit(1);
3359
2157
  }
3360
2158
  });
@@ -3366,15 +2164,302 @@ hook.command("install").description("Install pre-commit hook (advisory mode by d
3366
2164
  hook.command("uninstall").description("Remove the pre-commit hook").action(() => {
3367
2165
  uninstallHook();
3368
2166
  });
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) => {
2167
+ hook.command("bypass-prompt").description("Prompt developer for a bypass reason and save it as a rule (called automatically by hook)").action(async () => {
2168
+ if (!process.stdin.isTTY) return;
2169
+ const bypassStats = readBypassStats();
2170
+ const unrecorded = bypassStats.withoutReason;
2171
+ if (unrecorded > 0) {
2172
+ console.log(
2173
+ chalk.yellow(`
2174
+ \u26A0 ${unrecorded} bypass${unrecorded > 1 ? "es" : ""} in this project had no reason recorded \u2014 lost signal.`)
2175
+ );
2176
+ }
2177
+ const config = readProjectConfig();
2178
+ let reason = await input({
2179
+ message: chalk.yellow("Why are you bypassing the hook?"),
2180
+ default: ""
2181
+ });
2182
+ if (!reason.trim()) {
2183
+ reason = await input({
2184
+ message: chalk.dim(" Skipping loses signal. Enter a reason or leave blank to proceed:"),
2185
+ default: ""
2186
+ });
2187
+ }
2188
+ const hadReason = reason.trim().length > 0;
2189
+ const stats = recordBypass(hadReason);
2190
+ if (!hadReason) {
2191
+ console.log(
2192
+ chalk.dim(` Bypass recorded without reason. (${stats.withoutReason} total unrecorded \u2014 run: memory-core stats)
2193
+ `)
2194
+ );
2195
+ await closePool();
2196
+ return;
2197
+ }
2198
+ try {
2199
+ await phase1.services.memoryEngine.remember({
2200
+ type: "rule",
2201
+ scope: "project",
2202
+ architecture: config?.backendArchitecture ?? config?.frontendFramework,
2203
+ projectName: config?.projectName,
2204
+ content: reason.trim(),
2205
+ reason: "Captured automatically from hook bypass"
2206
+ });
2207
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
2208
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2209
+ console.log(chalk.green(` \u2713 Bypass reason saved: "${reason.trim()}"
2210
+ `));
2211
+ } catch {
2212
+ }
2213
+ await closePool();
2214
+ });
2215
+ 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").option("--diff <ref>", "Check a git diff for architecture violations (evidence-based)").option("--second-opinion", "Run a second critique pass for uncertain decisions (use with --diff)").option("--json", "Output full JSON result (use with --diff)").action(async (opts) => {
2216
+ if (opts.diff !== void 0) {
2217
+ const { values: envValues } = readRuntimeEnv();
2218
+ applyRuntimeEnv(envValues);
2219
+ const startMs = Date.now();
2220
+ const diffRef = opts.diff || "HEAD~1";
2221
+ const diag = opts.json ? (msg) => process.stderr.write(msg + "\n") : (msg) => console.log(msg);
2222
+ const diagSpinner = (text) => ora({ text, stream: opts.json ? process.stderr : process.stdout });
2223
+ const { CheckRateLimiter } = await import("./rate-limiter-SLIPCXRF.js");
2224
+ const release = await new CheckRateLimiter(process.cwd()).acquire();
2225
+ process.on("exit", release);
2226
+ const { FileClassifier } = await import("./classifier-MZ65R7FK.js");
2227
+ const { ASTAnalyzer } = await import("./ast-analyzer-JM4CIOFY.js");
2228
+ const { GraphBuilder } = await import("./graph-TFNTB5OK.js");
2229
+ const { RuleMatcher } = await import("./rules-V3QMN3AR.js");
2230
+ const { EvidencePacketBuilder } = await import("./evidence-HVMSONTT.js");
2231
+ const { DeterministicValidator } = await import("./deterministic-validator-PP56B46I.js");
2232
+ const { OllamaJudge } = await import("./ollama-judge-D2LFK5PB.js");
2233
+ const { DeepSeekCritique } = await import("./deepseek-critique-MALVIYGF.js");
2234
+ const { ConfidenceGate, defaultGateConfig } = await import("./confidence-gate-ZQDAOS6P.js");
2235
+ const { CheckCache } = await import("./check-cache-6NWRTZJD.js");
2236
+ const { CheckLogger } = await import("./check-logger-5HYSWA3S.js");
2237
+ const configDir = getArchmindDir();
2238
+ const classifier = FileClassifier.loadFromConfig(configDir);
2239
+ const ruleMatcher = RuleMatcher.loadFromConfig(configDir);
2240
+ const astAnalyzer = new ASTAnalyzer(process.cwd());
2241
+ const graphBuilder = new GraphBuilder(astAnalyzer, classifier, process.cwd());
2242
+ const logger = new CheckLogger(process.cwd());
2243
+ let diffOutput = "";
2244
+ try {
2245
+ const result = spawnSync2("git", ["diff", "--name-status", diffRef], { encoding: "utf-8" });
2246
+ if (result.status !== 0) throw new Error(result.stderr);
2247
+ diffOutput = result.stdout;
2248
+ } catch {
2249
+ console.error("Could not run git diff against " + diffRef);
2250
+ process.exit(1);
2251
+ }
2252
+ const added = [];
2253
+ const modified = [];
2254
+ const deleted = [];
2255
+ for (const line of diffOutput.split("\n").filter(Boolean)) {
2256
+ const [status, ...rest] = line.split(" ");
2257
+ const file = rest.join(" ").trim();
2258
+ if (!file) continue;
2259
+ if (status.startsWith("A")) added.push(file);
2260
+ else if (status.startsWith("D")) deleted.push(file);
2261
+ else modified.push(file);
2262
+ }
2263
+ const allChanged = [...added, ...modified].filter((f) => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(f));
2264
+ const graph2 = await graphBuilder.buildFromFiles(allChanged);
2265
+ const builder = new EvidencePacketBuilder(process.cwd());
2266
+ const packet = await builder.build({ added, modified, deleted }, astAnalyzer, classifier, ruleMatcher, graph2);
2267
+ try {
2268
+ const fullGraph = await phase1.services.graphEngine.latest(process.cwd());
2269
+ if (fullGraph) {
2270
+ const snapshotAge = fullGraph.createdAt ? Date.now() - new Date(fullGraph.createdAt).getTime() : null;
2271
+ const STALE_MS = 24 * 60 * 60 * 1e3;
2272
+ if (snapshotAge !== null && snapshotAge > STALE_MS) {
2273
+ const hours = Math.round(snapshotAge / 36e5);
2274
+ diag(chalk.yellow(` \u26A0 Graph snapshot is ${hours}h old \u2014 run \`memory-core graph build\` to refresh`));
2275
+ }
2276
+ const changedSet = /* @__PURE__ */ new Set([...added, ...modified]);
2277
+ for (const edge of fullGraph.edges) {
2278
+ if (!changedSet.has(edge.from) || edge.to.startsWith("pkg:")) continue;
2279
+ const fromClass = classifier.classifyFile(edge.from);
2280
+ const toClass = classifier.classifyFile(edge.to);
2281
+ if (!fromClass || !toClass) continue;
2282
+ const violating = ruleMatcher.findViolatingRules(fromClass.layer, toClass.layer);
2283
+ for (const rule of violating) {
2284
+ const alreadyReported = packet.graphViolations.some(
2285
+ (v) => v.from === edge.from && v.to === edge.to && v.ruleName === rule.name
2286
+ );
2287
+ if (!alreadyReported) {
2288
+ packet.graphViolations.push({
2289
+ from: edge.from,
2290
+ fromLayer: fromClass.layer,
2291
+ to: edge.to,
2292
+ toLayer: toClass.layer,
2293
+ ruleName: rule.name,
2294
+ path: [edge.from, edge.to]
2295
+ });
2296
+ }
2297
+ }
2298
+ }
2299
+ } else {
2300
+ diag(chalk.yellow(" \u26A0 No graph snapshot \u2014 building now for full codebase enforcement\u2026"));
2301
+ const autoSpinner = diagSpinner(" Building dependency graph\u2026").start();
2302
+ try {
2303
+ const buildResult = await Promise.race([
2304
+ phase1.services.graphEngine.buildAndStoreSnapshot({ cwd: process.cwd() }),
2305
+ new Promise((_, reject) => setTimeout(() => reject(new Error("graph build timeout")), 15e3))
2306
+ ]);
2307
+ autoSpinner.succeed(` Graph snapshot ready (${buildResult.snapshot.nodes.length} nodes, ${buildResult.snapshot.edges.length} edges)`);
2308
+ const freshGraph = await phase1.services.graphEngine.latest(process.cwd());
2309
+ if (freshGraph) {
2310
+ const changedSet2 = /* @__PURE__ */ new Set([...added, ...modified]);
2311
+ for (const edge of freshGraph.edges) {
2312
+ if (!changedSet2.has(edge.from) || edge.to.startsWith("pkg:")) continue;
2313
+ const fromClass = classifier.classifyFile(edge.from);
2314
+ const toClass = classifier.classifyFile(edge.to);
2315
+ if (!fromClass || !toClass) continue;
2316
+ const violating = ruleMatcher.findViolatingRules(fromClass.layer, toClass.layer);
2317
+ for (const rule of violating) {
2318
+ const alreadyReported = packet.graphViolations.some(
2319
+ (v) => v.from === edge.from && v.to === edge.to && v.ruleName === rule.name
2320
+ );
2321
+ if (!alreadyReported) {
2322
+ packet.graphViolations.push({
2323
+ from: edge.from,
2324
+ fromLayer: fromClass.layer,
2325
+ to: edge.to,
2326
+ toLayer: toClass.layer,
2327
+ ruleName: rule.name,
2328
+ path: [edge.from, edge.to]
2329
+ });
2330
+ }
2331
+ }
2332
+ }
2333
+ }
2334
+ } catch (e) {
2335
+ autoSpinner.stop();
2336
+ const msg = e instanceof Error ? e.message : String(e);
2337
+ diag(chalk.dim(` (auto-build skipped: ${msg} \u2014 run \`memory-core graph build\` manually)`));
2338
+ }
2339
+ }
2340
+ } catch {
2341
+ }
2342
+ const det = new DeterministicValidator();
2343
+ if (det.validate(packet) === "block") {
2344
+ const out = {
2345
+ decision: "block",
2346
+ source: "deterministic",
2347
+ confidence: 1,
2348
+ violations: packet.violations.map((v) => ({ rule: v.rule.name, from: v.fromFile, to: v.toFile })),
2349
+ graphViolations: packet.graphViolations,
2350
+ ...opts.json ? { evidence: packet } : {}
2351
+ };
2352
+ console.log(JSON.stringify(out, null, 2));
2353
+ logger.append({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ref: diffRef, decision: "block", source: "deterministic", confidence: 1, violationCount: packet.violations.length, graphViolationCount: packet.graphViolations.length, changedFiles: allChanged.length, durationMs: Date.now() - startMs, fast: opts.fast });
2354
+ process.exit(1);
2355
+ }
2356
+ if (opts.fast) {
2357
+ const out = { decision: "allow", source: "deterministic", confidence: 1, violations: [], graphViolations: packet.graphViolations };
2358
+ if (opts.json) {
2359
+ console.log(JSON.stringify(out, null, 2));
2360
+ } else {
2361
+ diag(chalk.green("\n Decision: ALLOW") + chalk.dim(" (source: deterministic+fast, confidence: 1.00)"));
2362
+ }
2363
+ logger.append({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ref: diffRef, decision: "allow", source: "deterministic", confidence: 1, violationCount: 0, graphViolationCount: packet.graphViolations.length, changedFiles: allChanged.length, durationMs: Date.now() - startMs, fast: true });
2364
+ return;
2365
+ }
2366
+ let storedMemories = [];
2367
+ try {
2368
+ const { listMemories } = await import("./db-PRDHI2CN.js");
2369
+ const allMemories = await listMemories({ limit: 1e4 });
2370
+ const ranked = allMemories.filter((m) => ["rule", "pattern", "decision"].includes(m.type));
2371
+ const changedLayers = packet.layersAffected;
2372
+ const scored = ranked.map((m) => {
2373
+ const text = m.content.toLowerCase();
2374
+ const score = changedLayers.reduce((s, l) => s + (text.includes(l) ? 2 : 0), 0);
2375
+ return { m, score };
2376
+ });
2377
+ scored.sort((a, b) => b.score - a.score);
2378
+ storedMemories = scored.slice(0, 30).map(({ m }) => ({
2379
+ type: m.type,
2380
+ content: m.content,
2381
+ reason: m.reason ?? void 0
2382
+ }));
2383
+ } catch {
2384
+ }
2385
+ const cache = new CheckCache(process.cwd());
2386
+ const mHash = cache.memoriesHash(storedMemories);
2387
+ const rHash = cache.rulesHash(configDir);
2388
+ const cacheKey = cache.key(diffOutput, mHash, rHash);
2389
+ const cached = !opts.secondOpinion && cache.get(cacheKey);
2390
+ if (cached) {
2391
+ diag(chalk.dim(" (cached result)"));
2392
+ const output2 = {
2393
+ decision: cached.decision,
2394
+ source: cached.source + "+cache",
2395
+ violations: cached.violations,
2396
+ suggestedFix: cached.suggestedFix,
2397
+ reasoning: cached.reasoning,
2398
+ confidence: cached.confidence
2399
+ };
2400
+ if (opts.json) {
2401
+ console.log(JSON.stringify(output2, null, 2));
2402
+ } else {
2403
+ const color = cached.decision === "block" ? chalk.red : cached.decision === "warn" ? chalk.yellow : chalk.green;
2404
+ diag(color(`
2405
+ Decision: ${cached.decision.toUpperCase()}`) + chalk.dim(` (source: ${cached.source}+cache, confidence: ${cached.confidence.toFixed(2)})`));
2406
+ }
2407
+ logger.append({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ref: diffRef, decision: cached.decision, source: cached.source + "+cache", confidence: cached.confidence, violationCount: cached.violations.length, graphViolationCount: packet.graphViolations.length, changedFiles: allChanged.length, durationMs: Date.now() - startMs, cached: true });
2408
+ if (cached.decision === "block") process.exit(1);
2409
+ return;
2410
+ }
2411
+ const chatModel = process.env.CHAT_MODEL ?? process.env.OLLAMA_CHAT_MODEL ?? "unknown";
2412
+ const provider2 = process.env.CHAT_PROVIDER ?? "ollama";
2413
+ const spinner = diagSpinner(` Analyzing with ${provider2}/${chatModel}\u2026`).start();
2414
+ const primaryDecision = await new OllamaJudge().judge(packet, storedMemories);
2415
+ spinner.stop();
2416
+ const critiqueEnabled = opts.secondOpinion ?? false;
2417
+ const critiqueFn = critiqueEnabled ? async (p) => new DeepSeekCritique().critique(p, primaryDecision) : async () => ({ decision: primaryDecision.decision, confidence: primaryDecision.confidence, reasoning: "skipped", override: false });
2418
+ const gateDecision = await new ConfidenceGate(defaultGateConfig(critiqueEnabled)).decide(packet, primaryDecision, critiqueFn);
2419
+ cache.set(cacheKey, {
2420
+ decision: gateDecision.final,
2421
+ source: gateDecision.source,
2422
+ confidence: primaryDecision.confidence,
2423
+ violations: primaryDecision.violations,
2424
+ reasoning: primaryDecision.reasoning,
2425
+ suggestedFix: primaryDecision.suggestedFix
2426
+ });
2427
+ const output = {
2428
+ decision: gateDecision.final,
2429
+ source: gateDecision.source,
2430
+ violations: primaryDecision.violations,
2431
+ suggestedFix: primaryDecision.suggestedFix,
2432
+ reasoning: primaryDecision.reasoning,
2433
+ confidence: primaryDecision.confidence,
2434
+ ...opts.json ? { judge: primaryDecision, critique: gateDecision.deepseek, selfReview: gateDecision.selfReview, evidence: packet } : {}
2435
+ };
2436
+ if (opts.json) {
2437
+ console.log(JSON.stringify(output, null, 2));
2438
+ } else {
2439
+ const color = gateDecision.final === "block" ? chalk.red : gateDecision.final === "warn" ? chalk.yellow : chalk.green;
2440
+ diag(color(`
2441
+ Decision: ${gateDecision.final.toUpperCase()}`) + chalk.dim(` (source: ${gateDecision.source}, confidence: ${primaryDecision.confidence.toFixed(2)})`));
2442
+ if (primaryDecision.violations.length > 0) diag(chalk.dim(` Violations: ${primaryDecision.violations.join(", ")}`));
2443
+ if (primaryDecision.suggestedFix) diag(chalk.dim(` Fix: ${primaryDecision.suggestedFix}`));
2444
+ diag("");
2445
+ }
2446
+ logger.append({ timestamp: (/* @__PURE__ */ new Date()).toISOString(), ref: diffRef, decision: gateDecision.final, source: gateDecision.source, confidence: primaryDecision.confidence, violationCount: primaryDecision.violations.length, graphViolationCount: packet.graphViolations.length, changedFiles: allChanged.length, durationMs: Date.now() - startMs });
2447
+ if (gateDecision.final === "block") process.exit(1);
2448
+ return;
2449
+ }
2450
+ if (opts.file) {
2451
+ await checkFile(opts.file, { verbose: opts.verbose ?? false, debug: opts.debug ?? false, fast: opts.fast ?? false, dryRun: opts.dryRun ?? false });
2452
+ await closePool();
2453
+ return;
2454
+ }
3370
2455
  if (opts.commitMsg !== void 0) {
3371
- const msgFile = typeof opts.commitMsg === "string" ? opts.commitMsg : join3(process.cwd(), ".git", "COMMIT_EDITMSG");
2456
+ const msgFile = typeof opts.commitMsg === "string" ? opts.commitMsg : join(process.cwd(), ".git", "COMMIT_EDITMSG");
3372
2457
  await checkCommitMsg(msgFile, { verbose: opts.verbose ?? false, debug: opts.debug ?? false });
3373
2458
  await closePool();
3374
2459
  return;
3375
2460
  }
3376
2461
  if (opts.ci && opts.all) {
3377
- console.error(chalk2.red("\n Choose one mode: --ci or --all.\n"));
2462
+ console.error(chalk.red("\n Choose one mode: --ci or --all.\n"));
3378
2463
  process.exit(1);
3379
2464
  }
3380
2465
  if (opts.ci) {
@@ -3390,14 +2475,212 @@ program.command("check").description("Check staged changes against architecture
3390
2475
  if (summary.violations > 0) process.exit(1);
3391
2476
  return;
3392
2477
  }
3393
- await checkStaged({ verbose: opts.verbose ?? false, debug: opts.debug ?? false, fast: opts.fast ?? false });
2478
+ await checkStaged({ verbose: opts.verbose ?? false, debug: opts.debug ?? false, fast: opts.fast ?? false, dryRun: opts.dryRun ?? false });
3394
2479
  });
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) => {
2480
+ var schema = program.command("schema").description("Manage schema alignment rules between TypeScript and Go files");
2481
+ 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) => {
2482
+ const config = readProjectConfig();
2483
+ const content = JSON.stringify({ tsFile: opts.ts, goFile: opts.go });
2484
+ const spinner = ora("Saving schema rule\u2026").start();
2485
+ try {
2486
+ await phase1.services.memoryEngine.remember({
2487
+ type: "schema",
2488
+ scope: "project",
2489
+ architecture: config?.backendArchitecture ?? config?.frontendFramework,
2490
+ projectName: config?.projectName,
2491
+ content,
2492
+ reason: "Schema alignment rule \u2014 TypeScript and Go structs must stay in sync"
2493
+ });
2494
+ const dbVersionPath = join(process.cwd(), ".memory-core-db-version");
2495
+ writeFileSync(dbVersionPath, (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf-8");
2496
+ spinner.succeed(chalk.green(`Schema rule saved: ${opts.ts} \u2194 ${opts.go}`));
2497
+ } catch (err) {
2498
+ spinner.fail(`Failed: ${err.message}`);
2499
+ process.exit(1);
2500
+ }
2501
+ await closePool();
2502
+ });
2503
+ schema.command("list").description("List all schema alignment rules").action(async () => {
2504
+ const memories = await phase1.services.memoryEngine.list({ type: "schema", limit: 100 });
2505
+ if (memories.length === 0) {
2506
+ console.log(chalk.dim("\n No schema rules defined. Use: memory-core schema add --ts <file> --go <file>\n"));
2507
+ await closePool();
2508
+ return;
2509
+ }
2510
+ console.log(chalk.cyan(`
2511
+ ${memories.length} schema rule${memories.length > 1 ? "s" : ""}:
2512
+ `));
2513
+ for (const m of memories) {
2514
+ try {
2515
+ const rule = JSON.parse(m.content);
2516
+ console.log(chalk.white(` ${rule.tsFile}`) + chalk.dim(" \u2194 ") + chalk.white(rule.goFile));
2517
+ } catch {
2518
+ console.log(chalk.dim(` ${m.content}`));
2519
+ }
2520
+ }
2521
+ console.log();
2522
+ await closePool();
2523
+ });
2524
+ schema.command("check").description("Run schema alignment checks against all tracked file pairs").action(async () => {
2525
+ const app = getDefaultApplicationContainer();
2526
+ const violations = await findSchemaViolations({
2527
+ cwd: process.cwd(),
2528
+ memoryEngine: app.services.memoryEngine
2529
+ });
2530
+ if (violations.length === 0) {
2531
+ console.log(chalk.green("\n \u2713 All schema rules pass \u2014 TypeScript and Go are in sync.\n"));
2532
+ } else {
2533
+ console.log(chalk.red.bold(`
2534
+ \u2717 ${violations.length} schema violation${violations.length > 1 ? "s" : ""} found
2535
+ `));
2536
+ for (const v of violations) {
2537
+ console.log(chalk.bold(` ${v.file}`));
2538
+ console.log(chalk.yellow(" Rule: ") + v.rule);
2539
+ console.log(chalk.red(" Issue: ") + v.issue);
2540
+ if (v.suggestion) console.log(chalk.green(" Fix: ") + v.suggestion);
2541
+ console.log();
2542
+ }
2543
+ process.exit(1);
2544
+ }
2545
+ await closePool();
2546
+ });
2547
+ 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
2548
  await phase1.providers.watchService.start({
3397
2549
  path: opts.path,
3398
2550
  scanOnStart: opts.scanOnStart,
2551
+ autoFix: opts.autoFix,
3399
2552
  verbose: opts.verbose,
3400
2553
  debug: opts.debug
3401
2554
  });
3402
2555
  });
2556
+ var ARCHMIND_DIR = join(process.cwd(), ".archmind");
2557
+ var DEFAULT_ARCH_RULES = {
2558
+ rules: [
2559
+ { id: "domain-no-infra", name: "Domain must not import infrastructure", fromLayer: "domain", toLayer: "infrastructure", allowed: false, severity: "critical", enforcement: "block" },
2560
+ { id: "domain-no-api", name: "Domain must not import API layer", fromLayer: "domain", toLayer: "api", allowed: false, severity: "critical", enforcement: "block" },
2561
+ { id: "api-no-infra-direct", name: "API should not import infrastructure directly", fromLayer: "api", toLayer: "infrastructure", allowed: false, severity: "medium", enforcement: "warn" },
2562
+ { id: "api-depends-on-domain", name: "API depends on domain", fromLayer: "api", toLayer: "domain", allowed: true, severity: "low", enforcement: "suggest" },
2563
+ { id: "no-circular", name: "No circular dependencies", fromLayer: "*", toLayer: "*", allowed: false, severity: "critical", enforcement: "block" },
2564
+ { id: "application-no-api", name: "Application must not import API layer", fromLayer: "application", toLayer: "api", allowed: false, severity: "critical", enforcement: "block" },
2565
+ { id: "application-uses-domain", name: "Application depends on domain", fromLayer: "application", toLayer: "domain", allowed: true, severity: "low", enforcement: "suggest" },
2566
+ { id: "shared-no-domain", name: "Shared must not import domain", fromLayer: "shared", toLayer: "domain", allowed: false, severity: "medium", enforcement: "warn" }
2567
+ ]
2568
+ };
2569
+ function buildLayersTemplate(arch2) {
2570
+ const mvc = ["nestjs", "mvc", "laravel", "go-api"].includes(arch2);
2571
+ const frontend = ["react", "vue", "angular", "svelte", "nuxt", "react-native"].includes(arch2);
2572
+ if (frontend) {
2573
+ return {
2574
+ layers: [
2575
+ { name: "domain", paths: ["src/domain/**", "src/store/**", "src/state/**"], description: "State, models, business logic" },
2576
+ { name: "application", paths: ["src/services/**", "src/hooks/**", "src/composables/**"], description: "App services and hooks" },
2577
+ { name: "infrastructure", paths: ["src/api/**", "src/lib/**", "src/clients/**"], description: "External API clients, adapters" },
2578
+ { name: "shared", paths: ["src/utils/**", "src/helpers/**", "src/constants/**"], description: "Shared utilities" }
2579
+ ]
2580
+ };
2581
+ }
2582
+ if (mvc) {
2583
+ return {
2584
+ layers: [
2585
+ { name: "domain", paths: ["src/models/**", "src/entities/**"], description: "Data models and entities" },
2586
+ { name: "application", paths: ["src/services/**"], description: "Business logic services" },
2587
+ { name: "infrastructure", paths: ["src/repositories/**", "src/database/**", "src/infrastructure/**"], description: "DB, external APIs" },
2588
+ { name: "api", paths: ["src/controllers/**", "src/routes/**", "src/handlers/**"], description: "HTTP handlers" },
2589
+ { name: "shared", paths: ["src/utils/**", "src/helpers/**", "src/shared/**"], description: "Shared utilities" }
2590
+ ]
2591
+ };
2592
+ }
2593
+ return {
2594
+ layers: [
2595
+ { name: "domain", paths: ["src/domain/**", "src/core/domain/**", "src/modules/*/domain/**"], description: "Business logic, entities, use cases" },
2596
+ { name: "application", paths: ["src/application/**", "src/core/application/**", "src/modules/*/application/**"], description: "Use cases and orchestration" },
2597
+ { name: "infrastructure", paths: ["src/infrastructure/**", "src/modules/*/infrastructure/**"], description: "Database, external APIs, low-level I/O" },
2598
+ { name: "api", paths: ["src/api/**", "src/interfaces/**", "src/controllers/**"], description: "HTTP handlers, request/response" },
2599
+ { name: "shared", paths: ["src/shared/**", "src/utils/**", "src/helpers/**"], description: "Utilities, helpers, constants" }
2600
+ ]
2601
+ };
2602
+ }
2603
+ function getArchmindDir() {
2604
+ return ARCHMIND_DIR;
2605
+ }
2606
+ var arch = program.command("arch").description("Architecture enforcement: check, review captured rules, watch for errors, log incidents");
2607
+ arch.command("review").description("Review and approve/reject auto-captured architecture rules").option("--approve-all", "Approve everything without prompting").option("--reject-all", "Reject everything without prompting").action(async (opts) => {
2608
+ const { ApprovalQueue } = await import("./approval-queue-YBYRGBHP.js");
2609
+ const { join: join2 } = await import("path");
2610
+ const configDir = getArchmindDir();
2611
+ const queue = new ApprovalQueue(configDir);
2612
+ const pending = queue.pending();
2613
+ if (pending.length === 0) {
2614
+ console.log(chalk.dim(" No pending rules."));
2615
+ return;
2616
+ }
2617
+ console.log(chalk.bold(`
2618
+ Pending rules (${pending.length}):
2619
+ `));
2620
+ if (opts.approveAll) {
2621
+ for (const item of pending) queue.approve(item.id);
2622
+ const count2 = queue.persistApproved(join2(configDir, "rules.json"));
2623
+ console.log(chalk.green(` \u2713 Approved and saved ${count2} rules`));
2624
+ return;
2625
+ }
2626
+ if (opts.rejectAll) {
2627
+ for (const item of pending) queue.reject(item.id);
2628
+ console.log(chalk.yellow(` \u2717 Rejected ${pending.length} rules`));
2629
+ return;
2630
+ }
2631
+ for (const item of pending) {
2632
+ console.log(chalk.cyan(` [${item.source.toUpperCase()}]`) + ` ${item.rule.name}`);
2633
+ console.log(chalk.dim(` ${item.rule.description}`));
2634
+ console.log(chalk.dim(` captured: ${item.capturedAt} confidence: ${item.confidence}`));
2635
+ const { confirm: confirm2 } = await import("@inquirer/prompts");
2636
+ const accept = await confirm2({ message: " Keep this rule?" });
2637
+ accept ? queue.approve(item.id) : queue.reject(item.id);
2638
+ console.log(accept ? chalk.green(" \u2713 Kept") : chalk.yellow(" \u2717 Dropped"));
2639
+ console.log();
2640
+ }
2641
+ const count = queue.persistApproved(join2(configDir, "rules.json"));
2642
+ if (count > 0) console.log(chalk.green(` \u2713 Saved ${count} rules to .archmind/rules.json`));
2643
+ });
2644
+ arch.command("watch").description("Watch your dev processes and auto-draft rules from errors").action(async () => {
2645
+ const { WatchErrors, loadWatchConfig } = await import("./watch-errors-B3FA26N4.js");
2646
+ const configDir = getArchmindDir();
2647
+ const config = loadWatchConfig(configDir);
2648
+ console.log(chalk.cyan("\n Watching:"));
2649
+ for (const cmd of config.commands) console.log(chalk.dim(` \u2022 ${cmd}`));
2650
+ console.log(chalk.dim("\n Press Ctrl+C to stop.\n"));
2651
+ const watcher = new WatchErrors(config, configDir);
2652
+ watcher.start((rule) => {
2653
+ console.log(chalk.yellow(` [DRAFT] ${rule.name}`));
2654
+ console.log(chalk.dim(` ${rule.description.slice(0, 100)}`));
2655
+ console.log(chalk.dim(' Run "memory-core arch review" to approve.\n'));
2656
+ });
2657
+ process.on("SIGINT", () => {
2658
+ watcher.stop();
2659
+ console.log(chalk.dim("\n Stopped."));
2660
+ process.exit(0);
2661
+ });
2662
+ await new Promise(() => {
2663
+ });
2664
+ });
2665
+ arch.command("incident").description("Log a production incident and capture it as an architecture rule").action(async () => {
2666
+ const { input: input2, confirm: confirm2 } = await import("@inquirer/prompts");
2667
+ const { IncidentCaptureService } = await import("./incident-capture-RVPZULS7.js");
2668
+ const { ApprovalQueue } = await import("./approval-queue-YBYRGBHP.js");
2669
+ console.log(chalk.cyan("\n Incident capture\n"));
2670
+ const what = await input2({ message: "What broke?" });
2671
+ const why = await input2({ message: "Root cause?" });
2672
+ const where = await input2({ message: "Which file / layer / pattern?" });
2673
+ const rule = new IncidentCaptureService().draftRule({ what, why, where, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
2674
+ console.log(chalk.dim(`
2675
+ Rule: ${rule.name}`));
2676
+ console.log(chalk.dim(` Description: ${rule.description}
2677
+ `));
2678
+ const save = await confirm2({ message: "Save to approval queue?" });
2679
+ if (save) {
2680
+ new ApprovalQueue(getArchmindDir()).add(rule, "incident", "high");
2681
+ console.log(chalk.green(' \u2713 Saved. Run "memory-core arch review" to approve.\n'));
2682
+ } else {
2683
+ console.log(chalk.dim(" Discarded.\n"));
2684
+ }
2685
+ });
3403
2686
  program.parseAsync(process.argv);