@shahmilsaari/memory-core 1.0.0 → 1.0.2

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
@@ -173,6 +161,7 @@ function recordViolations(violations, source = "hook") {
173
161
  async function promptToSaveViolations(violations) {
174
162
  if (!process.stdin.isTTY || violations.length === 0) return;
175
163
  try {
164
+ const app = getDefaultApplicationContainer();
176
165
  const { confirm: confirm2, input: input2 } = await import("@inquirer/prompts");
177
166
  const save = await confirm2({
178
167
  message: "Save a caught violation as a project rule?",
@@ -185,15 +174,12 @@ async function promptToSaveViolations(violations) {
185
174
  message: "Why should this rule exist?",
186
175
  default: selected.reason ?? selected.issue ?? ""
187
176
  });
188
- const { embed: embed2 } = await import("./embedding-PAYD2JYW.js");
189
- const { upsertMemory: upsertMemory2 } = await import("./db-MF3VKVKH.js");
190
- await upsertMemory2({
177
+ await app.services.memoryEngine.remember({
191
178
  type: "rule",
192
179
  scope: "project",
193
180
  content: selected.rule,
194
181
  reason: reason || void 0,
195
- tags: ["violation"],
196
- embedding: await embed2(selected.rule)
182
+ tags: ["violation"]
197
183
  });
198
184
  console.log(chalk.green(" \u2713 Saved as project rule. Run memory-core sync to propagate it.\n"));
199
185
  } catch (err) {
@@ -203,9 +189,8 @@ async function promptToSaveViolations(violations) {
203
189
  }
204
190
  async function loadIgnorePatterns() {
205
191
  try {
206
- const { listMemories: listMemories2, closePool: closePool2 } = await import("./db-MF3VKVKH.js");
207
- const ignores = await listMemories2({ type: "ignore", limit: 1e3 });
208
- await closePool2();
192
+ const app = getDefaultApplicationContainer();
193
+ const ignores = await app.services.memoryEngine.list({ type: "ignore", limit: 1e3 });
209
194
  return ignores.map((ignore) => ignore.content);
210
195
  } catch {
211
196
  return [];
@@ -526,6 +511,12 @@ Do not include any text outside the JSON object.`;
526
511
  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
512
  }
528
513
  const deterministicViolations = findDeterministicViolations(diff, rules, avoids, allowPatterns);
514
+ const astViolations = findAstDeterministicViolationsForDiff(diff, {
515
+ cwd: process.cwd(),
516
+ config,
517
+ rules,
518
+ reasonLookup: reasonMap
519
+ });
529
520
  let modelViolations = [];
530
521
  try {
531
522
  const raw = await callChatModel([
@@ -559,7 +550,7 @@ ${diffToSend}` }
559
550
  }
560
551
  }
561
552
  modelViolations = await verifyViolations(diff, modelViolations, allowPatterns, options.debug ?? false);
562
- let violations = dedupeViolations([...deterministicViolations, ...modelViolations]);
553
+ let violations = dedupeViolations([...deterministicViolations, ...astViolations, ...modelViolations]);
563
554
  violations = applyAllowPatterns(violations, allowPatterns);
564
555
  if (violations.length === 0) {
565
556
  console.log(chalk.green(" \u2713 No rule violations \u2014 commit allowed.\n"));
@@ -806,6 +797,7 @@ var GITIGNORE_HEADING = "# memory-core generated files";
806
797
  var DEFAULT_OLLAMA_URL = "http://localhost:11434";
807
798
  var DEFAULT_EMBEDDING_MODEL = "nomic-embed-text";
808
799
  var DEFAULT_CHAT_MODEL = "llama3.2";
800
+ var phase1 = getDefaultApplicationContainer();
809
801
  function getEnvPath() {
810
802
  const memoryEnv = join3(process.cwd(), ".memory-core.env");
811
803
  if (existsSync3(memoryEnv)) return memoryEnv;
@@ -1045,6 +1037,35 @@ function truncate(value, length) {
1045
1037
  if (!value) return "";
1046
1038
  return value.length > length ? `${value.slice(0, Math.max(0, length - 1))}\u2026` : value;
1047
1039
  }
1040
+ function toMemoryTableRow(memory) {
1041
+ return {
1042
+ id: memory.id,
1043
+ type: memory.type,
1044
+ scope: memory.scope,
1045
+ title: memory.title,
1046
+ content: memory.content,
1047
+ reason: memory.reason,
1048
+ context: memory.context,
1049
+ tags: memory.tags ?? [],
1050
+ similarity: memory.similarity
1051
+ };
1052
+ }
1053
+ function toPortableFromRecord(memory) {
1054
+ return toPortableMemory({
1055
+ id: memory.id,
1056
+ type: memory.type,
1057
+ scope: memory.scope,
1058
+ architecture: memory.architecture,
1059
+ project_name: memory.projectName,
1060
+ title: memory.title,
1061
+ content: memory.content,
1062
+ reason: memory.reason,
1063
+ context: memory.context,
1064
+ tags: memory.tags ?? [],
1065
+ content_hash: memory.contentHash,
1066
+ similarity: memory.similarity
1067
+ });
1068
+ }
1048
1069
  function printMemoryTable(memories, title = "Rules in memory") {
1049
1070
  console.log(chalk2.bold(`
1050
1071
  ${title} (${memories.length} total)
@@ -1066,6 +1087,23 @@ function getCurrentListArchitectures(config) {
1066
1087
  function printStatusLine(label, value) {
1067
1088
  console.log(` ${chalk2.dim(label.padEnd(18))} ${value}`);
1068
1089
  }
1090
+ function abbreviate(value, max = 96) {
1091
+ return value.length > max ? `${value.slice(0, max - 1)}\u2026` : value;
1092
+ }
1093
+ function graphStoreFilePath(cwd = process.cwd()) {
1094
+ return join3(cwd, ".memory-core", "graph-snapshots.json");
1095
+ }
1096
+ function graphBackendLabel(values) {
1097
+ return values.DATABASE_URL ? "postgres (file fallback enabled)" : "file";
1098
+ }
1099
+ async function getGraphSnapshotCount(cwd = process.cwd()) {
1100
+ try {
1101
+ const snapshots = await phase1.services.graphEngine.listSnapshots(resolve(cwd), 1e4);
1102
+ return snapshots.length;
1103
+ } catch {
1104
+ return null;
1105
+ }
1106
+ }
1069
1107
  async function runModelDoctor() {
1070
1108
  const { envPath, values } = readRuntimeEnv();
1071
1109
  const provider2 = getConfiguredProvider(values);
@@ -1151,6 +1189,7 @@ async function printProjectStatus() {
1151
1189
  const statsPath = join3(process.cwd(), ".memory-core-stats.json");
1152
1190
  const dbError = await verifyDatabaseConnection(values.DATABASE_URL ?? "");
1153
1191
  const ollamaError = await verifyOllamaConnection(values.OLLAMA_URL ?? DEFAULT_OLLAMA_URL);
1192
+ const graphCount = await getGraphSnapshotCount(process.cwd());
1154
1193
  console.log(chalk2.bold("\n memory-core status\n"));
1155
1194
  printStatusLine("Project", config?.projectName ?? process.cwd().split("/").pop() ?? "unknown");
1156
1195
  printStatusLine("Project type", config?.projectType ?? chalk2.yellow("not initialized"));
@@ -1168,6 +1207,9 @@ async function printProjectStatus() {
1168
1207
  printStatusLine("Generated files", String(generatedFiles.length));
1169
1208
  printStatusLine("Hook", existsSync3(hookPath) ? "installed" : "not installed");
1170
1209
  printStatusLine("Stats file", existsSync3(statsPath) ? ".memory-core-stats.json" : chalk2.gray("none"));
1210
+ printStatusLine("Graph backend", graphBackendLabel(values));
1211
+ printStatusLine("Graph store", graphStoreFilePath(process.cwd()));
1212
+ printStatusLine("Graph snapshots", graphCount === null ? chalk2.gray("unavailable") : String(graphCount));
1171
1213
  console.log();
1172
1214
  printStatusLine("Database URL", redactDatabaseUrl(values.DATABASE_URL ?? ""));
1173
1215
  printStatusLine("Ollama URL", values.OLLAMA_URL ?? DEFAULT_OLLAMA_URL);
@@ -1655,8 +1697,7 @@ program.command("remember <text>").description("Save a new memory to the central
1655
1697
  }
1656
1698
  const spinner = ora("Saving memory\u2026").start();
1657
1699
  try {
1658
- const embedding = await embed(text);
1659
- await saveMemory({
1700
+ await phase1.services.memoryEngine.remember({
1660
1701
  type: opts.type,
1661
1702
  scope: opts.scope,
1662
1703
  architecture: config?.backendArchitecture ?? config?.frontendFramework,
@@ -1664,8 +1705,7 @@ program.command("remember <text>").description("Save a new memory to the central
1664
1705
  content: text,
1665
1706
  reason: reason || void 0,
1666
1707
  context: buildMemoryContext(opts),
1667
- tags: parseTags(opts.tags),
1668
- embedding
1708
+ tags: parseTags(opts.tags)
1669
1709
  });
1670
1710
  const reasonLine = reason ? chalk2.gray(`
1671
1711
  Why: ${reason}`) : "";
@@ -1682,11 +1722,12 @@ program.command("search <query>").description("Search memories using semantic si
1682
1722
  const spinner = ora("Searching\u2026").start();
1683
1723
  try {
1684
1724
  const architectures = inferProjectArchitectures(process.cwd(), config);
1685
- const results = await retrieve(
1686
- query,
1725
+ const result = await phase1.services.retrievalEngine.retrieve({
1726
+ text: query,
1687
1727
  architectures,
1688
- parseInt(opts.limit, 10)
1689
- );
1728
+ limit: parseInt(opts.limit, 10)
1729
+ });
1730
+ const results = result.items;
1690
1731
  spinner.stop();
1691
1732
  if (results.length === 0) {
1692
1733
  console.log(chalk2.yellow("No memories found."));
@@ -1715,8 +1756,8 @@ program.command("search <query>").description("Search memories using semantic si
1715
1756
  program.command("export").description(`Export DB memories to ${MEMORY_FILE}`).option("-o, --output <file>", `Output file (default: ${MEMORY_FILE})`).action(async (opts) => {
1716
1757
  const spinner = ora("Exporting memories\u2026").start();
1717
1758
  try {
1718
- const memories = await listMemories({ limit: 1e4 });
1719
- const portable = memories.map(toPortableMemory);
1759
+ const memories = await phase1.services.memoryEngine.list({ limit: 1e4 });
1760
+ const portable = memories.map(toPortableFromRecord);
1720
1761
  const outputPath = opts.output ? join3(process.cwd(), opts.output) : writeMemoryFile(portable);
1721
1762
  if (opts.output) {
1722
1763
  writeFileSync3(outputPath, JSON.stringify(portable, null, 2) + "\n", "utf-8");
@@ -1738,8 +1779,7 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
1738
1779
  let skipped = 0;
1739
1780
  spinner.text = `Importing ${memories.length} memories\u2026`;
1740
1781
  for (const memory of memories) {
1741
- const embedding = await embed(memory.content);
1742
- const result = await upsertMemory({
1782
+ const result = await phase1.services.memoryEngine.remember({
1743
1783
  type: memory.type,
1744
1784
  scope: memory.scope,
1745
1785
  architecture: memory.architecture,
@@ -1748,8 +1788,7 @@ program.command("import").description(`Import memories from ${MEMORY_FILE}`).opt
1748
1788
  content: memory.content,
1749
1789
  reason: memory.reason,
1750
1790
  context: memory.context,
1751
- tags: memory.tags,
1752
- embedding
1791
+ tags: memory.tags
1753
1792
  });
1754
1793
  if (result === "inserted") inserted++;
1755
1794
  else skipped++;
@@ -1770,7 +1809,7 @@ program.command("list").description("List memories from the local database").opt
1770
1809
  const config = readProjectConfig();
1771
1810
  const architectures = opts.arch ? opts.arch : opts.all ? void 0 : getCurrentListArchitectures(config);
1772
1811
  const projectName = opts.project ? opts.project : opts.all || opts.arch ? void 0 : config?.projectName;
1773
- const memories = await listMemories({
1812
+ const memories = await phase1.services.memoryEngine.list({
1774
1813
  type: opts.type,
1775
1814
  scope: opts.scope,
1776
1815
  architecture: architectures,
@@ -1779,7 +1818,7 @@ program.command("list").description("List memories from the local database").opt
1779
1818
  limit: parseInt(opts.limit, 10)
1780
1819
  });
1781
1820
  const title = opts.all ? "All memories" : `Current project memories${architectures ? ` (${Array.isArray(architectures) ? architectures.join(", ") : architectures})` : ""}`;
1782
- printMemoryTable(memories, title);
1821
+ printMemoryTable(memories.map(toMemoryTableRow), title);
1783
1822
  if (!opts.all) {
1784
1823
  console.log(chalk2.gray(" Showing current project context plus shared/global memories. Use --all for the full database.\n"));
1785
1824
  }
@@ -1793,7 +1832,7 @@ program.command("list").description("List memories from the local database").opt
1793
1832
  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
1833
  try {
1795
1834
  const config = readProjectConfig();
1796
- const deleted = await deleteMemory(parseInt(id, 10));
1835
+ const deleted = await phase1.services.memoryEngine.removeById(parseInt(id, 10));
1797
1836
  if (!deleted) {
1798
1837
  console.log(chalk2.yellow(`No memory found with ID ${id}`));
1799
1838
  process.exit(1);
@@ -1810,7 +1849,7 @@ program.command("remove <id>").description("Remove a memory by ID").option("--no
1810
1849
  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
1850
  try {
1812
1851
  const config = readProjectConfig();
1813
- const deleted = await deleteMemories({
1852
+ const deleted = await phase1.services.memoryEngine.removeMany({
1814
1853
  tag: opts.tag,
1815
1854
  scope: opts.scope,
1816
1855
  type: opts.type,
@@ -1831,7 +1870,7 @@ program.command("edit <id>").description("Edit a memory interactively").option("
1831
1870
  const memoryId = parseInt(id, 10);
1832
1871
  try {
1833
1872
  const config = readProjectConfig();
1834
- const existing = await getMemory(memoryId);
1873
+ const existing = await phase1.services.memoryEngine.getById(memoryId);
1835
1874
  if (!existing) {
1836
1875
  console.log(chalk2.yellow(`No memory found with ID ${id}`));
1837
1876
  process.exit(1);
@@ -1846,16 +1885,14 @@ program.command("edit <id>").description("Edit a memory interactively").option("
1846
1885
  const examples = await input({ message: "Examples? (comma-separated)", default: existing.context?.examples?.join(",") ?? "" });
1847
1886
  const source = await input({ message: "Source?", default: existing.context?.source ?? "" });
1848
1887
  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, {
1888
+ await phase1.services.memoryEngine.update(memoryId, {
1851
1889
  type,
1852
1890
  scope,
1853
1891
  title: title || void 0,
1854
1892
  content,
1855
1893
  reason: reason || void 0,
1856
1894
  context: buildMemoryContext({ appliesTo, avoidWhen, example: examples, source }),
1857
- tags: parseTags(tags),
1858
- embedding
1895
+ tags: parseTags(tags)
1859
1896
  });
1860
1897
  console.log(chalk2.green(`Updated memory ${id}`));
1861
1898
  await autoSyncGeneratedFiles(config, "edit", opts.sync);
@@ -1870,11 +1907,12 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
1870
1907
  try {
1871
1908
  const config = readProjectConfig();
1872
1909
  if (opts.list) {
1873
- printMemoryTable(await listMemories({ type: "ignore", limit: 1e3 }), "Ignored patterns");
1910
+ const ignores = await phase1.services.memoryEngine.list({ type: "ignore", limit: 1e3 });
1911
+ printMemoryTable(ignores.map(toMemoryTableRow), "Ignored patterns");
1874
1912
  return;
1875
1913
  }
1876
1914
  if (opts.remove) {
1877
- const deleted = await deleteMemory(parseInt(opts.remove, 10));
1915
+ const deleted = await phase1.services.memoryEngine.removeById(parseInt(opts.remove, 10));
1878
1916
  if (!deleted) {
1879
1917
  console.log(chalk2.yellow(`No ignore pattern found with ID ${opts.remove}`));
1880
1918
  process.exit(1);
@@ -1887,15 +1925,13 @@ program.command("ignore [pattern]").description("Manage project-scoped false-pos
1887
1925
  console.error(chalk2.red("Provide a pattern, --list, or --remove <id>"));
1888
1926
  process.exit(1);
1889
1927
  }
1890
- const embedding = await embed(pattern);
1891
- await upsertMemory({
1928
+ await phase1.services.memoryEngine.remember({
1892
1929
  type: "ignore",
1893
1930
  scope: "project",
1894
1931
  architecture: config?.backendArchitecture ?? config?.frontendFramework,
1895
1932
  projectName: config?.projectName,
1896
1933
  content: pattern,
1897
- tags: ["ignore"],
1898
- embedding
1934
+ tags: ["ignore"]
1899
1935
  });
1900
1936
  console.log(chalk2.green(`Ignored pattern saved: "${pattern}"`));
1901
1937
  await autoSyncGeneratedFiles(config, "ignore", opts.sync);
@@ -2020,7 +2056,7 @@ program.command("stats").description("Show violation counters recorded by check
2020
2056
  console.log();
2021
2057
  });
2022
2058
  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");
2059
+ const { startDashboard } = await import("./dashboard-server-AUX4BQP6.js");
2024
2060
  await startDashboard({
2025
2061
  port: parseInt(opts.port, 10),
2026
2062
  path: opts.path,
@@ -2038,7 +2074,6 @@ program.command("seed").description("Load all predefined memories into the datab
2038
2074
  for (const seed of filtered) {
2039
2075
  const spinner = ora(`[${seed.architecture}] ${seed.title}`).start();
2040
2076
  try {
2041
- const embedding = await embed(seed.content);
2042
2077
  const payload = {
2043
2078
  type: seed.type,
2044
2079
  scope: seed.scope,
@@ -2046,15 +2081,14 @@ program.command("seed").description("Load all predefined memories into the datab
2046
2081
  title: seed.title,
2047
2082
  content: seed.content,
2048
2083
  reason: seed.reason,
2049
- tags: seed.tags,
2050
- embedding
2084
+ tags: seed.tags
2051
2085
  };
2052
2086
  if (opts.force) {
2053
- await saveMemory(payload);
2087
+ await phase1.services.memoryEngine.rememberForce(payload);
2054
2088
  saved++;
2055
2089
  spinner.succeed(chalk2.gray(`[${seed.architecture}] ${seed.title}`));
2056
2090
  } else {
2057
- const result = await upsertMemory(payload);
2091
+ const result = await phase1.services.memoryEngine.remember(payload);
2058
2092
  if (result === "inserted") {
2059
2093
  saved++;
2060
2094
  spinner.succeed(chalk2.gray(`[${seed.architecture}] ${seed.title}`));
@@ -2097,7 +2131,11 @@ program.command("global").description("Sync your memory into every AI agent glob
2097
2131
  let memories = [];
2098
2132
  try {
2099
2133
  const architectures = opts.arch ? [opts.arch] : inferProjectArchitectures(process.cwd(), readProjectConfig());
2100
- memories = dedupeMemories(await retrieve("architecture rules coding standards", architectures, 30));
2134
+ memories = (await phase1.services.retrievalEngine.retrieve({
2135
+ text: "architecture rules coding standards",
2136
+ architectures,
2137
+ limit: 30
2138
+ })).items;
2101
2139
  } catch (err) {
2102
2140
  spinner.fail(`Could not fetch memories: ${err.message}`);
2103
2141
  process.exit(1);
@@ -2256,6 +2294,164 @@ program.command("status").description("Show the current memory-core project and
2256
2294
  process.exit(1);
2257
2295
  }
2258
2296
  });
2297
+ var graph = program.command("graph").description("Build and inspect dependency graph snapshots");
2298
+ graph.command("migrate").description("Create or update PostgreSQL graph snapshot schema").action(async () => {
2299
+ const { values } = readRuntimeEnv();
2300
+ if (!values.DATABASE_URL) {
2301
+ console.error(chalk2.red("Graph migration requires DATABASE_URL. Configure it in .memory-core.env or .env."));
2302
+ process.exit(1);
2303
+ }
2304
+ const spinner = ora("Migrating graph snapshot schema\u2026").start();
2305
+ try {
2306
+ await migrateGraphSnapshots();
2307
+ spinner.succeed("Graph snapshot schema is ready in PostgreSQL");
2308
+ } catch (err) {
2309
+ spinner.fail(`Graph migration failed: ${err.message}`);
2310
+ process.exit(1);
2311
+ }
2312
+ });
2313
+ graph.command("doctor").description("Inspect graph storage backend health (PostgreSQL + file fallback)").option("--path <dir>", "Project root path (default: current directory)").action(async (opts) => {
2314
+ const cwd = resolve(opts.path ?? process.cwd());
2315
+ const { values } = readRuntimeEnv();
2316
+ const filePath = graphStoreFilePath(cwd);
2317
+ const usesPostgres = Boolean(values.DATABASE_URL);
2318
+ let ok = true;
2319
+ console.log(chalk2.bold("\n graph doctor\n"));
2320
+ printStatusLine("Project path", cwd);
2321
+ printStatusLine("Configured backend", usesPostgres ? "postgres + file fallback" : "file");
2322
+ printStatusLine("File store", filePath);
2323
+ console.log();
2324
+ if (!usesPostgres) {
2325
+ console.log(chalk2.yellow(" \u26A0 DATABASE_URL not set \u2014 using file backend only."));
2326
+ } else {
2327
+ try {
2328
+ await migrateGraphSnapshots();
2329
+ const probe = await probeGraphSnapshotStore(cwd);
2330
+ console.log(chalk2.green(" \u2713 Graph PostgreSQL") + chalk2.dim(` ready (${probe.snapshotCount} snapshot records for this root)`));
2331
+ } catch (err) {
2332
+ ok = false;
2333
+ console.log(chalk2.red(" \u2717 Graph PostgreSQL") + chalk2.dim(` ${err.message}`));
2334
+ }
2335
+ }
2336
+ try {
2337
+ const count = await getGraphSnapshotCount(cwd);
2338
+ console.log(chalk2.green(" \u2713 Graph service") + chalk2.dim(` readable (${count ?? 0} snapshots visible)`));
2339
+ } catch (err) {
2340
+ ok = false;
2341
+ console.log(chalk2.red(" \u2717 Graph service") + chalk2.dim(` ${err.message}`));
2342
+ }
2343
+ if (existsSync3(filePath)) {
2344
+ console.log(chalk2.green(" \u2713 File fallback") + chalk2.dim(" store exists"));
2345
+ } else {
2346
+ console.log(chalk2.yellow(" \u26A0 File fallback") + chalk2.dim(" store not created yet (run graph build once)"));
2347
+ }
2348
+ console.log();
2349
+ if (!ok) process.exit(1);
2350
+ });
2351
+ graph.command("build").description("Build a dependency graph snapshot and persist it").option("--path <dir>", "Project root path (default: current directory)").action(async (opts) => {
2352
+ const cwd = resolve(opts.path ?? process.cwd());
2353
+ const spinner = ora("Building dependency graph\u2026").start();
2354
+ try {
2355
+ const { snapshot } = await phase1.services.graphEngine.buildAndStoreSnapshot({ cwd });
2356
+ spinner.succeed(`Graph snapshot saved: ${snapshot.id}`);
2357
+ console.log(chalk2.gray(` Root: ${snapshot.rootPath}`));
2358
+ console.log(chalk2.gray(` Nodes: ${snapshot.nodes.length}`));
2359
+ console.log(chalk2.gray(` Edges: ${snapshot.edges.length}
2360
+ `));
2361
+ } catch (err) {
2362
+ spinner.fail(`Graph build failed: ${err.message}`);
2363
+ process.exit(1);
2364
+ }
2365
+ });
2366
+ 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) => {
2367
+ const cwd = resolve(opts.path ?? process.cwd());
2368
+ try {
2369
+ const snapshots = await phase1.services.graphEngine.listSnapshots(cwd, parseInt(opts.limit, 10));
2370
+ if (snapshots.length === 0) {
2371
+ console.log(chalk2.yellow("\n No graph snapshots found. Run: memory-core graph build\n"));
2372
+ return;
2373
+ }
2374
+ console.log(chalk2.bold("\n Graph snapshots\n"));
2375
+ snapshots.forEach((snapshot, index) => {
2376
+ console.log(` ${index + 1}. ${snapshot.id} ${chalk2.dim(snapshot.createdAt)} nodes=${snapshot.nodes.length} edges=${snapshot.edges.length}`);
2377
+ });
2378
+ console.log();
2379
+ } catch (err) {
2380
+ console.error(chalk2.red(`Graph list failed: ${err.message}`));
2381
+ process.exit(1);
2382
+ }
2383
+ });
2384
+ 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) => {
2385
+ const cwd = resolve(opts.path ?? process.cwd());
2386
+ try {
2387
+ const snapshot = snapshotId ? await phase1.services.graphEngine.getSnapshot(cwd, snapshotId) : await phase1.services.graphEngine.latest(cwd);
2388
+ if (!snapshot) {
2389
+ console.log(chalk2.yellow("\n No matching graph snapshot found. Run: memory-core graph build\n"));
2390
+ return;
2391
+ }
2392
+ console.log(chalk2.bold(`
2393
+ Graph ${snapshot.id ?? "(latest)"}
2394
+ `));
2395
+ console.log(chalk2.gray(` Root: ${snapshot.rootPath}`));
2396
+ console.log(chalk2.gray(` Created: ${snapshot.createdAt ?? "unknown"}`));
2397
+ console.log(chalk2.gray(` Nodes: ${snapshot.nodes.length}`));
2398
+ console.log(chalk2.gray(` Edges: ${snapshot.edges.length}
2399
+ `));
2400
+ const limit = parseInt(opts.edges, 10);
2401
+ snapshot.edges.slice(0, limit).forEach((edge, index) => {
2402
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2403
+ });
2404
+ if (snapshot.edges.length > limit) {
2405
+ console.log(chalk2.dim(`
2406
+ \u2026 ${snapshot.edges.length - limit} more edges not shown
2407
+ `));
2408
+ } else {
2409
+ console.log();
2410
+ }
2411
+ } catch (err) {
2412
+ console.error(chalk2.red(`Graph show failed: ${err.message}`));
2413
+ process.exit(1);
2414
+ }
2415
+ });
2416
+ 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) => {
2417
+ const cwd = resolve(opts.path ?? process.cwd());
2418
+ try {
2419
+ const left = await phase1.services.graphEngine.getSnapshot(cwd, leftSnapshotId);
2420
+ if (!left) {
2421
+ console.error(chalk2.red(`Left snapshot not found: ${leftSnapshotId}`));
2422
+ process.exit(1);
2423
+ }
2424
+ const right = rightSnapshotId ? await phase1.services.graphEngine.getSnapshot(cwd, rightSnapshotId) : await phase1.services.graphEngine.latest(cwd);
2425
+ if (!right) {
2426
+ console.error(chalk2.red(`Right snapshot not found${rightSnapshotId ? `: ${rightSnapshotId}` : ""}`));
2427
+ process.exit(1);
2428
+ }
2429
+ const diff = phase1.services.graphEngine.diffGraphs(left, right);
2430
+ console.log(chalk2.bold("\n Graph diff\n"));
2431
+ console.log(chalk2.gray(` Left: ${left.id}`));
2432
+ console.log(chalk2.gray(` Right: ${right.id}`));
2433
+ console.log(chalk2.green(` + Nodes: ${diff.addedNodes.length}`));
2434
+ console.log(chalk2.red(` - Nodes: ${diff.removedNodes.length}`));
2435
+ console.log(chalk2.green(` + Edges: ${diff.addedEdges.length}`));
2436
+ console.log(chalk2.red(` - Edges: ${diff.removedEdges.length}`));
2437
+ if (diff.addedEdges.length > 0) {
2438
+ console.log(chalk2.green("\n Added edges"));
2439
+ diff.addedEdges.slice(0, 20).forEach((edge, index) => {
2440
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2441
+ });
2442
+ }
2443
+ if (diff.removedEdges.length > 0) {
2444
+ console.log(chalk2.red("\n Removed edges"));
2445
+ diff.removedEdges.slice(0, 20).forEach((edge, index) => {
2446
+ console.log(` ${index + 1}. ${abbreviate(edge.from)} ${chalk2.dim(`--${edge.kind}-->`)} ${abbreviate(edge.to)}`);
2447
+ });
2448
+ }
2449
+ console.log();
2450
+ } catch (err) {
2451
+ console.error(chalk2.red(`Graph diff failed: ${err.message}`));
2452
+ process.exit(1);
2453
+ }
2454
+ });
2259
2455
  var hook = program.command("hook").description("Manage the pre-commit rule enforcement hook");
2260
2456
  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
2457
  const advisory = opts.strict ? false : true;
@@ -2271,7 +2467,11 @@ program.command("check").description("Check staged changes against architecture
2271
2467
  }
2272
2468
  await checkStaged({ verbose: opts.verbose ?? false, debug: opts.debug ?? false });
2273
2469
  });
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 });
2470
+ 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) => {
2471
+ await phase1.providers.watchService.start({
2472
+ path: opts.path,
2473
+ verbose: opts.verbose,
2474
+ debug: opts.debug
2475
+ });
2276
2476
  });
2277
2477
  program.parseAsync(process.argv);