@shahmilsaari/memory-core 0.1.2 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +41 -1
  2. package/dist/cli.js +296 -65
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -318,6 +318,46 @@ npx @shahmilsaari/memory-core hook uninstall # remove the hook
318
318
 
319
319
  ---
320
320
 
321
+ ### `watch` — Real-time rule checking
322
+
323
+ ```bash
324
+ npx @shahmilsaari/memory-core watch
325
+ ```
326
+
327
+ Runs in your terminal and checks every source file as you save it. Violations print immediately — no commit needed.
328
+
329
+ ```
330
+ archmind watch — real-time rule enforcement
331
+
332
+ watching: /your/project
333
+ model: llama3.2
334
+ rules: 24
335
+ ctrl+c to stop
336
+
337
+ [10:42:11] saved: src/controllers/user.ts
338
+
339
+ ✗ 1 violation in src/controllers/user.ts
340
+
341
+ [1] src/controllers/user.ts:34
342
+ Rule: Thin controllers — business logic belongs in services
343
+ Why: Logic in controllers cannot be reused from CLI, queues, or other
344
+ entry points — it gets siloed and duplicated across handlers
345
+ Issue: Password hashing inside the route handler
346
+ Fix: Move to UserService.hashPassword()
347
+
348
+ Fix violations or run: memory-core remember "<lesson>"
349
+ ```
350
+
351
+ Options:
352
+ ```bash
353
+ npx @shahmilsaari/memory-core watch --path src/ # watch a specific directory
354
+ npx @shahmilsaari/memory-core watch --verbose # show diff size and model details
355
+ ```
356
+
357
+ Ignores: `node_modules`, `dist`, `build`, `.git`, config files — only checks source code.
358
+
359
+ ---
360
+
321
361
  ### `check` — Manual rule check
322
362
 
323
363
  ```bash
@@ -452,7 +492,7 @@ The hook only checks source files (`.ts .tsx .js .jsx .py .php .rb .go .java .cs
452
492
  | Feature | Description |
453
493
  |---|---|
454
494
  | CI/CD check | GitHub Actions workflow — fails PRs that violate rules |
455
- | Watch mode | `memory-core watch` — checks files on save in real-time |
495
+ | Watch mode | `memory-core watch` — checks files on save in real-time |
456
496
  | Violation memory | Auto-save caught violations as "never do X, do Y" memories |
457
497
  | Rule analytics | Track which rules break most, which files are worst offenders |
458
498
  | Team sync | `memory-core push/pull` — shared memory pool across the whole team |
package/dist/cli.js CHANGED
@@ -3,12 +3,12 @@
3
3
  // src/cli.ts
4
4
  import { Command } from "commander";
5
5
  import { input, select, confirm } from "@inquirer/prompts";
6
- import chalk2 from "chalk";
6
+ import chalk3 from "chalk";
7
7
  import ora from "ora";
8
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync } from "fs";
9
- import { join as join5, dirname as dirname2 } from "path";
8
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync2, appendFileSync } from "fs";
9
+ import { join as join6, dirname as dirname2 } from "path";
10
10
  import { homedir } from "os";
11
- import { execSync as execSync2 } from "child_process";
11
+ import { execSync as execSync3 } from "child_process";
12
12
 
13
13
  // src/generator.ts
14
14
  import { readFileSync, readdirSync, writeFileSync, mkdirSync, existsSync } from "fs";
@@ -626,10 +626,10 @@ function uninstallHook() {
626
626
  console.log(chalk.green("\n \u2713 Pre-commit hook removed\n"));
627
627
  }
628
628
  async function checkStaged(options = {}) {
629
- const SOURCE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|php|rb|go|java|cs|swift|kt|rs|vue|svelte)$/;
629
+ const SOURCE_EXTENSIONS2 = /\.(ts|tsx|js|jsx|py|php|rb|go|java|cs|swift|kt|rs|vue|svelte)$/;
630
630
  let diff;
631
631
  try {
632
- const stagedFiles = execSync("git diff --cached --name-only", { encoding: "utf-8" }).split("\n").filter((f) => f && SOURCE_EXTENSIONS.test(f));
632
+ const stagedFiles = execSync("git diff --cached --name-only", { encoding: "utf-8" }).split("\n").filter((f) => f && SOURCE_EXTENSIONS2.test(f));
633
633
  if (stagedFiles.length === 0) {
634
634
  if (options.verbose) console.log(chalk.gray(" No source files staged \u2014 skipping rule check."));
635
635
  return;
@@ -782,57 +782,285 @@ function printModelMissing(model) {
782
782
  console.log(chalk.gray(" Recommended: llama3.2 | qwen2.5-coder:3b | mistral\n"));
783
783
  }
784
784
 
785
+ // src/watcher.ts
786
+ import { watch } from "chokidar";
787
+ import { execSync as execSync2 } from "child_process";
788
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
789
+ import { join as join5, relative } from "path";
790
+ import chalk2 from "chalk";
791
+ var SOURCE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|php|rb|go|java|cs|swift|kt|rs|vue|svelte)$/;
792
+ var IGNORE_PATTERNS = [
793
+ /node_modules/,
794
+ /\.git/,
795
+ /dist\//,
796
+ /build\//,
797
+ /coverage\//,
798
+ /\.memory-core/
799
+ ];
800
+ var reasonMap2 = new Map(
801
+ seeds.filter((s) => s.reason).map((s) => [s.content, s.reason])
802
+ );
803
+ function loadConfig(cwd) {
804
+ const configPath = join5(cwd, ".memory-core.json");
805
+ if (!existsSync5(configPath)) return null;
806
+ try {
807
+ return JSON.parse(readFileSync4(configPath, "utf-8"));
808
+ } catch {
809
+ return null;
810
+ }
811
+ }
812
+ function getProfileRules(config2) {
813
+ const rules = [];
814
+ const avoids = [];
815
+ if (config2.backendArchitecture) {
816
+ const profile = listProfiles("backend").find((p) => p.name === config2.backendArchitecture);
817
+ if (profile) {
818
+ rules.push(...profile.rules);
819
+ avoids.push(...profile.avoid);
820
+ }
821
+ }
822
+ if (config2.frontendFramework) {
823
+ const profile = listProfiles("frontend").find((p) => p.name === config2.frontendFramework);
824
+ if (profile) {
825
+ rules.push(...profile.rules);
826
+ avoids.push(...profile.avoid);
827
+ }
828
+ }
829
+ return { rules, avoids };
830
+ }
831
+ async function checkFile(filePath, cwd, config2, verbose) {
832
+ const rel = relative(cwd, filePath);
833
+ let diff;
834
+ try {
835
+ diff = execSync2(`git diff HEAD -- "${rel}" 2>/dev/null || git diff --no-index /dev/null "${rel}" 2>/dev/null || true`, {
836
+ encoding: "utf-8",
837
+ cwd
838
+ });
839
+ } catch {
840
+ try {
841
+ diff = execSync2(`git diff --no-index /dev/null "${rel}"`, { encoding: "utf-8", cwd });
842
+ } catch (e) {
843
+ diff = e.stdout ?? "";
844
+ }
845
+ }
846
+ if (!diff.trim()) return;
847
+ const { rules, avoids } = getProfileRules(config2);
848
+ if (rules.length === 0) return;
849
+ const ollamaUrl = process.env.OLLAMA_URL ?? "http://localhost:11434";
850
+ const chatModel = process.env.OLLAMA_CHAT_MODEL ?? "llama3.2";
851
+ const MAX_DIFF = 6e3;
852
+ const truncated = diff.length > MAX_DIFF;
853
+ const diffToSend = truncated ? diff.slice(0, MAX_DIFF) + "\n\n[diff truncated]" : diff;
854
+ if (verbose) {
855
+ console.log(chalk2.dim(`
856
+ [watch] checking ${rel} (${diff.length} chars)\u2026`));
857
+ }
858
+ const rulesWithReasons = rules.map((r, i) => {
859
+ const why = reasonMap2.get(r);
860
+ return why ? `${i + 1}. ${r}
861
+ WHY: ${why}` : `${i + 1}. ${r}`;
862
+ }).join("\n");
863
+ const systemPrompt = `You are a strict code reviewer enforcing architecture rules.
864
+ Analyze the file diff and identify ONLY clear, definite rule violations.
865
+ Use the WHY for each rule to understand intent and judge edge cases.
866
+
867
+ Rules to enforce:
868
+ ${rulesWithReasons}
869
+
870
+ Things that must never appear:
871
+ ${avoids.map((a, i) => `${i + 1}. ${a}`).join("\n")}
872
+
873
+ IMPORTANT: Respond with JSON: {"violations":[...]} or {"violations":[]}.
874
+ Each violation: {"rule":"...","file":"...","line":N,"issue":"...","suggestion":"...","reason":"..."}.
875
+ No text outside the JSON.`;
876
+ try {
877
+ const res = await fetch(`${ollamaUrl}/api/chat`, {
878
+ method: "POST",
879
+ headers: { "Content-Type": "application/json" },
880
+ body: JSON.stringify({
881
+ model: chatModel,
882
+ messages: [
883
+ { role: "system", content: systemPrompt },
884
+ { role: "user", content: `Review this diff for ${rel}:
885
+
886
+ ${diffToSend}` }
887
+ ],
888
+ stream: false,
889
+ format: "json"
890
+ })
891
+ });
892
+ if (!res.ok) {
893
+ const body = await res.text();
894
+ if (body.includes("not found") || body.includes("model")) {
895
+ console.log(chalk2.yellow(`
896
+ \u26A0 Chat model "${chatModel}" not found. Pull it: ollama pull ${chatModel}
897
+ `));
898
+ }
899
+ return;
900
+ }
901
+ const data = await res.json();
902
+ const raw = data.message.content.trim();
903
+ let violations = [];
904
+ try {
905
+ const parsed = JSON.parse(raw);
906
+ if (Array.isArray(parsed)) {
907
+ violations = parsed;
908
+ } else if (Array.isArray(parsed?.violations)) {
909
+ violations = parsed.violations;
910
+ } else if (parsed?.rule) {
911
+ violations = [parsed];
912
+ }
913
+ } catch {
914
+ violations = [];
915
+ }
916
+ if (violations.length === 0) {
917
+ console.log(chalk2.green(` \u2713 ${rel}`) + chalk2.dim(" \u2014 no violations"));
918
+ return;
919
+ }
920
+ console.log(
921
+ chalk2.red.bold(`
922
+ \u2717 ${violations.length} violation${violations.length > 1 ? "s" : ""} in ${rel}
923
+ `)
924
+ );
925
+ violations.forEach((v, i) => {
926
+ const loc = v.line ? `${v.file ?? rel}:${v.line}` : v.file ?? rel;
927
+ console.log(chalk2.bold(` [${i + 1}] ${loc}`));
928
+ console.log(chalk2.yellow(" Rule: ") + v.rule);
929
+ const why = v.reason ?? reasonMap2.get(v.rule);
930
+ if (why) console.log(chalk2.dim(" Why: ") + chalk2.dim(why));
931
+ if (v.issue) console.log(chalk2.red(" Issue: ") + v.issue);
932
+ if (v.suggestion) console.log(chalk2.green(" Fix: ") + v.suggestion);
933
+ console.log();
934
+ });
935
+ console.log(chalk2.dim(' Fix violations or run: memory-core remember "<lesson>"'));
936
+ console.log();
937
+ } catch (err) {
938
+ if (err.cause?.code === "ECONNREFUSED" || err.message?.includes("ECONNREFUSED")) {
939
+ return;
940
+ }
941
+ if (verbose) {
942
+ console.log(chalk2.yellow(` \u26A0 Check failed for ${rel}: ${err.message}`));
943
+ }
944
+ }
945
+ }
946
+ function startWatch(options = {}) {
947
+ const cwd = process.cwd();
948
+ const config2 = loadConfig(cwd);
949
+ if (!config2) {
950
+ console.error(chalk2.red("\n No .memory-core.json found. Run: memory-core init\n"));
951
+ process.exit(1);
952
+ }
953
+ const { rules } = getProfileRules(config2);
954
+ if (rules.length === 0) {
955
+ console.log(chalk2.yellow("\n No architecture rules configured in .memory-core.json \u2014 nothing to watch.\n"));
956
+ process.exit(0);
957
+ }
958
+ const watchPath = options.path ?? cwd;
959
+ const ollamaUrl = process.env.OLLAMA_URL ?? "http://localhost:11434";
960
+ const chatModel = process.env.OLLAMA_CHAT_MODEL ?? "llama3.2";
961
+ console.log(chalk2.cyan("\n archmind watch \u2014 real-time rule enforcement\n"));
962
+ console.log(chalk2.dim(` watching: ${watchPath}`));
963
+ console.log(chalk2.dim(` model: ${chatModel}`));
964
+ console.log(chalk2.dim(` rules: ${rules.length}`));
965
+ console.log(chalk2.dim(" ctrl+c to stop\n"));
966
+ const pending = /* @__PURE__ */ new Map();
967
+ let ollamaWarned = false;
968
+ const watcher = watch(watchPath, {
969
+ ignored: (filePath) => {
970
+ if (IGNORE_PATTERNS.some((p) => p.test(filePath))) return true;
971
+ const isFile = !filePath.endsWith("/");
972
+ if (isFile && !SOURCE_EXTENSIONS.test(filePath)) return true;
973
+ return false;
974
+ },
975
+ ignoreInitial: true,
976
+ persistent: true,
977
+ awaitWriteFinish: { stabilityThreshold: 150, pollInterval: 50 }
978
+ });
979
+ const handle = (filePath) => {
980
+ if (pending.has(filePath)) clearTimeout(pending.get(filePath));
981
+ const timer = setTimeout(async () => {
982
+ pending.delete(filePath);
983
+ console.log(chalk2.dim(`
984
+ [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] saved: ${relative(cwd, filePath)}`));
985
+ try {
986
+ const ping = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(2e3) });
987
+ if (!ping.ok) throw new Error("not ok");
988
+ ollamaWarned = false;
989
+ } catch {
990
+ if (!ollamaWarned) {
991
+ console.log(chalk2.yellow(` \u26A0 Ollama not running at ${ollamaUrl} \u2014 skipping check.`));
992
+ console.log(chalk2.gray(" Start it: ollama serve\n"));
993
+ ollamaWarned = true;
994
+ }
995
+ return;
996
+ }
997
+ await checkFile(filePath, cwd, config2, options.verbose ?? false);
998
+ }, 300);
999
+ pending.set(filePath, timer);
1000
+ };
1001
+ watcher.on("add", handle);
1002
+ watcher.on("change", handle);
1003
+ watcher.on("error", (err) => {
1004
+ console.error(chalk2.red(` watcher error: ${err.message}`));
1005
+ });
1006
+ process.on("SIGINT", () => {
1007
+ console.log(chalk2.dim("\n\n archmind watch stopped.\n"));
1008
+ watcher.close();
1009
+ process.exit(0);
1010
+ });
1011
+ }
1012
+
785
1013
  // src/cli.ts
786
1014
  function printBanner(projectName, agentCount) {
787
1015
  const lines = [
788
1016
  "",
789
- chalk2.cyan(" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 "),
790
- chalk2.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557"),
791
- chalk2.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551"),
792
- chalk2.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551"),
793
- chalk2.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D"),
794
- chalk2.cyan(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D"),
1017
+ chalk3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 "),
1018
+ chalk3.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557"),
1019
+ chalk3.cyan(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551"),
1020
+ chalk3.cyan(" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551"),
1021
+ chalk3.cyan(" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D"),
1022
+ chalk3.cyan(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D"),
795
1023
  "",
796
- chalk2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 ") + chalk2.bold.white("C O R E") + chalk2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1024
+ chalk3.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 ") + chalk3.bold.white("C O R E") + chalk3.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"),
797
1025
  "",
798
- chalk2.green(` \u2713 Project `) + chalk2.bold(projectName),
799
- chalk2.green(` \u2713 Agents `) + chalk2.bold(`${agentCount} AI agents configured`),
800
- chalk2.green(` \u2713 Memory `) + chalk2.bold("PostgreSQL + pgvector ready"),
1026
+ chalk3.green(` \u2713 Project `) + chalk3.bold(projectName),
1027
+ chalk3.green(` \u2713 Agents `) + chalk3.bold(`${agentCount} AI agents configured`),
1028
+ chalk3.green(` \u2713 Memory `) + chalk3.bold("PostgreSQL + pgvector ready"),
801
1029
  "",
802
- chalk2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
1030
+ chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),
803
1031
  "",
804
- chalk2.bold(" Every AI agent in this project now follows your rules."),
1032
+ chalk3.bold(" Every AI agent in this project now follows your rules."),
805
1033
  "",
806
- chalk2.gray(" Next steps:"),
807
- chalk2.gray(' memory-core remember "Your architectural decision"'),
808
- chalk2.gray(' memory-core search "query"'),
809
- chalk2.gray(" memory-core sync"),
1034
+ chalk3.gray(" Next steps:"),
1035
+ chalk3.gray(' memory-core remember "Your architectural decision"'),
1036
+ chalk3.gray(' memory-core search "query"'),
1037
+ chalk3.gray(" memory-core sync"),
810
1038
  ""
811
1039
  ];
812
1040
  lines.forEach((l) => console.log(l));
813
1041
  }
814
1042
  var CONFIG_FILE = ".memory-core.json";
815
1043
  function readProjectConfig() {
816
- const path = join5(process.cwd(), CONFIG_FILE);
817
- if (!existsSync5(path)) return null;
1044
+ const path = join6(process.cwd(), CONFIG_FILE);
1045
+ if (!existsSync6(path)) return null;
818
1046
  try {
819
- return JSON.parse(readFileSync4(path, "utf-8"));
1047
+ return JSON.parse(readFileSync5(path, "utf-8"));
820
1048
  } catch {
821
1049
  return null;
822
1050
  }
823
1051
  }
824
1052
  function writeProjectConfig(config2) {
825
- writeFileSync3(join5(process.cwd(), CONFIG_FILE), JSON.stringify(config2, null, 2));
1053
+ writeFileSync3(join6(process.cwd(), CONFIG_FILE), JSON.stringify(config2, null, 2));
826
1054
  }
827
1055
  var program = new Command();
828
1056
  program.name("memory-core").description("Universal AI memory core \u2014 generate AI context files for all coding agents").version("0.1.0");
829
1057
  program.command("init").description("Initialize memory-core in the current project").action(async () => {
830
- console.log(chalk2.bold.cyan("\n memory-core init\n"));
1058
+ console.log(chalk3.bold.cyan("\n memory-core init\n"));
831
1059
  const detected = detectProject();
832
- const envPath = join5(process.cwd(), ".memory-core.env");
833
- const hasEnv = existsSync5(envPath) || existsSync5(join5(process.cwd(), ".env")) || !!process.env.DATABASE_URL;
1060
+ const envPath = join6(process.cwd(), ".memory-core.env");
1061
+ const hasEnv = existsSync6(envPath) || existsSync6(join6(process.cwd(), ".env")) || !!process.env.DATABASE_URL;
834
1062
  if (!hasEnv) {
835
- console.log(chalk2.dim(" No .memory-core.env found \u2014 let's set up your database connection.\n"));
1063
+ console.log(chalk3.dim(" No .memory-core.env found \u2014 let's set up your database connection.\n"));
836
1064
  const dbUser = process.env.USER ?? process.env.USERNAME ?? "postgres";
837
1065
  const dbUrl = await input({
838
1066
  message: "PostgreSQL connection URL?",
@@ -853,17 +1081,17 @@ program.command("init").description("Initialize memory-core in the current proje
853
1081
  process.env.OLLAMA_URL = ollamaUrl;
854
1082
  process.env.OLLAMA_MODEL = "nomic-embed-text";
855
1083
  process.env.OLLAMA_CHAT_MODEL = "llama3.2";
856
- const gitignorePath = join5(process.cwd(), ".gitignore");
857
- if (existsSync5(gitignorePath)) {
858
- const gi = readFileSync4(gitignorePath, "utf-8");
1084
+ const gitignorePath = join6(process.cwd(), ".gitignore");
1085
+ if (existsSync6(gitignorePath)) {
1086
+ const gi = readFileSync5(gitignorePath, "utf-8");
859
1087
  if (!gi.includes(".memory-core.env")) {
860
1088
  appendFileSync(gitignorePath, "\n.memory-core.env\n");
861
1089
  }
862
1090
  } else {
863
1091
  writeFileSync3(gitignorePath, ".memory-core.env\n");
864
1092
  }
865
- console.log(chalk2.green("\n \u2713 .memory-core.env created"));
866
- console.log(chalk2.gray(" Added to .gitignore \u2014 your DB credentials stay local.\n"));
1093
+ console.log(chalk3.green("\n \u2713 .memory-core.env created"));
1094
+ console.log(chalk3.gray(" Added to .gitignore \u2014 your DB credentials stay local.\n"));
867
1095
  }
868
1096
  const projectName = await input({
869
1097
  message: "Project name?",
@@ -944,7 +1172,7 @@ program.command("init").description("Initialize memory-core in the current proje
944
1172
  if (installCaveman) {
945
1173
  const spinner2 = ora("Installing caveman token saver\u2026").start();
946
1174
  try {
947
- execSync2(
1175
+ execSync3(
948
1176
  "curl -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh | bash",
949
1177
  { stdio: "pipe", cwd: process.cwd() }
950
1178
  );
@@ -966,7 +1194,7 @@ program.command("init").description("Initialize memory-core in the current proje
966
1194
  program.command("sync").description("Re-pull memories and regenerate all AI agent files").action(async () => {
967
1195
  const config2 = readProjectConfig();
968
1196
  if (!config2) {
969
- console.error(chalk2.red("No .memory-core.json found. Run: memory-core init"));
1197
+ console.error(chalk3.red("No .memory-core.json found. Run: memory-core init"));
970
1198
  process.exit(1);
971
1199
  }
972
1200
  const spinner = ora("Syncing memories\u2026").start();
@@ -998,7 +1226,7 @@ program.command("remember <text>").description("Save a new memory to the central
998
1226
  let reason = opts.reason;
999
1227
  if (!reason) {
1000
1228
  reason = await input({
1001
- message: chalk2.dim("Why does this rule exist? (optional \u2014 helps agents debug violations)"),
1229
+ message: chalk3.dim("Why does this rule exist? (optional \u2014 helps agents debug violations)"),
1002
1230
  default: ""
1003
1231
  });
1004
1232
  }
@@ -1015,9 +1243,9 @@ program.command("remember <text>").description("Save a new memory to the central
1015
1243
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : [],
1016
1244
  embedding
1017
1245
  });
1018
- const reasonLine = reason ? chalk2.gray(`
1246
+ const reasonLine = reason ? chalk3.gray(`
1019
1247
  Why: ${reason}`) : "";
1020
- spinner.succeed(chalk2.green(`Memory saved: "${text}"`) + reasonLine);
1248
+ spinner.succeed(chalk3.green(`Memory saved: "${text}"`) + reasonLine);
1021
1249
  } catch (err) {
1022
1250
  spinner.fail(`Failed: ${err.message}`);
1023
1251
  process.exit(1);
@@ -1035,16 +1263,16 @@ program.command("search <query>").description("Search memories using semantic si
1035
1263
  );
1036
1264
  spinner.stop();
1037
1265
  if (results.length === 0) {
1038
- console.log(chalk2.yellow("No memories found."));
1266
+ console.log(chalk3.yellow("No memories found."));
1039
1267
  } else {
1040
- console.log(chalk2.bold(`
1268
+ console.log(chalk3.bold(`
1041
1269
  ${results.length} results for "${query}"
1042
1270
  `));
1043
1271
  results.forEach((m, i) => {
1044
- const sim = m.similarity ? chalk2.gray(` (${(m.similarity * 100).toFixed(0)}% match)`) : "";
1045
- console.log(chalk2.cyan(` ${i + 1}. [${m.type}] ${m.title ?? ""}`));
1046
- console.log(chalk2.white(` ${m.content}`) + sim);
1047
- if (m.tags?.length) console.log(chalk2.gray(` tags: ${m.tags.join(", ")}`));
1272
+ const sim = m.similarity ? chalk3.gray(` (${(m.similarity * 100).toFixed(0)}% match)`) : "";
1273
+ console.log(chalk3.cyan(` ${i + 1}. [${m.type}] ${m.title ?? ""}`));
1274
+ console.log(chalk3.white(` ${m.content}`) + sim);
1275
+ if (m.tags?.length) console.log(chalk3.gray(` tags: ${m.tags.join(", ")}`));
1048
1276
  console.log();
1049
1277
  });
1050
1278
  }
@@ -1057,7 +1285,7 @@ program.command("search <query>").description("Search memories using semantic si
1057
1285
  program.command("seed").description("Load all predefined memories into the database").option("--arch <architecture>", "Only seed a specific architecture (e.g. clean-architecture)").option("--force", "Re-seed even if memories already exist", false).action(async (opts) => {
1058
1286
  await runMigrations();
1059
1287
  const filtered = opts.arch ? seeds.filter((s) => s.architecture === opts.arch || s.architecture === "global") : seeds;
1060
- console.log(chalk2.bold.cyan(`
1288
+ console.log(chalk3.bold.cyan(`
1061
1289
  Seeding ${filtered.length} memories\u2026
1062
1290
  `));
1063
1291
  let saved = 0;
@@ -1076,14 +1304,14 @@ program.command("seed").description("Load all predefined memories into the datab
1076
1304
  tags: seed.tags,
1077
1305
  embedding
1078
1306
  });
1079
- spinner.succeed(chalk2.gray(`[${seed.architecture}] ${seed.title}`));
1307
+ spinner.succeed(chalk3.gray(`[${seed.architecture}] ${seed.title}`));
1080
1308
  saved++;
1081
1309
  } catch (err) {
1082
1310
  spinner.warn(`Skipped \u2014 ${err.message}`);
1083
1311
  skipped++;
1084
1312
  }
1085
1313
  }
1086
- console.log(chalk2.bold.green(`
1314
+ console.log(chalk3.bold.green(`
1087
1315
  Done. ${saved} memories seeded, ${skipped} skipped.
1088
1316
  `));
1089
1317
  await closePool();
@@ -1092,21 +1320,21 @@ program.command("global").description("Sync your memory into every AI agent glob
1092
1320
  const home = homedir();
1093
1321
  const GLOBAL_TARGETS = [
1094
1322
  // Claude Code
1095
- { label: "Claude Code", path: join5(home, ".claude/CLAUDE.md"), type: "md" },
1323
+ { label: "Claude Code", path: join6(home, ".claude/CLAUDE.md"), type: "md" },
1096
1324
  // GitHub Copilot (VS Code)
1097
- { label: "Copilot", path: join5(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-copilot" },
1325
+ { label: "Copilot", path: join6(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-copilot" },
1098
1326
  // Cursor global rules
1099
- { label: "Cursor", path: join5(home, ".cursor/rules/memory-core.mdc"), type: "md" },
1327
+ { label: "Cursor", path: join6(home, ".cursor/rules/memory-core.mdc"), type: "md" },
1100
1328
  // Cline (VS Code)
1101
- { label: "Cline", path: join5(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-cline" },
1329
+ { label: "Cline", path: join6(home, "Library/Application Support/Code/User/settings.json"), type: "vscode-cline" },
1102
1330
  // Continue.dev global config
1103
- { label: "Continue.dev", path: join5(home, ".continue/config.json"), type: "continue" },
1331
+ { label: "Continue.dev", path: join6(home, ".continue/config.json"), type: "continue" },
1104
1332
  // Aider global config
1105
- { label: "Aider", path: join5(home, ".aider.conf.yml"), type: "aider" },
1333
+ { label: "Aider", path: join6(home, ".aider.conf.yml"), type: "aider" },
1106
1334
  // Zed global settings
1107
- { label: "Zed AI", path: join5(home, ".config/zed/settings.json"), type: "zed" },
1335
+ { label: "Zed AI", path: join6(home, ".config/zed/settings.json"), type: "zed" },
1108
1336
  // Windsurf global rules
1109
- { label: "Windsurf", path: join5(home, ".windsurf/rules/memory-core.md"), type: "md" }
1337
+ { label: "Windsurf", path: join6(home, ".windsurf/rules/memory-core.md"), type: "md" }
1110
1338
  ];
1111
1339
  const spinner = ora("Fetching global memories\u2026").start();
1112
1340
  let memories = [];
@@ -1134,9 +1362,9 @@ ${rulesText}
1134
1362
  writeFileSync3(filePath, content, "utf-8");
1135
1363
  };
1136
1364
  const readJson = (filePath) => {
1137
- if (!existsSync5(filePath)) return {};
1365
+ if (!existsSync6(filePath)) return {};
1138
1366
  try {
1139
- return JSON.parse(readFileSync4(filePath, "utf-8"));
1367
+ return JSON.parse(readFileSync5(filePath, "utf-8"));
1140
1368
  } catch {
1141
1369
  return {};
1142
1370
  }
@@ -1184,14 +1412,14 @@ read:
1184
1412
  skipped.push(target.label);
1185
1413
  }
1186
1414
  }
1187
- spinner.succeed(chalk2.green(`Synced ${memories.length} memories \u2192 ${written.length} agents`));
1188
- console.log(chalk2.green("\n Updated:"));
1189
- written.forEach((l) => console.log(chalk2.gray(` \u2713 ${l}`)));
1415
+ spinner.succeed(chalk3.green(`Synced ${memories.length} memories \u2192 ${written.length} agents`));
1416
+ console.log(chalk3.green("\n Updated:"));
1417
+ written.forEach((l) => console.log(chalk3.gray(` \u2713 ${l}`)));
1190
1418
  if (skipped.length) {
1191
- console.log(chalk2.yellow("\n Skipped (not installed):"));
1192
- skipped.forEach((l) => console.log(chalk2.gray(` \u2717 ${l}`)));
1419
+ console.log(chalk3.yellow("\n Skipped (not installed):"));
1420
+ skipped.forEach((l) => console.log(chalk3.gray(` \u2717 ${l}`)));
1193
1421
  }
1194
- console.log(chalk2.bold("\n Every AI agent now follows your memory globally.\n"));
1422
+ console.log(chalk3.bold("\n Every AI agent now follows your memory globally.\n"));
1195
1423
  await closePool();
1196
1424
  });
1197
1425
  var hook = program.command("hook").description("Manage the pre-commit rule enforcement hook");
@@ -1204,4 +1432,7 @@ hook.command("uninstall").description("Remove the pre-commit hook").action(() =>
1204
1432
  program.command("check").description("Check staged changes against architecture rules (used by pre-commit hook)").option("--staged", "Check git staged diff (default behaviour)").option("--verbose", "Show model and diff details").action(async (opts) => {
1205
1433
  await checkStaged({ verbose: opts.verbose ?? false });
1206
1434
  });
1435
+ 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").action((opts) => {
1436
+ startWatch({ path: opts.path, verbose: opts.verbose });
1437
+ });
1207
1438
  program.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shahmilsaari/memory-core",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Universal AI memory core — generate AI context files from architecture profiles with RAG support",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "dependencies": {
20
20
  "@inquirer/prompts": "^5.0.0",
21
21
  "chalk": "^5.3.0",
22
+ "chokidar": "^5.0.0",
22
23
  "commander": "^12.0.0",
23
24
  "dotenv": "^16.4.0",
24
25
  "handlebars": "^4.7.8",