@mingxy/cerebro-claude-code 0.3.7 → 0.3.8

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 (39) hide show
  1. package/.claude-plugin/marketplace.json +0 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.mcp.json +0 -0
  4. package/README.md +0 -0
  5. package/config.json +0 -0
  6. package/hooks/apply-dream.mjs +11 -1
  7. package/hooks/common.mjs +0 -0
  8. package/hooks/common.sh +0 -0
  9. package/hooks/dream.mjs +0 -0
  10. package/hooks/flush-detached.mjs +0 -0
  11. package/hooks/hooks.json +0 -0
  12. package/hooks/post-compact.mjs +0 -0
  13. package/hooks/pre-compact.mjs +0 -0
  14. package/hooks/recall-approve.mjs +0 -0
  15. package/hooks/session-end.mjs +0 -0
  16. package/hooks/session-start.mjs +0 -0
  17. package/hooks/stop.mjs +0 -0
  18. package/hooks/user-prompt-submit.mjs +0 -0
  19. package/package.json +1 -1
  20. package/scripts/memory-profile.sh +0 -0
  21. package/scripts/memory-save.sh +0 -0
  22. package/scripts/memory-search.sh +0 -0
  23. package/scripts/web-server.mjs +0 -0
  24. package/skills/apply-dream/SKILL.md +0 -0
  25. package/skills/dream/SKILL.md +0 -0
  26. package/skills/memory-profile/SKILL.md +0 -0
  27. package/skills/memory-save/SKILL.md +0 -0
  28. package/skills/memory-search/SKILL.md +0 -0
  29. package/tests/common.test.mjs +0 -0
  30. package/tests/hooks.test.mjs +37 -0
  31. package/tests/test_smoke.sh +0 -0
  32. package/web/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  33. package/web/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  34. package/web/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  35. package/web/assets/index-DVguCuEA.css +0 -0
  36. package/web/assets/index-zOJZZn-i.js +0 -0
  37. package/web/favicon.svg +0 -0
  38. package/web/icons.svg +0 -0
  39. package/web/index.html +0 -0
File without changes
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cerebro",
3
3
  "description": "Persistent memory for Claude Code — memories survive across sessions, projects, and machines",
4
- "version": "0.3.7",
4
+ "version": "0.3.8",
5
5
  "author": {
6
6
  "name": "mingxy-cerebro",
7
7
  "email": "hi@cerebro.dev"
package/.mcp.json CHANGED
File without changes
package/README.md CHANGED
File without changes
package/config.json CHANGED
File without changes
@@ -5,6 +5,8 @@
5
5
  // unknown kept name → surfaced as `unknown`, NEVER silently dropped (that is memory evaporation)
6
6
  // dropped → removal candidate, listed for review; applied only with --apply
7
7
  // merged/updated/added → LLM version wins (content is allowed to change there)
8
+ // except: added onto an existing local file is a conflict (LLM relabeled an
9
+ // existing memory as new) — skipped and surfaced, never blindly overwrites
8
10
  // stats.total still counts kept entries — the ledger is the server's, not ours to re-derive
9
11
  //
10
12
  // Read-only review by default (prints the diff); `--apply` writes after user approval.
@@ -88,6 +90,7 @@ let entries = JSON.parse(readFileSync(archivePath, "utf8")).entries || [];
88
90
  // ─── merge ────────────────────────────────────────────────────────────────────
89
91
  const unknown = []; // kept names missing from the old archive — LLM renamed, memory at risk
90
92
  const empty = []; // content actions with empty body+description — never written, surfaced
93
+ const conflict = []; // added but a local file with that name exists — LLM rewrite mislabeled as new
91
94
  const report = { keep: 0, write: [], drop: [], dropCount: 0 };
92
95
  for (let e of entries) {
93
96
  // normalize BEFORE any branch: an LLM-emitted Chinese name that the MEMORY.md
@@ -105,6 +108,11 @@ for (let e of entries) {
105
108
  // 60-byte frontmatter shell. merged/updated skip = old content survives;
106
109
  // added skip = surfaced below, not silently dropped.
107
110
  if (!(e.body || "").trim() && !(e.description || "").trim()) { empty.push(`${e.name} (${act})`); continue; }
111
+ // added onto an existing local file = the LLM relabeled a rewrite as new
112
+ // (deepseek tic, cf. the 2026-08-20 archive where 6 existing entries came
113
+ // back as added with condensed bodies and zero new info). Its content
114
+ // would trade a hand-written file for a stub — skip, surface, human decides.
115
+ if (act === "added" && oldFiles.has(e.name)) { conflict.push(e.name); continue; }
108
116
  report.write.push(e); // LLM content is authoritative for these actions
109
117
  } else {
110
118
  unknown.push(`${e.name} (action=${act || "?"})`);
@@ -114,15 +122,17 @@ for (let e of entries) {
114
122
  // ─── review output ────────────────────────────────────────────────────────────
115
123
  const lines = [
116
124
  `apply-dream review · archive ${archivePath}`,
117
- `kept ${report.keep} / write ${report.write.length} / drop ${report.dropCount} / unknown ${unknown.length}`,
125
+ `kept ${report.keep} / write ${report.write.length} / drop ${report.dropCount} / unknown ${unknown.length} / conflict ${conflict.length}`,
118
126
  ];
119
127
  for (const e of report.write) lines.push(` ${e.source || e.action} ${e.name} — ${(e.description || "").slice(0, 60)}`);
120
128
  for (const n of report.drop) lines.push(` drop ${n}`);
121
129
  for (const n of unknown) lines.push(` ? ${n} ← surfaced, not dropped`);
122
130
  for (const n of empty) lines.push(` ~ ${n} ← empty stub skipped, not written`);
131
+ for (const n of conflict) lines.push(` ! ${n} ← added but exists locally, skipped — review manually`);
123
132
  if (orphanFiles.length) lines.push(` (unparsed old files left untouched: ${orphanFiles.length})`);
124
133
  console.log(lines.join("\n"));
125
134
  if (unknown.length) console.log("\n⚠ URGENT: unknown kept names above — surface to the user BEFORE applying; they may be renames the LLM invented.");
135
+ if (conflict.length) console.log("\n⚠ CONFLICT: added-but-exists above — old file kept, dream content discarded; merge by hand if the dream version carries new info.");
126
136
  if (!APPLY) { console.log("\ndry run — pass --apply to write"); process.exit(0); }
127
137
 
128
138
  // ─── write phase (--apply) ─────────────────────────────────────────────────────
package/hooks/common.mjs CHANGED
File without changes
package/hooks/common.sh CHANGED
File without changes
package/hooks/dream.mjs CHANGED
File without changes
File without changes
package/hooks/hooks.json CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/hooks/stop.mjs CHANGED
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mingxy/cerebro-claude-code",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Persistent memory for Claude Code — memories survive across sessions, projects, and machines",
5
5
  "author": {
6
6
  "name": "mingxy-cerebro",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -3,6 +3,8 @@ import assert from "node:assert/strict";
3
3
  import { spawn } from "node:child_process";
4
4
  import { join, dirname } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
6
8
 
7
9
  const HOOKS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "hooks");
8
10
 
@@ -109,3 +111,38 @@ describe("session-end.mjs", () => {
109
111
  assert.deepEqual(out, {});
110
112
  });
111
113
  });
114
+
115
+ describe("apply-dream.mjs", () => {
116
+ test("added onto existing local file is a conflict, not an overwrite", async () => {
117
+ const tmp = mkdtempSync(join(tmpdir(), "apply-dream-"));
118
+ try {
119
+ const memDir = join(tmp, "memory");
120
+ mkdirSync(memDir);
121
+ const existing = "---\nname: cc-x\ndescription: hand-written\nmetadata:\n type: feedback\n---\n\nprecise body\n";
122
+ writeFileSync(join(memDir, "cc-x.md"), existing);
123
+ writeFileSync(join(memDir, "MEMORY.md"), "- [cc-x](cc-x.md) — hand-written\n");
124
+ const outDir = join(tmp, "dream", "output");
125
+ mkdirSync(outDir, { recursive: true });
126
+ writeFileSync(join(outDir, "20260820.json"), JSON.stringify({ entries: [
127
+ { name: "cc-x", action: "added", body: "condensed stub rewrite" }, // mislabeled rewrite
128
+ { name: "cc-new", action: "added", body: "genuinely new" }, // real addition
129
+ ] }));
130
+
131
+ const res = await new Promise((resolve) => {
132
+ const child = spawn(process.execPath, [join(HOOKS_DIR, "apply-dream.mjs"), "--apply"], {
133
+ env: { ...process.env, OMEM_DREAM_DIR: join(tmp, "dream"), OMEM_DREAM_MEMORY_DIR: memDir },
134
+ });
135
+ let stdout = "";
136
+ child.stdout.on("data", (d) => (stdout += d));
137
+ child.on("close", (code) => resolve({ stdout, code }));
138
+ });
139
+
140
+ assert.ok(res.stdout.includes("conflict 1"), `expected one conflict, got: ${res.stdout}`);
141
+ assert.ok(res.stdout.includes("! cc-x"), "conflicting name must be listed");
142
+ assert.equal(readFileSync(join(memDir, "cc-x.md"), "utf8"), existing, "existing file must stay untouched");
143
+ assert.ok(readFileSync(join(memDir, "cc-new.md"), "utf8").includes("genuinely new"), "genuinely new entry must be written");
144
+ } finally {
145
+ rmSync(tmp, { recursive: true, force: true });
146
+ }
147
+ });
148
+ });
File without changes
File without changes
File without changes
File without changes
package/web/favicon.svg CHANGED
File without changes
package/web/icons.svg CHANGED
File without changes
package/web/index.html CHANGED
File without changes