@shahmilsaari/memory-core 0.2.12 → 0.2.13

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.
@@ -82,8 +82,17 @@ async function listMemories(filters = {}) {
82
82
  where.push(`scope = $${params.length}`);
83
83
  }
84
84
  if (filters.architecture) {
85
- params.push(filters.architecture);
86
- where.push(`architecture = $${params.length}`);
85
+ if (Array.isArray(filters.architecture)) {
86
+ params.push(filters.architecture);
87
+ where.push(`architecture = ANY($${params.length})`);
88
+ } else {
89
+ params.push(filters.architecture);
90
+ where.push(`architecture = $${params.length}`);
91
+ }
92
+ }
93
+ if (filters.tags?.length) {
94
+ params.push(filters.tags);
95
+ where.push(`tags && $${params.length}::text[]`);
87
96
  }
88
97
  const limit = filters.limit ?? 200;
89
98
  params.push(limit);
@@ -112,6 +121,40 @@ async function deleteMemory(id) {
112
121
  const result = await getPool().query(`DELETE FROM memories WHERE id = $1`, [id]);
113
122
  return (result.rowCount ?? 0) > 0;
114
123
  }
124
+ async function deleteMemories(filters) {
125
+ await runMigrations();
126
+ const where = [];
127
+ const params = [];
128
+ if (filters.type) {
129
+ params.push(filters.type);
130
+ where.push(`type = $${params.length}`);
131
+ }
132
+ if (filters.scope) {
133
+ params.push(filters.scope);
134
+ where.push(`scope = $${params.length}`);
135
+ }
136
+ if (filters.architecture) {
137
+ if (Array.isArray(filters.architecture)) {
138
+ params.push(filters.architecture);
139
+ where.push(`architecture = ANY($${params.length})`);
140
+ } else {
141
+ params.push(filters.architecture);
142
+ where.push(`architecture = $${params.length}`);
143
+ }
144
+ }
145
+ if (filters.tag) {
146
+ params.push(filters.tag);
147
+ where.push(`$${params.length} = ANY(tags)`);
148
+ }
149
+ if (where.length === 0) {
150
+ throw new Error("Refusing to bulk-delete without filters");
151
+ }
152
+ const result = await getPool().query(
153
+ `DELETE FROM memories WHERE ${where.join(" AND ")}`,
154
+ params
155
+ );
156
+ return result.rowCount ?? 0;
157
+ }
115
158
  async function updateMemory(id, patch) {
116
159
  await runMigrations();
117
160
  const current = await getMemory(id);
@@ -145,14 +188,19 @@ async function updateMemory(id, patch) {
145
188
  );
146
189
  return result.rows[0] ?? null;
147
190
  }
148
- async function searchMemories(embedding, architecture, limit = 10) {
191
+ async function searchMemories(embedding, architectures, limit = 10) {
149
192
  await runMigrations();
150
193
  const vector = `[${embedding.join(",")}]`;
151
194
  const params = [vector];
152
195
  let whereClause = "";
153
- if (architecture) {
154
- whereClause = `WHERE (architecture = $2 OR scope = 'global')`;
155
- params.push(architecture);
196
+ const selectedArchitectures = architectures ? (Array.isArray(architectures) ? architectures : [architectures]).filter(Boolean) : [];
197
+ if (selectedArchitectures.length > 0) {
198
+ whereClause = `WHERE (
199
+ architecture = ANY($2)
200
+ OR architecture IS NULL
201
+ OR architecture = 'global'
202
+ )`;
203
+ params.push(selectedArchitectures);
156
204
  }
157
205
  const client = await getPool().connect();
158
206
  try {
@@ -190,6 +238,7 @@ export {
190
238
  listMemories,
191
239
  getMemory,
192
240
  deleteMemory,
241
+ deleteMemories,
193
242
  updateMemory,
194
243
  searchMemories,
195
244
  closePool
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  } from "./chunk-HAGRPKR3.js";
5
5
  import {
6
6
  closePool,
7
+ deleteMemories,
7
8
  deleteMemory,
8
9
  getMemory,
9
10
  listMemories,
@@ -12,7 +13,7 @@ import {
12
13
  searchMemories,
13
14
  updateMemory,
14
15
  upsertMemory
15
- } from "./chunk-73SRPNAL.js";
16
+ } from "./chunk-25Y2KI7M.js";
16
17
  import "./chunk-KSLFLWB4.js";
17
18
 
18
19
  // src/cli.ts
@@ -26,14 +27,161 @@ import { homedir } from "os";
26
27
  import { execSync as execSync2 } from "child_process";
27
28
 
28
29
  // src/generator.ts
29
- import { readFileSync, readdirSync, writeFileSync, mkdirSync, existsSync } from "fs";
30
- import { join, dirname } from "path";
30
+ import { readFileSync as readFileSync2, readdirSync, writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
31
+ import { join as join2, dirname } from "path";
31
32
  import { fileURLToPath } from "url";
32
33
  import Handlebars from "handlebars";
33
34
  import yaml from "js-yaml";
35
+
36
+ // src/project-detector.ts
37
+ import { existsSync, readFileSync } from "fs";
38
+ import { join } from "path";
39
+ function detectProject(cwd = process.cwd()) {
40
+ const has = (file) => existsSync(join(cwd, file));
41
+ const readJson = (file) => {
42
+ try {
43
+ return JSON.parse(readFileSync(join(cwd, file), "utf-8"));
44
+ } catch {
45
+ return {};
46
+ }
47
+ };
48
+ if (has("next.config.js") || has("next.config.ts") || has("next.config.mjs")) {
49
+ return { language: "TypeScript", framework: "Next.js" };
50
+ }
51
+ if (has("artisan") && has("composer.json")) {
52
+ return { language: "PHP", framework: "Laravel" };
53
+ }
54
+ if (has("nuxt.config.ts") || has("nuxt.config.js")) {
55
+ return { language: "TypeScript", framework: "Nuxt.js" };
56
+ }
57
+ if (has("manage.py")) {
58
+ if (has("requirements.txt")) {
59
+ const req = readFileSync(join(cwd, "requirements.txt"), "utf-8");
60
+ if (req.includes("djangorestframework")) {
61
+ return { language: "Python", framework: "Django REST Framework" };
62
+ }
63
+ }
64
+ return { language: "Python", framework: "Django" };
65
+ }
66
+ if (has("go.mod")) {
67
+ return { language: "Go", framework: "Go" };
68
+ }
69
+ if (has("Cargo.toml")) {
70
+ return { language: "Rust", framework: "Rust" };
71
+ }
72
+ if (has("pubspec.yaml")) {
73
+ return { language: "Dart", framework: "Flutter" };
74
+ }
75
+ if (has("pom.xml")) {
76
+ return { language: "Java", framework: "Spring Boot" };
77
+ }
78
+ if (has("build.gradle") || has("build.gradle.kts")) {
79
+ return { language: "Kotlin", framework: "Kotlin/JVM" };
80
+ }
81
+ if (has("package.json")) {
82
+ const pkg = readJson("package.json");
83
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
84
+ if (deps["@nestjs/core"]) return { language: "TypeScript", framework: "NestJS" };
85
+ if (deps["express"]) return { language: "TypeScript", framework: "Express.js" };
86
+ if (deps["fastify"]) return { language: "TypeScript", framework: "Fastify" };
87
+ if (deps["react"]) return { language: "TypeScript", framework: "React" };
88
+ if (deps["vue"]) return { language: "TypeScript", framework: "Vue.js" };
89
+ if (deps["svelte"]) return { language: "TypeScript", framework: "Svelte" };
90
+ return { language: "TypeScript/JavaScript", framework: "Node.js" };
91
+ }
92
+ if (has("requirements.txt") || has("pyproject.toml")) {
93
+ return { language: "Python", framework: "Python" };
94
+ }
95
+ return { language: "Unknown", framework: "Unknown" };
96
+ }
97
+
98
+ // src/retriever.ts
99
+ async function retrieve(query, architecture, limit = 10) {
100
+ const embedding = await embed(query);
101
+ return searchMemories(embedding, architecture, limit);
102
+ }
103
+
104
+ // src/memory-selection.ts
105
+ var FRAMEWORK_ARCHITECTURE_MAP = {
106
+ Laravel: ["laravel-service-repository"],
107
+ "Next.js": ["nextjs"],
108
+ "Nuxt.js": ["nuxt"],
109
+ Go: ["go-api"],
110
+ NestJS: ["nestjs"],
111
+ React: ["react"],
112
+ "Vue.js": ["vue"],
113
+ Svelte: ["svelte"]
114
+ };
115
+ function normalizeText(value) {
116
+ return value.toLowerCase().replace(/[`"'()[\]{}.,:;!?/\\<>|=*+-]/g, " ").replace(/\s+/g, " ").trim();
117
+ }
118
+ function tokenSet(value) {
119
+ return new Set(
120
+ normalizeText(value).split(" ").filter((token) => token.length > 2)
121
+ );
122
+ }
123
+ function similarityScore(a, b) {
124
+ const left = tokenSet(a);
125
+ const right = tokenSet(b);
126
+ if (left.size === 0 || right.size === 0) return 0;
127
+ let intersection = 0;
128
+ for (const token of left) {
129
+ if (right.has(token)) intersection++;
130
+ }
131
+ return 2 * intersection / (left.size + right.size);
132
+ }
133
+ function mergeMemory(primary, secondary) {
134
+ const mergedTags = [.../* @__PURE__ */ new Set([...primary.tags ?? [], ...secondary.tags ?? []])];
135
+ const reason = [primary.reason, secondary.reason].filter(Boolean).join(" | ") || void 0;
136
+ return {
137
+ ...primary,
138
+ tags: mergedTags,
139
+ reason
140
+ };
141
+ }
142
+ function dedupeMemories(memories, threshold = 0.8) {
143
+ const deduped = [];
144
+ for (const memory of memories) {
145
+ const existingIndex = deduped.findIndex((candidate) => {
146
+ if (candidate.content_hash && memory.content_hash && candidate.content_hash === memory.content_hash) {
147
+ return true;
148
+ }
149
+ return similarityScore(candidate.content, memory.content) >= threshold;
150
+ });
151
+ if (existingIndex === -1) {
152
+ deduped.push(memory);
153
+ continue;
154
+ }
155
+ deduped[existingIndex] = mergeMemory(deduped[existingIndex], memory);
156
+ }
157
+ return deduped;
158
+ }
159
+ function inferProjectArchitectures(cwd = process.cwd(), config) {
160
+ const inferred = /* @__PURE__ */ new Set();
161
+ if (config?.backendArchitecture) inferred.add(config.backendArchitecture);
162
+ if (config?.frontendFramework) inferred.add(config.frontendFramework);
163
+ const detected = detectProject(cwd);
164
+ for (const architecture of FRAMEWORK_ARCHITECTURE_MAP[detected.framework] ?? []) {
165
+ inferred.add(architecture);
166
+ }
167
+ return [...inferred];
168
+ }
169
+ function getAllowPatterns(config) {
170
+ return [...new Set(config?.allowPatterns?.filter(Boolean) ?? [])];
171
+ }
172
+ function buildContextQuery(parts, maxLength = 1200) {
173
+ return parts.filter(Boolean).join("\n").slice(0, maxLength);
174
+ }
175
+ async function retrieveContextualMemories(options) {
176
+ const architectures = inferProjectArchitectures(options.cwd, options.config);
177
+ const memories = await retrieve(options.query, architectures, options.limit ?? 15);
178
+ return dedupeMemories(memories);
179
+ }
180
+
181
+ // src/generator.ts
34
182
  var __filename = fileURLToPath(import.meta.url);
35
183
  var __dirname = dirname(__filename);
36
- var PKG_ROOT = join(__dirname, "..");
184
+ var PKG_ROOT = join2(__dirname, "..");
37
185
  var OUTPUT_FILES = [
38
186
  { template: "CLAUDE.md.hbs", path: "CLAUDE.md", agent: "Claude Code" },
39
187
  { template: "copilot-instructions.md.hbs", path: ".github/copilot-instructions.md", agent: "GitHub Copilot" },
@@ -69,13 +217,13 @@ Handlebars.registerHelper(
69
217
  );
70
218
  Handlebars.registerHelper("json", (val) => JSON.stringify(val, null, 2));
71
219
  function loadProfile(name) {
72
- const profilePath = join(PKG_ROOT, "profiles", `${name}.yml`);
73
- if (!existsSync(profilePath)) throw new Error(`Profile not found: ${name}`);
74
- return yaml.load(readFileSync(profilePath, "utf-8"));
220
+ const profilePath = join2(PKG_ROOT, "profiles", `${name}.yml`);
221
+ if (!existsSync2(profilePath)) throw new Error(`Profile not found: ${name}`);
222
+ return yaml.load(readFileSync2(profilePath, "utf-8"));
75
223
  }
76
224
  function listProfiles(layer) {
77
- const files = readdirSync(join(PKG_ROOT, "profiles")).filter((f) => f.endsWith(".yml"));
78
- const all = files.map((f) => yaml.load(readFileSync(join(PKG_ROOT, "profiles", f), "utf-8")));
225
+ const files = readdirSync(join2(PKG_ROOT, "profiles")).filter((f) => f.endsWith(".yml"));
226
+ const all = files.map((f) => yaml.load(readFileSync2(join2(PKG_ROOT, "profiles", f), "utf-8")));
79
227
  if (!layer) return all;
80
228
  if (layer === "backend") return all.filter((p) => p.layer === "backend" || p.layer === "fullstack");
81
229
  if (layer === "frontend") return all.filter((p) => p.layer === "frontend" || p.layer === "fullstack");
@@ -84,6 +232,7 @@ function listProfiles(layer) {
84
232
  function buildTemplateData(options) {
85
233
  const backend = options.backendArchitecture ? loadProfile(options.backendArchitecture) : null;
86
234
  const frontend = options.frontendFramework ? loadProfile(options.frontendFramework) : null;
235
+ const dedupedMemories = dedupeMemories(options.memories);
87
236
  const allRules = [
88
237
  ...backend?.rules ?? [],
89
238
  ...frontend?.rules ?? []
@@ -127,8 +276,8 @@ function buildTemplateData(options) {
127
276
  avoid: allAvoid,
128
277
  description: [backend?.description, frontend?.description].filter(Boolean).join(" | "),
129
278
  // memories
130
- memories: options.memories,
131
- hasMemories: options.memories.length > 0,
279
+ memories: dedupedMemories,
280
+ hasMemories: dedupedMemories.length > 0,
132
281
  // misc
133
282
  language: options.language,
134
283
  caveman: options.caveman,
@@ -136,15 +285,15 @@ function buildTemplateData(options) {
136
285
  };
137
286
  }
138
287
  function renderTemplate(templateName, data) {
139
- const templatePath = join(PKG_ROOT, "templates", templateName);
140
- if (!existsSync(templatePath)) throw new Error(`Template not found: ${templateName}`);
141
- return Handlebars.compile(readFileSync(templatePath, "utf-8"))(data);
288
+ const templatePath = join2(PKG_ROOT, "templates", templateName);
289
+ if (!existsSync2(templatePath)) throw new Error(`Template not found: ${templateName}`);
290
+ return Handlebars.compile(readFileSync2(templatePath, "utf-8"))(data);
142
291
  }
143
292
  function writeFile(filePath, content) {
144
293
  const dir = dirname(filePath);
145
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
146
- if (existsSync(filePath)) {
147
- const existing = readFileSync(filePath, "utf-8");
294
+ if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
295
+ if (existsSync2(filePath)) {
296
+ const existing = readFileSync2(filePath, "utf-8");
148
297
  if (existing === content) return "skipped";
149
298
  }
150
299
  writeFileSync(filePath, content, "utf-8");
@@ -156,8 +305,8 @@ async function generate(options, cwd = process.cwd(), onlyAgents) {
156
305
  const skipped = [];
157
306
  const files = onlyAgents ? OUTPUT_FILES.filter((f) => onlyAgents.includes(f.agent)) : OUTPUT_FILES;
158
307
  for (const output of files) {
159
- const targetPath = join(cwd, output.path);
160
- if (output.skipIfExists && existsSync(targetPath)) {
308
+ const targetPath = join2(cwd, output.path);
309
+ if (output.skipIfExists && existsSync2(targetPath)) {
161
310
  skipped.push(output.path);
162
311
  continue;
163
312
  }
@@ -554,71 +703,6 @@ var seeds = [
554
703
  { type: "rule", scope: "global", architecture: "svelte", title: "Avoid options API style \u2014 runes only", content: "Do not use the Svelte 4 options-style patterns (export let, $: reactive statements, $store subscriptions) in new Svelte 5 components. Use runes throughout.", reason: "Mixing the two reactivity systems in the same codebase creates two mental models, confuses new developers, and makes future migrations harder. Svelte 5 runes supersede every Svelte 4 pattern.", tags: ["svelte", "runes", "anti-pattern"] }
555
704
  ];
556
705
 
557
- // src/retriever.ts
558
- async function retrieve(query, architecture, limit = 10) {
559
- const embedding = await embed(query);
560
- return searchMemories(embedding, architecture, limit);
561
- }
562
-
563
- // src/project-detector.ts
564
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
565
- import { join as join2 } from "path";
566
- function detectProject(cwd = process.cwd()) {
567
- const has = (file) => existsSync2(join2(cwd, file));
568
- const readJson = (file) => {
569
- try {
570
- return JSON.parse(readFileSync2(join2(cwd, file), "utf-8"));
571
- } catch {
572
- return {};
573
- }
574
- };
575
- if (has("artisan") && has("composer.json")) {
576
- return { language: "PHP", framework: "Laravel" };
577
- }
578
- if (has("nuxt.config.ts") || has("nuxt.config.js")) {
579
- return { language: "TypeScript", framework: "Nuxt.js" };
580
- }
581
- if (has("manage.py")) {
582
- if (has("requirements.txt")) {
583
- const req = readFileSync2(join2(cwd, "requirements.txt"), "utf-8");
584
- if (req.includes("djangorestframework")) {
585
- return { language: "Python", framework: "Django REST Framework" };
586
- }
587
- }
588
- return { language: "Python", framework: "Django" };
589
- }
590
- if (has("go.mod")) {
591
- return { language: "Go", framework: "Go" };
592
- }
593
- if (has("Cargo.toml")) {
594
- return { language: "Rust", framework: "Rust" };
595
- }
596
- if (has("pubspec.yaml")) {
597
- return { language: "Dart", framework: "Flutter" };
598
- }
599
- if (has("pom.xml")) {
600
- return { language: "Java", framework: "Spring Boot" };
601
- }
602
- if (has("build.gradle") || has("build.gradle.kts")) {
603
- return { language: "Kotlin", framework: "Kotlin/JVM" };
604
- }
605
- if (has("package.json")) {
606
- const pkg = readJson("package.json");
607
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
608
- if (deps["@nestjs/core"]) return { language: "TypeScript", framework: "NestJS" };
609
- if (deps["express"]) return { language: "TypeScript", framework: "Express.js" };
610
- if (deps["fastify"]) return { language: "TypeScript", framework: "Fastify" };
611
- if (deps["react"]) return { language: "TypeScript", framework: "React" };
612
- if (deps["vue"]) return { language: "TypeScript", framework: "Vue.js" };
613
- if (deps["svelte"]) return { language: "TypeScript", framework: "Svelte" };
614
- return { language: "TypeScript/JavaScript", framework: "Node.js" };
615
- }
616
- if (has("requirements.txt") || has("pyproject.toml")) {
617
- return { language: "Python", framework: "Python" };
618
- }
619
- return { language: "Unknown", framework: "Unknown" };
620
- }
621
-
622
706
  // src/hook.ts
623
707
  import { execSync, spawnSync } from "child_process";
624
708
  import { writeFileSync as writeFileSync3, existsSync as existsSync4, unlinkSync, readFileSync as readFileSync4, chmodSync } from "fs";
@@ -838,7 +922,7 @@ async function promptToSaveViolations(violations) {
838
922
  default: selected.reason ?? selected.issue ?? ""
839
923
  });
840
924
  const { embed: embed2 } = await import("./embedding-PAYD2JYW.js");
841
- const { upsertMemory: upsertMemory2 } = await import("./db-KU4EEG4Y.js");
925
+ const { upsertMemory: upsertMemory2 } = await import("./db-5X5LTUCB.js");
842
926
  await upsertMemory2({
843
927
  type: "rule",
844
928
  scope: "project",
@@ -855,7 +939,7 @@ async function promptToSaveViolations(violations) {
855
939
  }
856
940
  async function loadIgnorePatterns() {
857
941
  try {
858
- const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-KU4EEG4Y.js");
942
+ const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-5X5LTUCB.js");
859
943
  const ignores = await listMemories2({ type: "ignore", limit: 1e3 });
860
944
  await closePool2();
861
945
  return ignores.map((ignore) => ignore.content);
@@ -863,6 +947,90 @@ async function loadIgnorePatterns() {
863
947
  return [];
864
948
  }
865
949
  }
950
+ function getProfileRules(config) {
951
+ const rules = [];
952
+ const avoids = [];
953
+ if (config.backendArchitecture) {
954
+ const profile = listProfiles("backend").find((p) => p.name === config.backendArchitecture);
955
+ if (profile) {
956
+ rules.push(...profile.rules);
957
+ avoids.push(...profile.avoid);
958
+ }
959
+ }
960
+ if (config.frontendFramework) {
961
+ const profile = listProfiles("frontend").find((p) => p.name === config.frontendFramework);
962
+ if (profile) {
963
+ rules.push(...profile.rules);
964
+ avoids.push(...profile.avoid);
965
+ }
966
+ }
967
+ return { rules, avoids };
968
+ }
969
+ async function loadRelevantRules(config, diff, stagedFiles, fallbackRules) {
970
+ try {
971
+ const query = buildContextQuery([
972
+ stagedFiles.join("\n"),
973
+ diff.slice(0, 1200),
974
+ config.backendArchitecture,
975
+ config.frontendFramework,
976
+ config.language
977
+ ]);
978
+ const memories = await retrieveContextualMemories({
979
+ query,
980
+ cwd: process.cwd(),
981
+ config,
982
+ limit: 15
983
+ });
984
+ const selected = memories.filter((memory) => ["rule", "pattern", "decision"].includes(memory.type)).map((memory) => memory.content);
985
+ return selected.length > 0 ? selected : fallbackRules;
986
+ } catch {
987
+ return fallbackRules;
988
+ }
989
+ }
990
+ function applyAllowPatterns(violations, allowPatterns) {
991
+ if (allowPatterns.length === 0) return violations;
992
+ return violations.filter((violation) => {
993
+ const haystack = `${violation.rule}
994
+ ${violation.issue}
995
+ ${violation.file}`.toLowerCase();
996
+ return !allowPatterns.some((pattern) => haystack.includes(pattern.toLowerCase()));
997
+ });
998
+ }
999
+ async function verifyViolations(diff, violations, allowPatterns, debug) {
1000
+ if (violations.length === 0) return violations;
1001
+ const systemPrompt = `You are verifying candidate architecture violations.
1002
+ Only keep violations that are directly supported by the diff.
1003
+ Reject speculative or weak matches.
1004
+ Treat these allowlisted patterns as intentional and valid:
1005
+ ${allowPatterns.length ? allowPatterns.map((pattern, index) => `${index + 1}. ${pattern}`).join("\n") : "(none)"}
1006
+
1007
+ Return strict JSON:
1008
+ {"violations":[{"rule":"...","file":"...","line":1,"issue":"...","suggestion":"...","reason":"..."}]}
1009
+ Do not include any text outside the JSON.`;
1010
+ const userPrompt = `Diff:
1011
+ ${diff.slice(0, 6e3)}
1012
+
1013
+ Candidate violations:
1014
+ ${JSON.stringify(violations, null, 2)}`;
1015
+ if (debug) {
1016
+ console.log(chalk.gray("\n [debug] verifier prompt:"));
1017
+ console.log(chalk.dim(systemPrompt));
1018
+ console.log(chalk.dim(userPrompt));
1019
+ 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"));
1020
+ }
1021
+ try {
1022
+ const raw = await callChatModel([
1023
+ { role: "system", content: systemPrompt },
1024
+ { role: "user", content: userPrompt }
1025
+ ]);
1026
+ const parsed = JSON.parse(raw);
1027
+ if (Array.isArray(parsed?.violations)) return parsed.violations;
1028
+ if (Array.isArray(parsed)) return parsed;
1029
+ return violations;
1030
+ } catch {
1031
+ return violations;
1032
+ }
1033
+ }
866
1034
  function installHook(advisory = true) {
867
1035
  if (!existsSync4(".git")) {
868
1036
  console.error(chalk.red("\n Not a git repository. Run from project root.\n"));
@@ -911,8 +1079,9 @@ function uninstallHook() {
911
1079
  async function checkStaged(options = {}) {
912
1080
  const SOURCE_EXTENSIONS2 = /\.(ts|tsx|js|jsx|py|php|rb|go|java|cs|swift|kt|rs|vue|svelte)$/;
913
1081
  let diff;
1082
+ let stagedFiles = [];
914
1083
  try {
915
- const stagedFiles = execSync("git diff --cached --name-only", { encoding: "utf-8" }).split("\n").filter((f) => f && SOURCE_EXTENSIONS2.test(f));
1084
+ stagedFiles = execSync("git diff --cached --name-only", { encoding: "utf-8" }).split("\n").filter((f) => f && SOURCE_EXTENSIONS2.test(f));
916
1085
  if (stagedFiles.length === 0) {
917
1086
  if (options.verbose) console.log(chalk.gray(" No source files staged \u2014 skipping rule check."));
918
1087
  return;
@@ -930,22 +1099,9 @@ async function checkStaged(options = {}) {
930
1099
  const configPath = join4(process.cwd(), ".memory-core.json");
931
1100
  if (!existsSync4(configPath)) return;
932
1101
  const config = JSON.parse(readFileSync4(configPath, "utf-8"));
933
- const rules = [];
934
- const avoids = [];
935
- if (config.backendArchitecture) {
936
- const profile = listProfiles("backend").find((p) => p.name === config.backendArchitecture);
937
- if (profile) {
938
- rules.push(...profile.rules);
939
- avoids.push(...profile.avoid);
940
- }
941
- }
942
- if (config.frontendFramework) {
943
- const profile = listProfiles("frontend").find((p) => p.name === config.frontendFramework);
944
- if (profile) {
945
- rules.push(...profile.rules);
946
- avoids.push(...profile.avoid);
947
- }
948
- }
1102
+ const { rules: fallbackRules, avoids } = getProfileRules(config);
1103
+ const rules = await loadRelevantRules(config, diff, stagedFiles, fallbackRules);
1104
+ const allowPatterns = [.../* @__PURE__ */ new Set([...getAllowPatterns(config), ...await loadIgnorePatterns()])];
949
1105
  if (rules.length === 0) return;
950
1106
  const MAX_DIFF = 8e3;
951
1107
  const truncated = diff.length > MAX_DIFF;
@@ -959,7 +1115,6 @@ async function checkStaged(options = {}) {
959
1115
  return why ? `${i + 1}. ${r}
960
1116
  WHY: ${why}` : `${i + 1}. ${r}`;
961
1117
  }).join("\n");
962
- const ignorePatterns = await loadIgnorePatterns();
963
1118
  const systemPrompt = `You are a strict code reviewer enforcing architecture and framework rules.
964
1119
  Analyze the git diff and identify ONLY clear, definite rule violations \u2014 not style preferences.
965
1120
  Use the WHY for each rule to understand intent and judge edge cases correctly.
@@ -971,7 +1126,7 @@ Things that must never appear:
971
1126
  ${avoids.map((a, i) => `${i + 1}. ${a}`).join("\n")}
972
1127
 
973
1128
  Never flag these accepted project patterns:
974
- ${ignorePatterns.length ? ignorePatterns.map((a, i) => `${i + 1}. ${a}`).join("\n") : "(none)"}
1129
+ ${allowPatterns.length ? allowPatterns.map((a, i) => `${i + 1}. ${a}`).join("\n") : "(none)"}
975
1130
 
976
1131
  IMPORTANT: You MUST respond with a JSON object that has a "violations" key containing an array.
977
1132
  For each violation include a "reason" field \u2014 copy the WHY from the rule to explain to the developer why this matters.
@@ -1027,6 +1182,8 @@ ${diffToSend}` }
1027
1182
  `));
1028
1183
  return;
1029
1184
  }
1185
+ violations = await verifyViolations(diff, violations, allowPatterns, options.debug ?? false);
1186
+ violations = applyAllowPatterns(violations, allowPatterns);
1030
1187
  if (violations.length === 0) {
1031
1188
  console.log(chalk.green(" \u2713 No rule violations \u2014 commit allowed.\n"));
1032
1189
  return;
@@ -1211,7 +1368,7 @@ function loadConfig(cwd) {
1211
1368
  return null;
1212
1369
  }
1213
1370
  }
1214
- function getProfileRules(config) {
1371
+ function getProfileRules2(config) {
1215
1372
  const rules = [];
1216
1373
  const avoids = [];
1217
1374
  if (config.backendArchitecture) {
@@ -1230,9 +1387,74 @@ function getProfileRules(config) {
1230
1387
  }
1231
1388
  return { rules, avoids };
1232
1389
  }
1390
+ async function loadRelevantRules2(config, rel, diff, fallbackRules) {
1391
+ try {
1392
+ const query = buildContextQuery([
1393
+ rel,
1394
+ diff.slice(0, 1200),
1395
+ config.backendArchitecture,
1396
+ config.frontendFramework,
1397
+ config.language
1398
+ ]);
1399
+ const memories = await retrieveContextualMemories({
1400
+ query,
1401
+ cwd: process.cwd(),
1402
+ config,
1403
+ limit: 15
1404
+ });
1405
+ const selected = memories.filter((memory) => ["rule", "pattern", "decision"].includes(memory.type)).map((memory) => memory.content);
1406
+ return selected.length > 0 ? selected : fallbackRules;
1407
+ } catch {
1408
+ return fallbackRules;
1409
+ }
1410
+ }
1411
+ function applyAllowPatterns2(violations, allowPatterns) {
1412
+ if (allowPatterns.length === 0) return violations;
1413
+ return violations.filter((violation) => {
1414
+ const haystack = `${violation.rule}
1415
+ ${violation.issue}
1416
+ ${violation.file}`.toLowerCase();
1417
+ return !allowPatterns.some((pattern) => haystack.includes(pattern.toLowerCase()));
1418
+ });
1419
+ }
1420
+ async function verifyViolations2(diff, violations, allowPatterns, debug) {
1421
+ if (violations.length === 0) return violations;
1422
+ const systemPrompt = `You are verifying candidate architecture violations.
1423
+ Only keep violations that are directly supported by the diff.
1424
+ Reject speculative or weak matches.
1425
+ Treat these allowlisted patterns as intentional and valid:
1426
+ ${allowPatterns.length ? allowPatterns.map((pattern, index) => `${index + 1}. ${pattern}`).join("\n") : "(none)"}
1427
+
1428
+ Return strict JSON:
1429
+ {"violations":[{"rule":"...","file":"...","line":1,"issue":"...","suggestion":"...","reason":"..."}]}
1430
+ Do not include any text outside the JSON.`;
1431
+ const userPrompt = `Diff:
1432
+ ${diff.slice(0, 6e3)}
1433
+
1434
+ Candidate violations:
1435
+ ${JSON.stringify(violations, null, 2)}`;
1436
+ if (debug) {
1437
+ console.log(chalk2.gray("\n [debug] verifier prompt:"));
1438
+ console.log(chalk2.dim(systemPrompt));
1439
+ console.log(chalk2.dim(userPrompt));
1440
+ 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"));
1441
+ }
1442
+ try {
1443
+ const raw = await callChatModel([
1444
+ { role: "system", content: systemPrompt },
1445
+ { role: "user", content: userPrompt }
1446
+ ]);
1447
+ const parsed = JSON.parse(raw);
1448
+ if (Array.isArray(parsed?.violations)) return parsed.violations;
1449
+ if (Array.isArray(parsed)) return parsed;
1450
+ return violations;
1451
+ } catch {
1452
+ return violations;
1453
+ }
1454
+ }
1233
1455
  async function loadIgnorePatterns2() {
1234
1456
  try {
1235
- const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-KU4EEG4Y.js");
1457
+ const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-5X5LTUCB.js");
1236
1458
  const ignores = await listMemories2({ type: "ignore", limit: 1e3 });
1237
1459
  await closePool2();
1238
1460
  return ignores.map((ignore) => ignore.content);
@@ -1251,7 +1473,8 @@ async function checkFile(filePath, cwd, config, verbose, debug) {
1251
1473
  diff = noIndexResult.stdout ?? "";
1252
1474
  }
1253
1475
  if (!diff.trim()) return;
1254
- const { rules, avoids } = getProfileRules(config);
1476
+ const { rules: fallbackRules, avoids } = getProfileRules2(config);
1477
+ const rules = await loadRelevantRules2(config, rel, diff, fallbackRules);
1255
1478
  if (rules.length === 0) return;
1256
1479
  const MAX_DIFF = 6e3;
1257
1480
  const truncated = diff.length > MAX_DIFF;
@@ -1265,7 +1488,7 @@ async function checkFile(filePath, cwd, config, verbose, debug) {
1265
1488
  return why ? `${i + 1}. ${r}
1266
1489
  WHY: ${why}` : `${i + 1}. ${r}`;
1267
1490
  }).join("\n");
1268
- const ignorePatterns = await loadIgnorePatterns2();
1491
+ const allowPatterns = [.../* @__PURE__ */ new Set([...getAllowPatterns(config), ...await loadIgnorePatterns2()])];
1269
1492
  const systemPrompt = `You are a strict code reviewer enforcing architecture rules.
1270
1493
  Analyze the file diff and identify ONLY clear, definite rule violations.
1271
1494
  Use the WHY for each rule to understand intent and judge edge cases.
@@ -1277,7 +1500,7 @@ Things that must never appear:
1277
1500
  ${avoids.map((a, i) => `${i + 1}. ${a}`).join("\n")}
1278
1501
 
1279
1502
  Never flag these accepted project patterns:
1280
- ${ignorePatterns.length ? ignorePatterns.map((a, i) => `${i + 1}. ${a}`).join("\n") : "(none)"}
1503
+ ${allowPatterns.length ? allowPatterns.map((a, i) => `${i + 1}. ${a}`).join("\n") : "(none)"}
1281
1504
 
1282
1505
  IMPORTANT: Respond with JSON: {"violations":[...]} or {"violations":[]}.
1283
1506
  Each violation: {"rule":"...","file":"...","line":N,"issue":"...","suggestion":"...","reason":"..."}.
@@ -1316,6 +1539,8 @@ ${diffToSend}` }
1316
1539
  } catch {
1317
1540
  violations = [];
1318
1541
  }
1542
+ violations = await verifyViolations2(diff, violations, allowPatterns, debug);
1543
+ violations = applyAllowPatterns2(violations, allowPatterns);
1319
1544
  if (violations.length === 0) {
1320
1545
  console.log(chalk2.green(` \u2713 ${rel}`) + chalk2.dim(" \u2014 no violations"));
1321
1546
  return;
@@ -1357,7 +1582,7 @@ async function startWatch(options = {}) {
1357
1582
  console.error(chalk2.red("\n No .memory-core.json found. Run: memory-core init\n"));
1358
1583
  process.exit(1);
1359
1584
  }
1360
- const { rules } = getProfileRules(config);
1585
+ const { rules } = getProfileRules2(config);
1361
1586
  if (rules.length === 0) {
1362
1587
  console.log(chalk2.yellow("\n No architecture rules configured in .memory-core.json \u2014 nothing to watch.\n"));
1363
1588
  process.exit(0);
@@ -1369,7 +1594,6 @@ async function startWatch(options = {}) {
1369
1594
  console.log(chalk2.dim(` rules: ${rules.length}`));
1370
1595
  console.log(chalk2.dim(" ctrl+c to stop\n"));
1371
1596
  const pending = /* @__PURE__ */ new Map();
1372
- let ollamaWarned = false;
1373
1597
  const watcher = watch(watchPath, {
1374
1598
  ignored: [
1375
1599
  "**/node_modules/**",
@@ -1392,18 +1616,6 @@ async function startWatch(options = {}) {
1392
1616
  pending.delete(filePath);
1393
1617
  console.log(chalk2.dim(`
1394
1618
  [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] saved: ${relative(cwd, filePath)}`));
1395
- try {
1396
- const ping = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(2e3) });
1397
- if (!ping.ok) throw new Error("not ok");
1398
- ollamaWarned = false;
1399
- } catch {
1400
- if (!ollamaWarned) {
1401
- console.log(chalk2.yellow(` \u26A0 Ollama not running at ${ollamaUrl} \u2014 skipping check.`));
1402
- console.log(chalk2.gray(" Start it: ollama serve\n"));
1403
- ollamaWarned = true;
1404
- }
1405
- return;
1406
- }
1407
1619
  await checkFile(filePath, cwd, config, options.verbose ?? false, options.debug ?? false);
1408
1620
  }, 300);
1409
1621
  pending.set(filePath, timer);
@@ -1454,7 +1666,7 @@ function printBanner(projectName, agentCount, status) {
1454
1666
  ];
1455
1667
  lines.forEach((l) => console.log(l));
1456
1668
  }
1457
- async function checkConnections(dbUrl, ollamaUrl2, chatModel) {
1669
+ async function checkConnections(dbUrl, ollamaUrl, chatModel) {
1458
1670
  const spinner = ora("Checking connections\u2026").start();
1459
1671
  let postgresOk = false;
1460
1672
  let ollamaOk = false;
@@ -1468,7 +1680,7 @@ async function checkConnections(dbUrl, ollamaUrl2, chatModel) {
1468
1680
  postgresOk = false;
1469
1681
  }
1470
1682
  try {
1471
- const res = await fetch(`${ollamaUrl2}/api/tags`, { signal: AbortSignal.timeout(5e3) });
1683
+ const res = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(5e3) });
1472
1684
  ollamaOk = res.ok;
1473
1685
  } catch {
1474
1686
  ollamaOk = false;
@@ -1497,6 +1709,19 @@ function readProjectConfig() {
1497
1709
  function writeProjectConfig(config) {
1498
1710
  writeFileSync5(join6(process.cwd(), CONFIG_FILE), JSON.stringify(config, null, 2));
1499
1711
  }
1712
+ function updateProjectConfig(mutator) {
1713
+ const current = readProjectConfig() ?? {
1714
+ projectName: process.cwd().split("/").pop() ?? "project",
1715
+ projectType: "backend",
1716
+ language: detectProject().language,
1717
+ caveman: { enabled: false, intensity: "full" },
1718
+ agents: [],
1719
+ allowPatterns: []
1720
+ };
1721
+ const updated = mutator(current);
1722
+ writeProjectConfig(updated);
1723
+ return updated;
1724
+ }
1500
1725
  function parseTags(tags) {
1501
1726
  return tags ? tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
1502
1727
  }
@@ -1530,17 +1755,17 @@ program.command("init").description("Initialize memory-core in the current proje
1530
1755
  if (!hasEnv && quick) {
1531
1756
  const dbUser = process.env.USER ?? process.env.USERNAME ?? "postgres";
1532
1757
  const dbUrl = `postgresql://${dbUser}@localhost:5432/memory_core`;
1533
- const ollamaUrl2 = "http://localhost:11434";
1758
+ const ollamaUrl = "http://localhost:11434";
1534
1759
  const chatModel = "llama3.2";
1535
1760
  const envContent = [
1536
1761
  `DATABASE_URL=${dbUrl}`,
1537
- `OLLAMA_URL=${ollamaUrl2}`,
1762
+ `OLLAMA_URL=${ollamaUrl}`,
1538
1763
  `OLLAMA_MODEL=nomic-embed-text`,
1539
1764
  `OLLAMA_CHAT_MODEL=${chatModel}`
1540
1765
  ].join("\n") + "\n";
1541
1766
  writeFileSync5(envPath, envContent);
1542
1767
  process.env.DATABASE_URL = dbUrl;
1543
- process.env.OLLAMA_URL = ollamaUrl2;
1768
+ process.env.OLLAMA_URL = ollamaUrl;
1544
1769
  process.env.OLLAMA_MODEL = "nomic-embed-text";
1545
1770
  process.env.OLLAMA_CHAT_MODEL = chatModel;
1546
1771
  const gitignorePath2 = join6(process.cwd(), ".gitignore");
@@ -1572,15 +1797,15 @@ program.command("init").description("Initialize memory-core in the current proje
1572
1797
  console.log(chalk3.yellow(" Please check that PostgreSQL is running and the URL is correct.\n"));
1573
1798
  }
1574
1799
  }
1575
- let ollamaUrl2 = "";
1800
+ let ollamaUrl = "";
1576
1801
  while (true) {
1577
- ollamaUrl2 = await input({
1802
+ ollamaUrl = await input({
1578
1803
  message: "Ollama URL?",
1579
- default: ollamaUrl2 || "http://localhost:11434"
1804
+ default: ollamaUrl || "http://localhost:11434"
1580
1805
  });
1581
1806
  const ollamaSpinner = ora(" Testing Ollama connection\u2026").start();
1582
1807
  try {
1583
- const res = await fetch(`${ollamaUrl2}/api/tags`, { signal: AbortSignal.timeout(5e3) });
1808
+ const res = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(5e3) });
1584
1809
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
1585
1810
  ollamaSpinner.succeed(chalk3.green("Ollama connected"));
1586
1811
  break;
@@ -1615,7 +1840,7 @@ program.command("init").description("Initialize memory-core in the current proje
1615
1840
  chatModel = chatModelChoice === "__custom__" ? await input({ message: "Model name?", default: "llama3.2" }) : chatModelChoice;
1616
1841
  const modelSpinner = ora(` Checking if ${chatModel} is installed\u2026`).start();
1617
1842
  try {
1618
- const res = await fetch(`${ollamaUrl2}/api/tags`, { signal: AbortSignal.timeout(5e3) });
1843
+ const res = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(5e3) });
1619
1844
  const data = await res.json();
1620
1845
  const models = data.models ?? [];
1621
1846
  const exact = models.find((m) => m.name === chatModel);
@@ -1666,7 +1891,7 @@ program.command("init").description("Initialize memory-core in the current proje
1666
1891
  }
1667
1892
  const envLines = [
1668
1893
  `DATABASE_URL=${dbUrl}`,
1669
- `OLLAMA_URL=${ollamaUrl2}`,
1894
+ `OLLAMA_URL=${ollamaUrl}`,
1670
1895
  `OLLAMA_MODEL=nomic-embed-text`,
1671
1896
  `CHAT_PROVIDER=${chatProvider}`,
1672
1897
  `CHAT_MODEL=${chatModel}`
@@ -1676,7 +1901,7 @@ program.command("init").description("Initialize memory-core in the current proje
1676
1901
  const envContent = envLines.join("\n") + "\n";
1677
1902
  writeFileSync5(envPath, envContent);
1678
1903
  process.env.DATABASE_URL = dbUrl;
1679
- process.env.OLLAMA_URL = ollamaUrl2;
1904
+ process.env.OLLAMA_URL = ollamaUrl;
1680
1905
  process.env.OLLAMA_MODEL = "nomic-embed-text";
1681
1906
  process.env.CHAT_PROVIDER = chatProvider;
1682
1907
  process.env.CHAT_MODEL = chatModel;
@@ -1796,14 +2021,20 @@ program.command("init").description("Initialize memory-core in the current proje
1796
2021
  frontendFramework,
1797
2022
  language,
1798
2023
  caveman: { enabled: installCaveman, intensity: cavemanIntensity },
1799
- agents: selectedAgents
2024
+ agents: selectedAgents,
2025
+ allowPatterns: []
1800
2026
  };
1801
2027
  let memories = [];
1802
2028
  if (pullMemories) {
1803
2029
  const spinner2 = ora("Retrieving relevant memories\u2026").start();
1804
2030
  try {
1805
2031
  const archQuery = [backendArchitecture, frontendFramework, language].filter(Boolean).join(" ");
1806
- memories = await retrieve(archQuery, backendArchitecture ?? frontendFramework, 10);
2032
+ memories = await retrieveContextualMemories({
2033
+ query: archQuery,
2034
+ cwd: process.cwd(),
2035
+ config,
2036
+ limit: 20
2037
+ });
1807
2038
  spinner2.succeed(`Found ${memories.length} relevant memories`);
1808
2039
  } catch (err) {
1809
2040
  spinner2.warn(`Could not retrieve memories: ${err.message}`);
@@ -1876,7 +2107,12 @@ program.command("sync").description("Re-pull memories and regenerate AI agent fi
1876
2107
  let memories = [];
1877
2108
  try {
1878
2109
  const archQuery = [config.backendArchitecture, config.frontendFramework, config.language].filter(Boolean).join(" ");
1879
- memories = await retrieve(archQuery, config.backendArchitecture ?? config.frontendFramework, 10);
2110
+ memories = await retrieveContextualMemories({
2111
+ query: archQuery,
2112
+ cwd: process.cwd(),
2113
+ config,
2114
+ limit: 25
2115
+ });
1880
2116
  spinner.text = `Found ${memories.length} memories \u2014 regenerating files\u2026`;
1881
2117
  } catch (err) {
1882
2118
  spinner.warn(`Could not retrieve memories: ${err.message}`);
@@ -1939,9 +2175,10 @@ program.command("search <query>").description("Search memories using semantic si
1939
2175
  const config = readProjectConfig();
1940
2176
  const spinner = ora("Searching\u2026").start();
1941
2177
  try {
2178
+ const architectures = inferProjectArchitectures(process.cwd(), config);
1942
2179
  const results = await retrieve(
1943
2180
  query,
1944
- config?.backendArchitecture ?? config?.frontendFramework,
2181
+ architectures,
1945
2182
  parseInt(opts.limit, 10)
1946
2183
  );
1947
2184
  spinner.stop();
@@ -2044,6 +2281,22 @@ program.command("remove <id>").description("Remove a memory by ID").action(async
2044
2281
  await closePool();
2045
2282
  }
2046
2283
  });
2284
+ program.command("forget").description("Bulk-delete memories by tag, scope, type, or architecture").option("--tag <tag>", "Delete memories with this tag").option("--scope <scope>", "Filter by scope").option("--type <type>", "Filter by type").option("--arch <architecture>", "Filter by architecture").action(async (opts) => {
2285
+ try {
2286
+ const deleted = await deleteMemories({
2287
+ tag: opts.tag,
2288
+ scope: opts.scope,
2289
+ type: opts.type,
2290
+ architecture: opts.arch
2291
+ });
2292
+ console.log(chalk3.green(`Deleted ${deleted} memories`));
2293
+ } catch (err) {
2294
+ console.error(chalk3.red(`Forget failed: ${err.message}`));
2295
+ process.exit(1);
2296
+ } finally {
2297
+ await closePool();
2298
+ }
2299
+ });
2047
2300
  program.command("edit <id>").description("Edit a memory interactively").action(async (id) => {
2048
2301
  const memoryId = parseInt(id, 10);
2049
2302
  try {
@@ -2114,6 +2367,36 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
2114
2367
  await closePool();
2115
2368
  }
2116
2369
  });
2370
+ program.command("allow [pattern]").description("Manage project allow patterns in .memory-core.json").option("--list", "List current allow patterns").option("--remove <pattern>", "Remove an allow pattern").action((pattern, opts) => {
2371
+ if (opts.list) {
2372
+ const patterns = getAllowPatterns(readProjectConfig());
2373
+ if (patterns.length === 0) {
2374
+ console.log(chalk3.yellow("\n No allow patterns configured.\n"));
2375
+ return;
2376
+ }
2377
+ console.log(chalk3.bold("\n Allow patterns\n"));
2378
+ patterns.forEach((entry, index) => console.log(` ${index + 1}. ${entry}`));
2379
+ console.log();
2380
+ return;
2381
+ }
2382
+ if (opts.remove) {
2383
+ updateProjectConfig((config) => ({
2384
+ ...config,
2385
+ allowPatterns: getAllowPatterns(config).filter((entry) => entry !== opts.remove)
2386
+ }));
2387
+ console.log(chalk3.green(`Removed allow pattern: "${opts.remove}"`));
2388
+ return;
2389
+ }
2390
+ if (!pattern) {
2391
+ console.error(chalk3.red("Provide a pattern, --list, or --remove <pattern>"));
2392
+ process.exit(1);
2393
+ }
2394
+ updateProjectConfig((config) => ({
2395
+ ...config,
2396
+ allowPatterns: [.../* @__PURE__ */ new Set([...getAllowPatterns(config), pattern])]
2397
+ }));
2398
+ console.log(chalk3.green(`Allow pattern saved: "${pattern}"`));
2399
+ });
2117
2400
  program.command("ci-setup").description("Generate GitHub Actions workflow for memory-core").action(() => {
2118
2401
  const workflowPath = join6(process.cwd(), ".github", "workflows", "memory-core.yml");
2119
2402
  mkdirSync2(dirname2(workflowPath), { recursive: true });
@@ -2154,7 +2437,7 @@ program.command("reset").description("Remove memory-core generated files and loc
2154
2437
  default: false
2155
2438
  });
2156
2439
  if (ok) {
2157
- const { getPool } = await import("./db-KU4EEG4Y.js");
2440
+ const { getPool } = await import("./db-5X5LTUCB.js");
2158
2441
  await getPool().query("DROP TABLE IF EXISTS memories");
2159
2442
  await closePool();
2160
2443
  console.log(chalk3.yellow("Dropped memories table"));
@@ -2250,7 +2533,8 @@ program.command("global").description("Sync your memory into every AI agent glob
2250
2533
  const spinner = ora("Fetching global memories\u2026").start();
2251
2534
  let memories = [];
2252
2535
  try {
2253
- memories = await retrieve("architecture rules coding standards", opts.arch, 20);
2536
+ const architectures = opts.arch ? [opts.arch] : inferProjectArchitectures(process.cwd(), readProjectConfig());
2537
+ memories = dedupeMemories(await retrieve("architecture rules coding standards", architectures, 30));
2254
2538
  } catch (err) {
2255
2539
  spinner.fail(`Could not fetch memories: ${err.message}`);
2256
2540
  process.exit(1);
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  closePool,
4
+ deleteMemories,
4
5
  deleteMemory,
5
6
  getMemory,
6
7
  getPool,
@@ -11,10 +12,11 @@ import {
11
12
  searchMemories,
12
13
  updateMemory,
13
14
  upsertMemory
14
- } from "./chunk-73SRPNAL.js";
15
+ } from "./chunk-25Y2KI7M.js";
15
16
  import "./chunk-KSLFLWB4.js";
16
17
  export {
17
18
  closePool,
19
+ deleteMemories,
18
20
  deleteMemory,
19
21
  getMemory,
20
22
  getPool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shahmilsaari/memory-core",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Universal AI memory core — generate AI context files from architecture profiles with RAG support",
5
5
  "type": "module",
6
6
  "bin": {