@shahmilsaari/memory-core 1.0.0 → 1.0.3

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
@@ -4,35 +4,23 @@ import {
4
4
  OUTPUT_FILES,
5
5
  buildContextQuery,
6
6
  callChatModel,
7
- dedupeMemories,
7
+ closePool,
8
8
  detectProject,
9
+ findAstDeterministicViolationsForDiff,
9
10
  generate,
10
11
  getAllowPatterns,
11
12
  getChatProviderLabel,
13
+ getDefaultApplicationContainer,
14
+ getPool,
12
15
  inferProjectArchitectures,
13
16
  listProfiles,
14
- retrieve,
17
+ migrateGraphSnapshots,
18
+ probeGraphSnapshotStore,
15
19
  retrieveContextualMemories,
16
20
  retrieveMemorySelection,
17
- seeds,
18
- startWatch
19
- } from "./chunk-J7SPACA3.js";
20
- import {
21
- embed
22
- } from "./chunk-HAGRPKR3.js";
23
- import {
24
- closePool,
25
- deleteMemories,
26
- deleteMemory,
27
- getMemory,
28
- getPool,
29
- listMemories,
30
21
  runMigrations,
31
- saveMemory,
32
- updateMemory,
33
- upsertMemory
34
- } from "./chunk-WUL7HLAA.js";
35
- import "./chunk-KSLFLWB4.js";
22
+ seeds
23
+ } from "./chunk-PDQXIKL7.js";
36
24
 
37
25
  // src/cli.ts
38
26
  import { Command } from "commander";
@@ -40,7 +28,7 @@ import { input, select, confirm } from "@inquirer/prompts";
40
28
  import chalk2 from "chalk";
41
29
  import ora from "ora";
42
30
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync, appendFileSync, rmSync } from "fs";
43
- import { join as join3, dirname } from "path";
31
+ import { join as join3, dirname, resolve } from "path";
44
32
  import { homedir } from "os";
45
33
 
46
34
  // src/hook.ts
@@ -134,10 +122,18 @@ var reasonMap = new Map(
134
122
  );
135
123
  var HOOK_PATH = join2(".git", "hooks", "pre-commit");
136
124
  var HOOK_MARKER = "# archmind-memory-core";
137
- function buildHookScript(advisory) {
125
+ function buildHookBody(advisory) {
138
126
  const suffix = advisory ? " || true" : "";
139
- return `#!/bin/sh
140
- ${HOOK_MARKER}${advisory ? " advisory" : ""}
127
+ return `${HOOK_MARKER}${advisory ? " advisory" : ""}
128
+ if [ "\${MEMORY_CORE_SKIP_HOOK:-}" = "1" ] || [ "\${ARCHMIND_SKIP_HOOK:-}" = "1" ] || [ "\${HUSKY:-}" = "0" ] || [ "\${HUSKY_SKIP_HOOKS:-}" = "1" ]; then
129
+ exit 0
130
+ fi
131
+ if [ -n "\${SKIP:-}" ] && echo ",$SKIP," | grep -qiE ',(memory-core|archmind),'; then
132
+ exit 0
133
+ fi
134
+ if [ -n "\${SKIP_HOOKS:-}" ]; then
135
+ exit 0
136
+ fi
141
137
  if command -v memory-core >/dev/null 2>&1; then
142
138
  memory-core check --staged${suffix}
143
139
  elif [ -f "./node_modules/.bin/memory-core" ]; then
@@ -149,6 +145,26 @@ else
149
145
  fi
150
146
  `;
151
147
  }
148
+ function buildHookScript(advisory) {
149
+ return `#!/bin/sh
150
+
151
+ ${buildHookBody(advisory)}`;
152
+ }
153
+ function normalizeHookPreamble(content) {
154
+ const lines = content.split("\n");
155
+ const normalized = [];
156
+ let shebangSeen = false;
157
+ for (const line of lines) {
158
+ if (/^\s*#!\/bin\/sh\s*$/.test(line)) {
159
+ if (shebangSeen) continue;
160
+ shebangSeen = true;
161
+ normalized.push("#!/bin/sh");
162
+ continue;
163
+ }
164
+ normalized.push(line);
165
+ }
166
+ return normalized.join("\n").replace(/\n{3,}/g, "\n\n").trim();
167
+ }
152
168
  function recordViolations(violations, source = "hook") {
153
169
  const statsPath = join2(process.cwd(), ".memory-core-stats.json");
154
170
  let stats = { rules: {}, files: {} };
@@ -173,6 +189,7 @@ function recordViolations(violations, source = "hook") {
173
189
  async function promptToSaveViolations(violations) {
174
190
  if (!process.stdin.isTTY || violations.length === 0) return;
175
191
  try {
192
+ const app = getDefaultApplicationContainer();
176
193
  const { confirm: confirm2, input: input2 } = await import("@inquirer/prompts");
177
194
  const save = await confirm2({
178
195
  message: "Save a caught violation as a project rule?",
@@ -185,15 +202,12 @@ async function promptToSaveViolations(violations) {
185
202
  message: "Why should this rule exist?",
186
203
  default: selected.reason ?? selected.issue ?? ""
187
204
  });
188
- const { embed: embed2 } = await import("./embedding-PAYD2JYW.js");
189
- const { upsertMemory: upsertMemory2 } = await import("./db-MF3VKVKH.js");
190
- await upsertMemory2({
205
+ await app.services.memoryEngine.remember({
191
206
  type: "rule",
192
207
  scope: "project",
193
208
  content: selected.rule,
194
209
  reason: reason || void 0,
195
- tags: ["violation"],
196
- embedding: await embed2(selected.rule)
210
+ tags: ["violation"]
197
211
  });
198
212
  console.log(chalk.green(" \u2713 Saved as project rule. Run memory-core sync to propagate it.\n"));
199
213
  } catch (err) {
@@ -203,9 +217,8 @@ async function promptToSaveViolations(violations) {
203
217
  }
204
218
  async function loadIgnorePatterns() {
205
219
  try {
206
- const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-MF3VKVKH.js");
207
- const ignores = await listMemories2({ type: "ignore", limit: 1e3 });
208
- await closePool2();
220
+ const app = getDefaultApplicationContainer();
221
+ const ignores = await app.services.memoryEngine.list({ type: "ignore", limit: 1e3 });
209
222
  return ignores.map((ignore) => ignore.content);
210
223
  } catch {
211
224
  return [];
@@ -418,18 +431,26 @@ function installHook(advisory = true) {
418
431
  process.exit(1);
419
432
  }
420
433
  const script = buildHookScript(advisory);
434
+ const body = buildHookBody(advisory).trimEnd();
421
435
  if (existsSync2(HOOK_PATH)) {
422
436
  const existing = readFileSync2(HOOK_PATH, "utf-8");
423
437
  if (existing.includes(HOOK_MARKER)) {
424
438
  const markerIndex = existing.indexOf(HOOK_MARKER);
425
- const before = markerIndex > 1 ? existing.slice(0, markerIndex).trimEnd() + "\n\n" : "";
426
- writeFileSync2(HOOK_PATH, before + script);
439
+ const beforeRaw = markerIndex > 0 ? existing.slice(0, markerIndex) : "";
440
+ const normalizedBefore = normalizeHookPreamble(beforeRaw);
441
+ const preamble = normalizedBefore.length > 0 ? normalizedBefore : "#!/bin/sh";
442
+ const preambleWithShebang = preamble.startsWith("#!/bin/sh") ? preamble : `#!/bin/sh
443
+ ${preamble}`;
444
+ writeFileSync2(HOOK_PATH, `${preambleWithShebang}
445
+
446
+ ${body}
447
+ `);
427
448
  chmodSync(HOOK_PATH, 493);
428
449
  const modeLabel2 = advisory ? chalk.cyan("advisory") : chalk.yellow("strict");
429
450
  console.log(chalk.green("\n \u2713 Pre-commit hook updated") + chalk.dim(` (${modeLabel2} mode)`));
430
451
  return;
431
452
  }
432
- writeFileSync2(HOOK_PATH, existing.trimEnd() + "\n\n" + script);
453
+ writeFileSync2(HOOK_PATH, existing.trimEnd() + "\n\n" + body + "\n");
433
454
  } else {
434
455
  writeFileSync2(HOOK_PATH, script);
435
456
  }
@@ -450,7 +471,7 @@ function uninstallHook() {
450
471
  return;
451
472
  }
452
473
  const markerIndex = content.indexOf(HOOK_MARKER);
453
- const before = markerIndex > 1 ? content.slice(0, markerIndex).trimEnd() : "";
474
+ const before = markerIndex > 1 ? normalizeHookPreamble(content.slice(0, markerIndex)) : "";
454
475
  if (before && before !== "#!/bin/sh") {
455
476
  writeFileSync2(HOOK_PATH, `${before}
456
477
  `);
@@ -526,6 +547,12 @@ Do not include any text outside the JSON object.`;
526
547
  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"));
527
548
  }
528
549
  const deterministicViolations = findDeterministicViolations(diff, rules, avoids, allowPatterns);
550
+ const astViolations = findAstDeterministicViolationsForDiff(diff, {
551
+ cwd: process.cwd(),
552
+ config,
553
+ rules,
554
+ reasonLookup: reasonMap
555
+ });
529
556
  let modelViolations = [];
530
557
  try {
531
558
  const raw = await callChatModel([
@@ -559,7 +586,7 @@ ${diffToSend}` }
559
586
  }
560
587
  }
561
588
  modelViolations = await verifyViolations(diff, modelViolations, allowPatterns, options.debug ?? false);
562
- let violations = dedupeViolations([...deterministicViolations, ...modelViolations]);
589
+ let violations = dedupeViolations([...deterministicViolations, ...astViolations, ...modelViolations]);
563
590
  violations = applyAllowPatterns(violations, allowPatterns);
564
591
  if (violations.length === 0) {
565
592
  console.log(chalk.green(" \u2713 No rule violations \u2014 commit allowed.\n"));
@@ -584,6 +611,7 @@ ${diffToSend}` }
584
611
  });
585
612
  console.log(chalk.dim(" Fix the violations above, then commit again."));
586
613
  console.log(chalk.dim(" To bypass (not recommended): git commit --no-verify"));
614
+ console.log(chalk.dim(" Env bypass: MEMORY_CORE_SKIP_HOOK=1 git commit"));
587
615
  console.log(chalk.dim(' To save as memory: memory-core remember "<lesson>"'));
588
616
  console.log();
589
617
  recordViolations(violations);
@@ -806,6 +834,7 @@ var GITIGNORE_HEADING = "# memory-core generated files";
806
834
  var DEFAULT_OLLAMA_URL = "http://localhost:11434";
807
835
  var DEFAULT_EMBEDDING_MODEL = "nomic-embed-text";
808
836
  var DEFAULT_CHAT_MODEL = "llama3.2";
837
+ var phase1 = getDefaultApplicationContainer();
809
838
  function getEnvPath() {
810
839
  const memoryEnv = join3(process.cwd(), ".memory-core.env");
811
840
  if (existsSync3(memoryEnv)) return memoryEnv;
@@ -1045,6 +1074,35 @@ function truncate(value, length) {
1045
1074
  if (!value) return "";
1046
1075
  return value.length > length ? `${value.slice(0, Math.max(0, length - 1))}\u2026` : value;
1047
1076
  }
1077
+ function toMemoryTableRow(memory) {
1078
+ return {
1079
+ id: memory.id,
1080
+ type: memory.type,
1081
+ scope: memory.scope,
1082
+ title: memory.title,
1083
+ content: memory.content,
1084
+ reason: memory.reason,
1085
+ context: memory.context,
1086
+ tags: memory.tags ?? [],
1087
+ similarity: memory.similarity
1088
+ };
1089
+ }
1090
+ function toPortableFromRecord(memory) {
1091
+ return toPortableMemory({
1092
+ id: memory.id,
1093
+ type: memory.type,
1094
+ scope: memory.scope,
1095
+ architecture: memory.architecture,
1096
+ project_name: memory.projectName,
1097
+ title: memory.title,
1098
+ content: memory.content,
1099
+ reason: memory.reason,
1100
+ context: memory.context,
1101
+ tags: memory.tags ?? [],
1102
+ content_hash: memory.contentHash,
1103
+ similarity: memory.similarity
1104
+ });
1105
+ }
1048
1106
  function printMemoryTable(memories, title = "Rules in memory") {
1049
1107
  console.log(chalk2.bold(`
1050
1108
  ${title} (${memories.length} total)
@@ -1066,6 +1124,23 @@ function getCurrentListArchitectures(config) {
1066
1124
  function printStatusLine(label, value) {
1067
1125
  console.log(` ${chalk2.dim(label.padEnd(18))} ${value}`);
1068
1126
  }
1127
+ function abbreviate(value, max = 96) {
1128
+ return value.length > max ? `${value.slice(0, max - 1)}\u2026` : value;
1129
+ }
1130
+ function graphStoreFilePath(cwd = process.cwd()) {
1131
+ return join3(cwd, ".memory-core", "graph-snapshots.json");
1132
+ }
1133
+ function graphBackendLabel(values) {
1134
+ return values.DATABASE_URL ? "postgres (file fallback enabled)" : "file";
1135
+ }
1136
+ async function getGraphSnapshotCount(cwd = process.cwd()) {
1137
+ try {
1138
+ const snapshots = await phase1.services.graphEngine.listSnapshots(resolve(cwd), 1e4);
1139
+ return snapshots.length;
1140
+ } catch {
1141
+ return null;
1142
+ }
1143
+ }
1069
1144
  async function runModelDoctor() {
1070
1145
  const { envPath, values } = readRuntimeEnv();
1071
1146
  const provider2 = getConfiguredProvider(values);
@@ -1151,6 +1226,7 @@ async function printProjectStatus() {
1151
1226
  const statsPath = join3(process.cwd(), ".memory-core-stats.json");
1152
1227
  const dbError = await verifyDatabaseConnection(values.DATABASE_URL ?? "");
1153
1228
  const ollamaError = await verifyOllamaConnection(values.OLLAMA_URL ?? DEFAULT_OLLAMA_URL);
1229
+ const graphCount = await getGraphSnapshotCount(process.cwd());
1154
1230
  console.log(chalk2.bold("\n memory-core status\n"));
1155
1231
  printStatusLine("Project", config?.projectName ?? process.cwd().split("/").pop() ?? "unknown");
1156
1232
  printStatusLine("Project type", config?.projectType ?? chalk2.yellow("not initialized"));
@@ -1168,6 +1244,9 @@ async function printProjectStatus() {
1168
1244
  printStatusLine("Generated files", String(generatedFiles.length));
1169
1245
  printStatusLine("Hook", existsSync3(hookPath) ? "installed" : "not installed");
1170
1246
  printStatusLine("Stats file", existsSync3(statsPath) ? ".memory-core-stats.json" : chalk2.gray("none"));
1247
+ printStatusLine("Graph backend", graphBackendLabel(values));
1248
+ printStatusLine("Graph store", graphStoreFilePath(process.cwd()));
1249
+ printStatusLine("Graph snapshots", graphCount === null ? chalk2.gray("unavailable") : String(graphCount));
1171
1250
  console.log();
1172
1251
  printStatusLine("Database URL", redactDatabaseUrl(values.DATABASE_URL ?? ""));
1173
1252
  printStatusLine("Ollama URL", values.OLLAMA_URL ?? DEFAULT_OLLAMA_URL);
@@ -1655,8 +1734,7 @@ program.command("remember <text>").description("Save a new memory to the central
1655
1734
  }
1656
1735
  const spinner = ora("Saving memory\u2026").start();
1657
1736
  try {
1658
- const embedding = await embed(text);
1659
- await saveMemory({
1737
+ await phase1.services.memoryEngine.remember({
1660
1738
  type: opts.type,
1661
1739
  scope: opts.scope,
1662
1740
  architecture: config?.backendArchitecture ?? config?.frontendFramework,
@@ -1664,8 +1742,7 @@ program.command("remember <text>").description("Save a new memory to the central
1664
1742
  content: text,
1665
1743
  reason: reason || void 0,
1666
1744
  context: buildMemoryContext(opts),
1667
- tags: parseTags(opts.tags),
1668
- embedding
1745
+ tags: parseTags(opts.tags)
1669
1746
  });
1670
1747
  const reasonLine = reason ? chalk2.gray(`
1671
1748
  Why: ${reason}`) : "";
@@ -1682,11 +1759,12 @@ program.command("search <query>").description("Search memories using semantic si
1682
1759
  const spinner = ora("Searching\u2026").start();
1683
1760
  try {
1684
1761
  const architectures = inferProjectArchitectures(process.cwd(), config);
1685
- const results = await retrieve(
1686
- query,
1762
+ const result = await phase1.services.retrievalEngine.retrieve({
1763
+ text: query,
1687
1764
  architectures,
1688
- parseInt(opts.limit, 10)
1689
- );
1765
+ limit: parseInt(opts.limit, 10)
1766
+ });
1767
+ const results = result.items;
1690
1768
  spinner.stop();
1691
1769
  if (results.length === 0) {
1692
1770
  console.log(chalk2.yellow("No memories found."));
@@ -1715,8 +1793,8 @@ program.command("search <query>").description("Search memories using semantic si
1715
1793
  program.command("export").description(`Export DB memories to ${MEMORY_FILE}`).option("-o, --output <file>", `Output file (default: ${MEMORY_FILE})`).action(async (opts) => {
1716
1794
  const spinner = ora("Exporting memories\u2026").start();
1717
1795
  try {
1718
- const memories = await listMemories({ limit: 1e4 });
1719
- const portable = memories.map(toPortableMemory);
1796
+ const memories = await phase1.services.memoryEngine.list({ limit: 1e4 });
1797
+ const portable = memories.map(toPortableFromRecord);
1720
1798
  const outputPath = opts.output ? join3(process.cwd(), opts.output) : writeMemoryFile(portable);
1721
1799
  if (opts.output) {
1722
1800
  writeFileSync3(outputPath, JSON.stringify(portable, null, 2) + "\n", "utf-8");
@@ -1738,8 +1816,7 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
1738
1816
  let skipped = 0;
1739
1817
  spinner.text = `Importing ${memories.length} memories\u2026`;
1740
1818
  for (const memory of memories) {
1741
- const embedding = await embed(memory.content);
1742
- const result = await upsertMemory({
1819
+ const result = await phase1.services.memoryEngine.remember({
1743
1820
  type: memory.type,
1744
1821
  scope: memory.scope,
1745
1822
  architecture: memory.architecture,
@@ -1748,8 +1825,7 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
1748
1825
  content: memory.content,
1749
1826
  reason: memory.reason,
1750
1827
  context: memory.context,
1751
- tags: memory.tags,
1752
- embedding
1828
+ tags: memory.tags
1753
1829
  });
1754
1830
  if (result === "inserted") inserted++;
1755
1831
  else skipped++;
@@ -1770,7 +1846,7 @@ program.command("list").description("List memories from the local database").opt
1770
1846
  const config = readProjectConfig();
1771
1847
  const architectures = opts.arch ? opts.arch : opts.all ? void 0 : getCurrentListArchitectures(config);
1772
1848
  const projectName = opts.project ? opts.project : opts.all || opts.arch ? void 0 : config?.projectName;
1773
- const memories = await listMemories({
1849
+ const memories = await phase1.services.memoryEngine.list({
1774
1850
  type: opts.type,
1775
1851
  scope: opts.scope,
1776
1852
  architecture: architectures,
@@ -1779,7 +1855,7 @@ program.command("list").description("List memories from the local database").opt
1779
1855
  limit: parseInt(opts.limit, 10)
1780
1856
  });
1781
1857
  const title = opts.all ? "All memories" : `Current project memories${architectures ? ` (${Array.isArray(architectures) ? architectures.join(", ") : architectures})` : ""}`;
1782
- printMemoryTable(memories, title);
1858
+ printMemoryTable(memories.map(toMemoryTableRow), title);
1783
1859
  if (!opts.all) {
1784
1860
  console.log(chalk2.gray(" Showing current project context plus shared/global memories. Use --all for the full database.\n"));
1785
1861
  }
@@ -1793,7 +1869,7 @@ program.command("list").description("List memories from the local database").opt
1793
1869
  program.command("remove <id>").description("Remove a memory by ID").option("--no-sync", "Skip automatic agent file sync after removal").action(async (id, opts) => {
1794
1870
  try {
1795
1871
  const config = readProjectConfig();
1796
- const deleted = await deleteMemory(parseInt(id, 10));
1872
+ const deleted = await phase1.services.memoryEngine.removeById(parseInt(id, 10));
1797
1873
  if (!deleted) {
1798
1874
  console.log(chalk2.yellow(`No memory found with ID ${id}`));
1799
1875
  process.exit(1);
@@ -1810,7 +1886,7 @@ program.command("remove <id>").description("Remove a memory by ID").option("--no
1810
1886
  program.command("forget").description("Bulk-delete memories by tag, scope, type, or architecture").option("--tag <tag>", "Delete memories with this tag").option("--scope <scope>", "Filter by scope").option("--type <type>", "Filter by type").option("--arch <architecture>", "Filter by architecture").option("--no-sync", "Skip automatic agent file sync after deletion").action(async (opts) => {
1811
1887
  try {
1812
1888
  const config = readProjectConfig();
1813
- const deleted = await deleteMemories({
1889
+ const deleted = await phase1.services.memoryEngine.removeMany({
1814
1890
  tag: opts.tag,
1815
1891
  scope: opts.scope,
1816
1892
  type: opts.type,
@@ -1831,7 +1907,7 @@ program.command("edit <id>").description("Edit a memory interactively").option("
1831
1907
  const memoryId = parseInt(id, 10);
1832
1908
  try {
1833
1909
  const config = readProjectConfig();
1834
- const existing = await getMemory(memoryId);
1910
+ const existing = await phase1.services.memoryEngine.getById(memoryId);
1835
1911
  if (!existing) {
1836
1912
  console.log(chalk2.yellow(`No memory found with ID ${id}`));
1837
1913
  process.exit(1);
@@ -1846,16 +1922,14 @@ program.command("edit <id>").description("Edit a memory interactively").option("
1846
1922
  const examples = await input({ message: "Examples? (comma-separated)", default: existing.context?.examples?.join(",") ?? "" });
1847
1923
  const source = await input({ message: "Source?", default: existing.context?.source ?? "" });
1848
1924
  const tags = await input({ message: "Tags?", default: existing.tags.join(",") });
1849
- const embedding = content === existing.content ? void 0 : await embed(content);
1850
- await updateMemory(memoryId, {
1925
+ await phase1.services.memoryEngine.update(memoryId, {
1851
1926
  type,
1852
1927
  scope,
1853
1928
  title: title || void 0,
1854
1929
  content,
1855
1930
  reason: reason || void 0,
1856
1931
  context: buildMemoryContext({ appliesTo, avoidWhen, example: examples, source }),
1857
- tags: parseTags(tags),
1858
- embedding
1932
+ tags: parseTags(tags)
1859
1933
  });
1860
1934
  console.log(chalk2.green(`Updated memory ${id}`));
1861
1935
  await autoSyncGeneratedFiles(config, "edit", opts.sync);
@@ -1870,11 +1944,12 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
1870
1944
  try {
1871
1945
  const config = readProjectConfig();
1872
1946
  if (opts.list) {
1873
- printMemoryTable(await listMemories({ type: "ignore", limit: 1e3 }), "Ignored patterns");
1947
+ const ignores = await phase1.services.memoryEngine.list({ type: "ignore", limit: 1e3 });
1948
+ printMemoryTable(ignores.map(toMemoryTableRow), "Ignored patterns");
1874
1949
  return;
1875
1950
  }
1876
1951
  if (opts.remove) {
1877
- const deleted = await deleteMemory(parseInt(opts.remove, 10));
1952
+ const deleted = await phase1.services.memoryEngine.removeById(parseInt(opts.remove, 10));
1878
1953
  if (!deleted) {
1879
1954
  console.log(chalk2.yellow(`No ignore pattern found with ID ${opts.remove}`));
1880
1955
  process.exit(1);
@@ -1887,15 +1962,13 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
1887
1962
  console.error(chalk2.red("Provide a pattern, --list, or --remove <id>"));
1888
1963
  process.exit(1);
1889
1964
  }
1890
- const embedding = await embed(pattern);
1891
- await upsertMemory({
1965
+ await phase1.services.memoryEngine.remember({
1892
1966
  type: "ignore",
1893
1967
  scope: "project",
1894
1968
  architecture: config?.backendArchitecture ?? config?.frontendFramework,
1895
1969
  projectName: config?.projectName,
1896
1970
  content: pattern,
1897
- tags: ["ignore"],
1898
- embedding
1971
+ tags: ["ignore"]
1899
1972
  });
1900
1973
  console.log(chalk2.green(`Ignored pattern saved: "${pattern}"`));
1901
1974
  await autoSyncGeneratedFiles(config, "ignore", opts.sync);
@@ -2020,7 +2093,7 @@ program.command("stats").description("Show violation counters recorded by check
2020
2093
  console.log();
2021
2094
  });
2022
2095
  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) => {
2023
- const { startDashboard } = await import("./dashboard-server-QMNMSEPJ.js");
2096
+ const { startDashboard } = await import("./dashboard-server-AUX4BQP6.js");
2024
2097
  await startDashboard({
2025
2098
  port: parseInt(opts.port, 10),
2026
2099
  path: opts.path,
@@ -2038,7 +2111,6 @@ program.command("seed").description("Load all predefined memories into the datab
2038
2111
  for (const seed of filtered) {
2039
2112
  const spinner = ora(`[${seed.architecture}] ${seed.title}`).start();
2040
2113
  try {
2041
- const embedding = await embed(seed.content);
2042
2114
  const payload = {
2043
2115
  type: seed.type,
2044
2116
  scope: seed.scope,
@@ -2046,15 +2118,14 @@ program.command("seed").description("Load all predefined memories into the datab
2046
2118
  title: seed.title,
2047
2119
  content: seed.content,
2048
2120
  reason: seed.reason,
2049
- tags: seed.tags,
2050
- embedding
2121
+ tags: seed.tags
2051
2122
  };
2052
2123
  if (opts.force) {
2053
- await saveMemory(payload);
2124
+ await phase1.services.memoryEngine.rememberForce(payload);
2054
2125
  saved++;
2055
2126
  spinner.succeed(chalk2.gray(`[${seed.architecture}] ${seed.title}`));
2056
2127
  } else {
2057
- const result = await upsertMemory(payload);
2128
+ const result = await phase1.services.memoryEngine.remember(payload);
2058
2129
  if (result === "inserted") {
2059
2130
  saved++;
2060
2131
  spinner.succeed(chalk2.gray(`[${seed.architecture}] ${seed.title}`));
@@ -2097,7 +2168,11 @@ program.command("global").description("Sync your memory into every AI agent glob
2097
2168
  let memories = [];
2098
2169
  try {
2099
2170
  const architectures = opts.arch ? [opts.arch] : inferProjectArchitectures(process.cwd(), readProjectConfig());
2100
- memories = dedupeMemories(await retrieve("architecture rules coding standards", architectures, 30));
2171
+ memories = (await phase1.services.retrievalEngine.retrieve({
2172
+ text: "architecture rules coding standards",
2173
+ architectures,
2174
+ limit: 30
2175
+ })).items;
2101
2176
  } catch (err) {
2102
2177
  spinner.fail(`Could not fetch memories: ${err.message}`);
2103
2178
  process.exit(1);
@@ -2256,6 +2331,164 @@ program.command("status").description("Show the current memory-core project and
2256
2331
  process.exit(1);
2257
2332
  }
2258
2333
  });
2334
+ var graph = program.command("graph").description("Build and inspect dependency graph snapshots");
2335
+ graph.command("migrate").description("Create or update PostgreSQL graph snapshot schema").action(async () => {
2336
+ const { values } = readRuntimeEnv();
2337
+ if (!values.DATABASE_URL) {
2338
+ console.error(chalk2.red("Graph migration requires DATABASE_URL. Configure it in .memory-core.env or .env."));
2339
+ process.exit(1);
2340
+ }
2341
+ const spinner = ora("Migrating graph snapshot schema\u2026").start();
2342
+ try {
2343
+ await migrateGraphSnapshots();
2344
+ spinner.succeed("Graph snapshot schema is ready in PostgreSQL");
2345
+ } catch (err) {
2346
+ spinner.fail(`Graph migration failed: ${err.message}`);
2347
+ process.exit(1);
2348
+ }
2349
+ });
2350
+ graph.command("doctor").description("Inspect graph storage backend health (PostgreSQL + file fallback)").option("--path <dir>", "Project root path (default: current directory)").action(async (opts) => {
2351
+ const cwd = resolve(opts.path ?? process.cwd());
2352
+ const { values } = readRuntimeEnv();
2353
+ const filePath = graphStoreFilePath(cwd);
2354
+ const usesPostgres = Boolean(values.DATABASE_URL);
2355
+ let ok = true;
2356
+ console.log(chalk2.bold("\n graph doctor\n"));
2357
+ printStatusLine("Project path", cwd);
2358
+ printStatusLine("Configured backend", usesPostgres ? "postgres + file fallback" : "file");
2359
+ printStatusLine("File store", filePath);
2360
+ console.log();
2361
+ if (!usesPostgres) {
2362
+ console.log(chalk2.yellow(" \u26A0 DATABASE_URL not set \u2014 using file backend only."));
2363
+ } else {
2364
+ try {
2365
+ await migrateGraphSnapshots();
2366
+ const probe = await probeGraphSnapshotStore(cwd);
2367
+ console.log(chalk2.green(" \u2713 Graph PostgreSQL") + chalk2.dim(` ready (${probe.snapshotCount} snapshot records for this root)`));
2368
+ } catch (err) {
2369
+ ok = false;
2370
+ console.log(chalk2.red(" \u2717 Graph PostgreSQL") + chalk2.dim(` ${err.message}`));
2371
+ }
2372
+ }
2373
+ try {
2374
+ const count = await getGraphSnapshotCount(cwd);
2375
+ console.log(chalk2.green(" \u2713 Graph service") + chalk2.dim(` readable (${count ?? 0} snapshots visible)`));
2376
+ } catch (err) {
2377
+ ok = false;
2378
+ console.log(chalk2.red(" \u2717 Graph service") + chalk2.dim(` ${err.message}`));
2379
+ }
2380
+ if (existsSync3(filePath)) {
2381
+ console.log(chalk2.green(" \u2713 File fallback") + chalk2.dim(" store exists"));
2382
+ } else {
2383
+ console.log(chalk2.yellow(" \u26A0 File fallback") + chalk2.dim(" store not created yet (run graph build once)"));
2384
+ }
2385
+ console.log();
2386
+ if (!ok) process.exit(1);
2387
+ });
2388
+ graph.command("build").description("Build a dependency graph snapshot and persist it").option("--path <dir>", "Project root path (default: current directory)").action(async (opts) => {
2389
+ const cwd = resolve(opts.path ?? process.cwd());
2390
+ const spinner = ora("Building dependency graph\u2026").start();
2391
+ try {
2392
+ const { snapshot } = await phase1.services.graphEngine.buildAndStoreSnapshot({ cwd });
2393
+ spinner.succeed(`Graph snapshot saved: ${snapshot.id}`);
2394
+ console.log(chalk2.gray(` Root: ${snapshot.rootPath}`));
2395
+ console.log(chalk2.gray(` Nodes: ${snapshot.nodes.length}`));
2396
+ console.log(chalk2.gray(` Edges: ${snapshot.edges.length}
2397
+ `));
2398
+ } catch (err) {
2399
+ spinner.fail(`Graph build failed: ${err.message}`);
2400
+ process.exit(1);
2401
+ }
2402
+ });
2403
+ graph.command("list").description("List saved dependency graph snapshots").option("--path <dir>", "Project root path (default: current directory)").option("-n, --limit <n>", "Number of snapshots to list", "10").action(async (opts) => {
2404
+ const cwd = resolve(opts.path ?? process.cwd());
2405
+ try {
2406
+ const snapshots = await phase1.services.graphEngine.listSnapshots(cwd, parseInt(opts.limit, 10));
2407
+ if (snapshots.length === 0) {
2408
+ console.log(chalk2.yellow("\n No graph snapshots found. Run: memory-core graph build\n"));
2409
+ return;
2410
+ }
2411
+ console.log(chalk2.bold("\n Graph snapshots\n"));
2412
+ snapshots.forEach((snapshot, index) => {
2413
+ console.log(` ${index + 1}. ${snapshot.id} ${chalk2.dim(snapshot.createdAt)} nodes=${snapshot.nodes.length} edges=${snapshot.edges.length}`);
2414
+ });
2415
+ console.log();
2416
+ } catch (err) {
2417
+ console.error(chalk2.red(`Graph list failed: ${err.message}`));
2418
+ process.exit(1);
2419
+ }
2420
+ });
2421
+ graph.command("show [snapshotId]").description("Show a saved graph snapshot (latest by default)").option("--path <dir>", "Project root path (default: current directory)").option("--edges <n>", "Number of edges to print", "25").action(async (snapshotId, opts) => {
2422
+ const cwd = resolve(opts.path ?? process.cwd());
2423
+ try {
2424
+ const snapshot = snapshotId ? await phase1.services.graphEngine.getSnapshot(cwd, snapshotId) : await phase1.services.graphEngine.latest(cwd);
2425
+ if (!snapshot) {
2426
+ console.log(chalk2.yellow("\n No matching graph snapshot found. Run: memory-core graph build\n"));
2427
+ return;
2428
+ }
2429
+ console.log(chalk2.bold(`
2430
+ Graph ${snapshot.id ?? "(latest)"}
2431
+ `));
2432
+ console.log(chalk2.gray(` Root: ${snapshot.rootPath}`));
2433
+ console.log(chalk2.gray(` Created: ${snapshot.createdAt ?? "unknown"}`));
2434
+ console.log(chalk2.gray(` Nodes: ${snapshot.nodes.length}`));
2435
+ console.log(chalk2.gray(` Edges: ${snapshot.edges.length}
2436
+ `));
2437
+ const limit = parseInt(opts.edges, 10);
2438
+ snapshot.edges.slice(0, limit).forEach((edge, index) => {
2439
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2440
+ });
2441
+ if (snapshot.edges.length > limit) {
2442
+ console.log(chalk2.dim(`
2443
+ \u2026 ${snapshot.edges.length - limit} more edges not shown
2444
+ `));
2445
+ } else {
2446
+ console.log();
2447
+ }
2448
+ } catch (err) {
2449
+ console.error(chalk2.red(`Graph show failed: ${err.message}`));
2450
+ process.exit(1);
2451
+ }
2452
+ });
2453
+ graph.command("diff <leftSnapshotId> [rightSnapshotId]").description("Diff two snapshots (right defaults to latest)").option("--path <dir>", "Project root path (default: current directory)").action(async (leftSnapshotId, rightSnapshotId, opts) => {
2454
+ const cwd = resolve(opts.path ?? process.cwd());
2455
+ try {
2456
+ const left = await phase1.services.graphEngine.getSnapshot(cwd, leftSnapshotId);
2457
+ if (!left) {
2458
+ console.error(chalk2.red(`Left snapshot not found: ${leftSnapshotId}`));
2459
+ process.exit(1);
2460
+ }
2461
+ const right = rightSnapshotId ? await phase1.services.graphEngine.getSnapshot(cwd, rightSnapshotId) : await phase1.services.graphEngine.latest(cwd);
2462
+ if (!right) {
2463
+ console.error(chalk2.red(`Right snapshot not found${rightSnapshotId ? `: ${rightSnapshotId}` : ""}`));
2464
+ process.exit(1);
2465
+ }
2466
+ const diff = phase1.services.graphEngine.diffGraphs(left, right);
2467
+ console.log(chalk2.bold("\n Graph diff\n"));
2468
+ console.log(chalk2.gray(` Left: ${left.id}`));
2469
+ console.log(chalk2.gray(` Right: ${right.id}`));
2470
+ console.log(chalk2.green(` + Nodes: ${diff.addedNodes.length}`));
2471
+ console.log(chalk2.red(` - Nodes: ${diff.removedNodes.length}`));
2472
+ console.log(chalk2.green(` + Edges: ${diff.addedEdges.length}`));
2473
+ console.log(chalk2.red(` - Edges: ${diff.removedEdges.length}`));
2474
+ if (diff.addedEdges.length > 0) {
2475
+ console.log(chalk2.green("\n Added edges"));
2476
+ diff.addedEdges.slice(0, 20).forEach((edge, index) => {
2477
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2478
+ });
2479
+ }
2480
+ if (diff.removedEdges.length > 0) {
2481
+ console.log(chalk2.red("\n Removed edges"));
2482
+ diff.removedEdges.slice(0, 20).forEach((edge, index) => {
2483
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2484
+ });
2485
+ }
2486
+ console.log();
2487
+ } catch (err) {
2488
+ console.error(chalk2.red(`Graph diff failed: ${err.message}`));
2489
+ process.exit(1);
2490
+ }
2491
+ });
2259
2492
  var hook = program.command("hook").description("Manage the pre-commit rule enforcement hook");
2260
2493
  hook.command("install").description("Install pre-commit hook (advisory mode by default \u2014 logs violations, never blocks)").option("--advisory", "Log violations but never block commits (default)").option("--strict", "Block commits that violate your rules").action((opts) => {
2261
2494
  const advisory = opts.strict ? false : true;
@@ -2271,7 +2504,11 @@ program.command("check").description("Check staged changes against architecture
2271
2504
  }
2272
2505
  await checkStaged({ verbose: opts.verbose ?? false, debug: opts.debug ?? false });
2273
2506
  });
2274
- 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("--verbose", "Show diff size and model details per file").option("--debug", "Show prompt, diff, and raw model response").action((opts) => {
2275
- startWatch({ path: opts.path, verbose: opts.verbose, debug: opts.debug });
2507
+ 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("--verbose", "Show diff size and model details per file").option("--debug", "Show prompt, diff, and raw model response").action(async (opts) => {
2508
+ await phase1.providers.watchService.start({
2509
+ path: opts.path,
2510
+ verbose: opts.verbose,
2511
+ debug: opts.debug
2512
+ });
2276
2513
  });
2277
2514
  program.parseAsync(process.argv);