@0xmaxma/claude-gateway 1.7.9 → 1.8.0

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 (258) hide show
  1. package/README.md +178 -44
  2. package/dist/agent/builtin-commands.js +2 -2
  3. package/dist/agent/builtin-commands.js.map +1 -1
  4. package/dist/agent/dreaming/accept.d.ts.map +1 -1
  5. package/dist/agent/dreaming/accept.js +4 -1
  6. package/dist/agent/dreaming/accept.js.map +1 -1
  7. package/dist/agent/dreaming/config.d.ts.map +1 -1
  8. package/dist/agent/dreaming/config.js +2 -0
  9. package/dist/agent/dreaming/config.js.map +1 -1
  10. package/dist/agent/dreaming/reviewer.d.ts.map +1 -1
  11. package/dist/agent/dreaming/reviewer.js +9 -2
  12. package/dist/agent/dreaming/reviewer.js.map +1 -1
  13. package/dist/agent/dreaming/staleness.d.ts +7 -1
  14. package/dist/agent/dreaming/staleness.d.ts.map +1 -1
  15. package/dist/agent/dreaming/staleness.js +17 -2
  16. package/dist/agent/dreaming/staleness.js.map +1 -1
  17. package/dist/agent/dreaming/types.d.ts +17 -1
  18. package/dist/agent/dreaming/types.d.ts.map +1 -1
  19. package/dist/agent/knowledge/archive-db.d.ts +43 -0
  20. package/dist/agent/knowledge/archive-db.d.ts.map +1 -1
  21. package/dist/agent/knowledge/archive-db.js +130 -0
  22. package/dist/agent/knowledge/archive-db.js.map +1 -1
  23. package/dist/agent/knowledge/config.d.ts +3 -1
  24. package/dist/agent/knowledge/config.d.ts.map +1 -1
  25. package/dist/agent/knowledge/config.js +32 -10
  26. package/dist/agent/knowledge/config.js.map +1 -1
  27. package/dist/agent/knowledge/index.d.ts +11 -4
  28. package/dist/agent/knowledge/index.d.ts.map +1 -1
  29. package/dist/agent/knowledge/index.js +22 -1
  30. package/dist/agent/knowledge/index.js.map +1 -1
  31. package/dist/agent/knowledge/indexer.d.ts +0 -7
  32. package/dist/agent/knowledge/indexer.d.ts.map +1 -1
  33. package/dist/agent/knowledge/indexer.js +34 -7
  34. package/dist/agent/knowledge/indexer.js.map +1 -1
  35. package/dist/agent/knowledge/lifecycle.d.ts +20 -4
  36. package/dist/agent/knowledge/lifecycle.d.ts.map +1 -1
  37. package/dist/agent/knowledge/lifecycle.js +33 -4
  38. package/dist/agent/knowledge/lifecycle.js.map +1 -1
  39. package/dist/agent/knowledge/reflection.d.ts +114 -0
  40. package/dist/agent/knowledge/reflection.d.ts.map +1 -0
  41. package/dist/agent/knowledge/reflection.js +447 -0
  42. package/dist/agent/knowledge/reflection.js.map +1 -0
  43. package/dist/agent/knowledge/shared-dedup.d.ts +85 -0
  44. package/dist/agent/knowledge/shared-dedup.d.ts.map +1 -0
  45. package/dist/agent/knowledge/shared-dedup.js +187 -0
  46. package/dist/agent/knowledge/shared-dedup.js.map +1 -0
  47. package/dist/agent/knowledge/shared-promote.d.ts +82 -7
  48. package/dist/agent/knowledge/shared-promote.d.ts.map +1 -1
  49. package/dist/agent/knowledge/shared-promote.js +244 -11
  50. package/dist/agent/knowledge/shared-promote.js.map +1 -1
  51. package/dist/agent/knowledge/shared-staleness.d.ts +51 -0
  52. package/dist/agent/knowledge/shared-staleness.d.ts.map +1 -0
  53. package/dist/agent/knowledge/shared-staleness.js +149 -0
  54. package/dist/agent/knowledge/shared-staleness.js.map +1 -0
  55. package/dist/agent/knowledge/shared-writer.d.ts +19 -0
  56. package/dist/agent/knowledge/shared-writer.d.ts.map +1 -1
  57. package/dist/agent/knowledge/shared-writer.js +42 -0
  58. package/dist/agent/knowledge/shared-writer.js.map +1 -1
  59. package/dist/agent/knowledge/types.d.ts +24 -0
  60. package/dist/agent/knowledge/types.d.ts.map +1 -1
  61. package/dist/agent/model-catalog.d.ts +62 -0
  62. package/dist/agent/model-catalog.d.ts.map +1 -0
  63. package/dist/agent/model-catalog.js +264 -0
  64. package/dist/agent/model-catalog.js.map +1 -0
  65. package/dist/agent/runner.d.ts +83 -0
  66. package/dist/agent/runner.d.ts.map +1 -1
  67. package/dist/agent/runner.js +281 -51
  68. package/dist/agent/runner.js.map +1 -1
  69. package/dist/agent/workspace-loader.d.ts +17 -2
  70. package/dist/agent/workspace-loader.d.ts.map +1 -1
  71. package/dist/agent/workspace-loader.js +17 -1
  72. package/dist/agent/workspace-loader.js.map +1 -1
  73. package/dist/api/cron-router.d.ts +12 -8
  74. package/dist/api/cron-router.d.ts.map +1 -1
  75. package/dist/api/cron-router.js +82 -16
  76. package/dist/api/cron-router.js.map +1 -1
  77. package/dist/api/gateway-router.d.ts +4 -0
  78. package/dist/api/gateway-router.d.ts.map +1 -1
  79. package/dist/api/gateway-router.js +16 -1
  80. package/dist/api/gateway-router.js.map +1 -1
  81. package/dist/api/manifest-sources.d.ts +15 -0
  82. package/dist/api/manifest-sources.d.ts.map +1 -0
  83. package/dist/api/manifest-sources.js +27 -0
  84. package/dist/api/manifest-sources.js.map +1 -0
  85. package/dist/api/meta-router.d.ts +13 -0
  86. package/dist/api/meta-router.d.ts.map +1 -0
  87. package/dist/api/meta-router.js +26 -0
  88. package/dist/api/meta-router.js.map +1 -0
  89. package/dist/api/packages.d.ts.map +1 -1
  90. package/dist/api/packages.js +18 -198
  91. package/dist/api/packages.js.map +1 -1
  92. package/dist/api/route-registry.d.ts +81 -0
  93. package/dist/api/route-registry.d.ts.map +1 -0
  94. package/dist/api/route-registry.js +44 -0
  95. package/dist/api/route-registry.js.map +1 -0
  96. package/dist/api/router.d.ts.map +1 -1
  97. package/dist/api/router.js +21 -3
  98. package/dist/api/router.js.map +1 -1
  99. package/dist/apps/compose-generator.js +7 -4
  100. package/dist/apps/compose-generator.js.map +1 -1
  101. package/dist/apps/installer.d.ts +154 -0
  102. package/dist/apps/installer.d.ts.map +1 -1
  103. package/dist/apps/installer.js +657 -54
  104. package/dist/apps/installer.js.map +1 -1
  105. package/dist/cli/args.d.ts +25 -0
  106. package/dist/cli/args.d.ts.map +1 -0
  107. package/dist/cli/args.js +55 -0
  108. package/dist/cli/args.js.map +1 -0
  109. package/dist/cli/colors.d.ts +37 -0
  110. package/dist/cli/colors.d.ts.map +1 -0
  111. package/dist/cli/colors.js +63 -0
  112. package/dist/cli/colors.js.map +1 -0
  113. package/dist/cli/command-names.d.ts +98 -0
  114. package/dist/cli/command-names.d.ts.map +1 -0
  115. package/dist/cli/command-names.js +109 -0
  116. package/dist/cli/command-names.js.map +1 -0
  117. package/dist/cli/commands/agents.d.ts +28 -0
  118. package/dist/cli/commands/agents.d.ts.map +1 -0
  119. package/dist/cli/commands/agents.js +321 -0
  120. package/dist/cli/commands/agents.js.map +1 -0
  121. package/dist/cli/commands/channels.d.ts +17 -0
  122. package/dist/cli/commands/channels.d.ts.map +1 -0
  123. package/dist/cli/commands/channels.js +101 -0
  124. package/dist/cli/commands/channels.js.map +1 -0
  125. package/dist/cli/commands/debug-bundle.d.ts +6 -0
  126. package/dist/cli/commands/debug-bundle.d.ts.map +1 -0
  127. package/dist/cli/commands/debug-bundle.js +220 -0
  128. package/dist/cli/commands/debug-bundle.js.map +1 -0
  129. package/dist/cli/commands/doctor.d.ts +3 -0
  130. package/dist/cli/commands/doctor.d.ts.map +1 -0
  131. package/dist/cli/commands/doctor.js +104 -0
  132. package/dist/cli/commands/doctor.js.map +1 -0
  133. package/dist/cli/commands/gateway.d.ts +12 -0
  134. package/dist/cli/commands/gateway.d.ts.map +1 -0
  135. package/dist/cli/commands/gateway.js +145 -0
  136. package/dist/cli/commands/gateway.js.map +1 -0
  137. package/dist/cli/commands/service.d.ts +33 -0
  138. package/dist/cli/commands/service.d.ts.map +1 -0
  139. package/dist/cli/commands/service.js +513 -0
  140. package/dist/cli/commands/service.js.map +1 -0
  141. package/dist/cli/commands/update.d.ts +16 -0
  142. package/dist/cli/commands/update.d.ts.map +1 -0
  143. package/dist/cli/commands/update.js +149 -0
  144. package/dist/cli/commands/update.js.map +1 -0
  145. package/dist/cli/commands.generated.d.ts +4 -0
  146. package/dist/cli/commands.generated.d.ts.map +1 -0
  147. package/dist/cli/commands.generated.js +220 -0
  148. package/dist/cli/commands.generated.js.map +1 -0
  149. package/dist/cli/health.d.ts +31 -0
  150. package/dist/cli/health.d.ts.map +1 -0
  151. package/dist/cli/health.js +49 -0
  152. package/dist/cli/health.js.map +1 -0
  153. package/dist/cli/http-client.d.ts +140 -0
  154. package/dist/cli/http-client.d.ts.map +1 -0
  155. package/dist/cli/http-client.js +332 -0
  156. package/dist/cli/http-client.js.map +1 -0
  157. package/dist/cli/index.d.ts +27 -0
  158. package/dist/cli/index.d.ts.map +1 -0
  159. package/dist/cli/index.js +372 -0
  160. package/dist/cli/index.js.map +1 -0
  161. package/dist/cli/manager.d.ts +64 -0
  162. package/dist/cli/manager.d.ts.map +1 -0
  163. package/dist/cli/manager.js +181 -0
  164. package/dist/cli/manager.js.map +1 -0
  165. package/dist/cli/output.d.ts +49 -0
  166. package/dist/cli/output.d.ts.map +1 -0
  167. package/dist/cli/output.js +101 -0
  168. package/dist/cli/output.js.map +1 -0
  169. package/dist/cli/prompt.d.ts +29 -0
  170. package/dist/cli/prompt.d.ts.map +1 -0
  171. package/dist/cli/prompt.js +154 -0
  172. package/dist/cli/prompt.js.map +1 -0
  173. package/dist/cli/redact.d.ts +3 -0
  174. package/dist/cli/redact.d.ts.map +1 -0
  175. package/dist/cli/redact.js +33 -0
  176. package/dist/cli/redact.js.map +1 -0
  177. package/dist/cli/types.d.ts +22 -0
  178. package/dist/cli/types.d.ts.map +1 -0
  179. package/dist/cli/types.js +4 -0
  180. package/dist/cli/types.js.map +1 -0
  181. package/dist/config/bootstrap.d.ts +33 -0
  182. package/dist/config/bootstrap.d.ts.map +1 -0
  183. package/dist/config/bootstrap.js +117 -0
  184. package/dist/config/bootstrap.js.map +1 -0
  185. package/dist/config/loader.d.ts.map +1 -1
  186. package/dist/config/loader.js +5 -1
  187. package/dist/config/loader.js.map +1 -1
  188. package/dist/config/watcher.d.ts.map +1 -1
  189. package/dist/config/watcher.js +2 -35
  190. package/dist/config/watcher.js.map +1 -1
  191. package/dist/discord/receiver.d.ts +6 -1
  192. package/dist/discord/receiver.d.ts.map +1 -1
  193. package/dist/discord/receiver.js +9 -1
  194. package/dist/discord/receiver.js.map +1 -1
  195. package/dist/entry.d.ts +3 -0
  196. package/dist/entry.d.ts.map +1 -0
  197. package/dist/entry.js +83 -0
  198. package/dist/entry.js.map +1 -0
  199. package/dist/index.js +197 -47
  200. package/dist/index.js.map +1 -1
  201. package/dist/load-dotenv.d.ts +2 -0
  202. package/dist/load-dotenv.d.ts.map +1 -0
  203. package/dist/load-dotenv.js +68 -0
  204. package/dist/load-dotenv.js.map +1 -0
  205. package/dist/packages/registry.d.ts +50 -0
  206. package/dist/packages/registry.d.ts.map +1 -0
  207. package/dist/packages/registry.js +209 -0
  208. package/dist/packages/registry.js.map +1 -0
  209. package/dist/session/process.d.ts +20 -3
  210. package/dist/session/process.d.ts.map +1 -1
  211. package/dist/session/process.js +120 -5
  212. package/dist/session/process.js.map +1 -1
  213. package/dist/shell/claude-pty-shell.js +146 -27
  214. package/dist/shell/claude-pty-shell.js.map +1 -1
  215. package/dist/shell/draft-phantom.d.ts +49 -0
  216. package/dist/shell/draft-phantom.d.ts.map +1 -0
  217. package/dist/shell/draft-phantom.js +59 -0
  218. package/dist/shell/draft-phantom.js.map +1 -0
  219. package/dist/shell/submit-diag.d.ts +67 -0
  220. package/dist/shell/submit-diag.d.ts.map +1 -0
  221. package/dist/shell/submit-diag.js +69 -0
  222. package/dist/shell/submit-diag.js.map +1 -0
  223. package/dist/shutdown-signals.d.ts +47 -0
  224. package/dist/shutdown-signals.d.ts.map +1 -0
  225. package/dist/shutdown-signals.js +62 -0
  226. package/dist/shutdown-signals.js.map +1 -0
  227. package/dist/telegram/receiver.d.ts +6 -1
  228. package/dist/telegram/receiver.d.ts.map +1 -1
  229. package/dist/telegram/receiver.js +9 -1
  230. package/dist/telegram/receiver.js.map +1 -1
  231. package/dist/types.d.ts +25 -0
  232. package/dist/types.d.ts.map +1 -1
  233. package/dist/utils/orphan-receivers.d.ts +90 -0
  234. package/dist/utils/orphan-receivers.d.ts.map +1 -0
  235. package/dist/utils/orphan-receivers.js +218 -0
  236. package/dist/utils/orphan-receivers.js.map +1 -0
  237. package/dist/utils/paths.d.ts +16 -0
  238. package/dist/utils/paths.d.ts.map +1 -0
  239. package/dist/utils/paths.js +60 -0
  240. package/dist/utils/paths.js.map +1 -0
  241. package/dist/utils/stop-child.d.ts +21 -0
  242. package/dist/utils/stop-child.d.ts.map +1 -0
  243. package/dist/utils/stop-child.js +64 -0
  244. package/dist/utils/stop-child.js.map +1 -0
  245. package/lib/pairing.ts +1 -1
  246. package/mcp/tools/agent/handlers.ts +1 -1
  247. package/mcp/tools/discord/module.ts +23 -15
  248. package/mcp/tools/discord/receiver-server.ts +4 -0
  249. package/mcp/tools/memory/archive-reader.test.ts +25 -1
  250. package/mcp/tools/memory/archive-reader.ts +70 -12
  251. package/mcp/tools/memory/archive-writer.test.ts +168 -0
  252. package/mcp/tools/memory/archive-writer.ts +137 -0
  253. package/mcp/tools/memory/module.test.ts +187 -0
  254. package/mcp/tools/memory/module.ts +192 -6
  255. package/mcp/tools/telegram/module.ts +10 -2
  256. package/mcp/tools/telegram/receiver-server.ts +15 -6
  257. package/mcp/tools/telegram/typing.ts +178 -34
  258. package/package.json +6 -4
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ /**
3
+ * Node-native near-duplicate search over the shared vault's `kb.sqlite`
4
+ * (planning-64 K3 follow-up / issue #386).
5
+ *
6
+ * The shared vault is indexed with the SAME `ArchiveDB` schema the per-agent
7
+ * archive uses (`indexSharedArchive` in `indexer.ts` calls
8
+ * `ArchiveDB.forPath(sharedDbPath(cfg), ...)`), so `ArchiveDB.searchSimilar`
9
+ * works against it unmodified. This exists because the MCP tools'
10
+ * `findSimilarSharedNotes` (`mcp/tools/memory/archive-reader.ts`) runs under
11
+ * Bun (`bun:sqlite`) and cannot be imported from here — the Node gateway
12
+ * process (`node:sqlite`). Same on-disk file, two bindings; keep both in sync.
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.MIN_RELATED_CONTAINMENT = exports.MIN_MERGE_CONTAINMENT = exports.SEED_TERM_BUDGET = void 0;
49
+ exports.findSimilarSharedNotes = findSimilarSharedNotes;
50
+ exports.significantTokens = significantTokens;
51
+ exports.containmentScore = containmentScore;
52
+ exports.filterNearDuplicates = filterNearDuplicates;
53
+ exports.buildSeedText = buildSeedText;
54
+ const fs = __importStar(require("fs"));
55
+ const path = __importStar(require("path"));
56
+ const archive_db_1 = require("./archive-db");
57
+ const config_1 = require("./config");
58
+ /**
59
+ * FTS term budget for a near-dup lookup. `searchSimilar` truncates the seed to
60
+ * this many terms, so the budget has to be split deliberately between the note
61
+ * name and its content (`buildSeedText`) — a long freeform name used to consume
62
+ * all 12 terms and the promoted fact itself never reached the query (issue #398).
63
+ */
64
+ exports.SEED_TERM_BUDGET = 16;
65
+ /**
66
+ * Find shared notes whose content overlaps `seedText`. Read-only; returns []
67
+ * when the shared index doesn't exist yet or the seed has no usable tokens.
68
+ */
69
+ function findSimilarSharedNotes(cfg, seedText, maxResults = 3, maxTerms = exports.SEED_TERM_BUDGET) {
70
+ const dbPath = (0, config_1.sharedDbPath)(cfg);
71
+ if (!fs.existsSync(dbPath))
72
+ return [];
73
+ const db = archive_db_1.ArchiveDB.forPath(dbPath);
74
+ const hits = db.searchSimilar(seedText, maxResults, maxTerms);
75
+ return hits.map((h) => ({
76
+ name: path.basename(h.path, '.md'),
77
+ snippet: h.text.length > 500 ? `${h.text.slice(0, 500)}…` : h.text,
78
+ }));
79
+ }
80
+ // ── Near-duplicate gate (issue #398) ────────────────────────────────────────
81
+ //
82
+ // FTS above is RECALL only: `searchSimilar` ORs the seed's tokens, so a single
83
+ // shared word is enough to return a hit. Auto-promotion used to treat any hit
84
+ // as a near-duplicate and merge into it unattended, which fused unrelated notes
85
+ // together (a live vault had 39 of 48 chunks matching a typical seed). Recall
86
+ // stays loose — a real near-dup rarely repeats every word — and the PRECISION
87
+ // bar below decides what may actually be merged.
88
+ //
89
+ // The bar is deterministic token containment rather than a BM25 cutoff: BM25 is
90
+ // corpus-relative, so a threshold tuned on one vault silently changes meaning as
91
+ // the vault grows. Containment ("how much of the seed does this note already
92
+ // say?") is stable, explainable, and testable offline.
93
+ /** Tokens carried by markdown/prose scaffolding rather than by the fact itself. */
94
+ const STOPWORDS = new Set([
95
+ 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'because', 'been', 'but', 'by', 'for', 'from', 'had', 'has', 'have', 'he', 'her', 'his',
96
+ 'how', 'i', 'if', 'in', 'into', 'is', 'it', 'its', 'me', 'my', 'need', 'needs', 'new', 'not', 'of', 'on', 'or', 'our', 'she', 'so',
97
+ 'that', 'the', 'their', 'them', 'then', 'there', 'these', 'they', 'this', 'to', 'up', 'use', 'used', 'user', 'was', 'we',
98
+ 'were', 'what', 'when', 'where', 'which', 'while', 'who', 'why', 'will', 'with', 'would', 'you', 'your',
99
+ // markdown / memory-file scaffolding: present in almost every promoted line
100
+ 'md', 'memory', 'index', 'entry', 'note', 'notes', 'pointer', 'pointers', 'add', 'added', 'insert', 'section', 'file', 'files',
101
+ ]);
102
+ /** Normalized, stopword-free, deduped content tokens of a text. */
103
+ function significantTokens(text) {
104
+ const out = new Set();
105
+ for (const raw of text.toLowerCase().split(/[^\p{L}\p{N}_]+/u)) {
106
+ if (raw.length < 3)
107
+ continue; // 1-2 char fragments carry no topic signal
108
+ if (STOPWORDS.has(raw))
109
+ continue;
110
+ out.add(raw);
111
+ }
112
+ return out;
113
+ }
114
+ /**
115
+ * How much of `seed` the `candidate` already covers, in [0, 1]. Containment
116
+ * (not Jaccard) on purpose: a short new fact merging into a long established
117
+ * note is a legitimate near-duplicate, and Jaccard would punish it for the
118
+ * candidate's length alone.
119
+ *
120
+ * Note the asymmetry in the inputs: `seed` is the truncated query text
121
+ * (`buildSeedText`), while `candidate` should be a full note body. The score is
122
+ * therefore "how much of the QUERY this note already says" — the candidate is
123
+ * never penalised for saying more.
124
+ */
125
+ function containmentScore(seed, candidate) {
126
+ const a = significantTokens(seed);
127
+ if (a.size === 0)
128
+ return 0;
129
+ const b = significantTokens(candidate);
130
+ let hit = 0;
131
+ for (const t of a)
132
+ if (b.has(t))
133
+ hit++;
134
+ return hit / a.size;
135
+ }
136
+ /**
137
+ * Minimum containment for an UNATTENDED merge. Deliberately strict: creating a
138
+ * separate note when two really were duplicates is a tidy-up cost, while merging
139
+ * two unrelated facts destroys both (the reader can no longer tell which note
140
+ * the fact belongs to). Errors are pushed to the recoverable side.
141
+ *
142
+ * Read this against what the seed actually IS. Callers score against
143
+ * `buildSeedText`'s output, which is capped at `SEED_TERM_BUDGET` significant
144
+ * tokens (a minority from the name, the rest from the fact) — so the bar is "half
145
+ * of the fact's first ~11 topic words are already in this note", not "half of the
146
+ * whole fact". That is looser than the bare number reads, and deliberately so:
147
+ * the cap is what keeps the FTS query itself affordable, and the same truncation
148
+ * has to apply on both sides for the score to mean anything.
149
+ */
150
+ exports.MIN_MERGE_CONTAINMENT = 0.5;
151
+ /**
152
+ * Minimum containment for emitting a `[[wikilink]]` to a note (issue #386 part
153
+ * B's graph edges). Lower than the merge bar on purpose — the two operations
154
+ * make different claims. A merge asserts "these are the same fact" and destroys
155
+ * the distinction between them; a link only asserts "these are related" and is
156
+ * additive, so the same evidence buys a link long before it buys a merge.
157
+ */
158
+ exports.MIN_RELATED_CONTAINMENT = 0.25;
159
+ /**
160
+ * Candidates that clear `minContainment` against the seed, best first.
161
+ *
162
+ * `resolveText` decides WHAT the candidate is scored against. It defaults to the
163
+ * FTS snippet, but callers that can read the note should pass its full body: a
164
+ * snippet is one 500-char chunk, so an established note that a recurring topic
165
+ * has merged into for months spreads the seed's tokens across several chunks and
166
+ * scores far below its true containment — turning the bar into "much stricter
167
+ * than 0.5" for exactly the notes that most need to match (issue #398 review).
168
+ */
169
+ function filterNearDuplicates(seedText, candidates, minContainment = exports.MIN_MERGE_CONTAINMENT, resolveText = (c) => c.snippet) {
170
+ return candidates
171
+ .map((c) => ({ c, score: containmentScore(seedText, resolveText(c)) }))
172
+ .filter((x) => x.score >= minContainment)
173
+ .sort((x, y) => y.score - x.score)
174
+ .map((x) => x.c);
175
+ }
176
+ /**
177
+ * Balanced FTS seed for a promotion lookup: the name gets a minority share of
178
+ * `SEED_TERM_BUDGET` and the promoted fact gets the rest, so a long freeform
179
+ * `reason` can no longer starve the content out of the query (issue #398).
180
+ */
181
+ function buildSeedText(name, content, budget = exports.SEED_TERM_BUDGET) {
182
+ const nameBudget = Math.max(1, Math.floor(budget / 3));
183
+ const nameTokens = [...significantTokens(name)].slice(0, nameBudget);
184
+ const contentTokens = [...significantTokens(content)].filter((t) => !nameTokens.includes(t));
185
+ return [...nameTokens, ...contentTokens].slice(0, budget).join(' ');
186
+ }
187
+ //# sourceMappingURL=shared-dedup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-dedup.js","sourceRoot":"","sources":["../../../src/agent/knowledge/shared-dedup.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,93 @@
1
+ import { writeSharedNote } from './shared-writer';
1
2
  import type { KnowledgeSharedConfig } from './types';
3
+ /**
4
+ * Merge a newly-promoted fact into a note's existing content, rather than
5
+ * blindly overwriting it. Deterministic append (no extra LLM call — dreaming
6
+ * already spent its budget proposing the fact); a no-op if the addition is
7
+ * already present verbatim. `relatedNames` (issue #386 part B) adds
8
+ * `[[wikilink]]`s to OTHER near-duplicate notes found alongside the merge
9
+ * target, so the existing (previously unpopulated) `/knowledge/graph`
10
+ * dashboard gets real edges — `wiki.ts` already resolves `[[links]]` into
11
+ * graph edges, no parser change needed.
12
+ */
13
+ export declare function mergeIntoNote(existing: string, addition: string, relatedNames?: string[]): string;
14
+ /**
15
+ * Write only if the result stays within the same size cap the manual
16
+ * `memory_shared_create`/`_update` tools enforce. Unlike those tools, dreaming
17
+ * has no one to show a "content too large" error to — a note a recurring topic
18
+ * keeps merging into over months has no natural stopping point otherwise, so
19
+ * once a merge would cross the cap the note is left as-is (best-effort: a
20
+ * skipped promotion never fails the local dream, same as any other error here).
21
+ */
22
+ export declare function writeCapped(cfg: Parameters<typeof writeSharedNote>[0], name: string, content: string, relatedNames?: string[]): boolean;
23
+ /**
24
+ * True when the content is nothing but `MEMORY.md` index pointers.
25
+ *
26
+ * The memory-tiering convention writes a fact to its own file AND a one-line
27
+ * pointer into the `MEMORY.md` index. Both land as durable `add`s, so both used
28
+ * to be promoted — and the pointer's links resolve only inside the promoting
29
+ * agent's own workspace, making it dead weight in a shared vault (issue #398:
30
+ * 30 of 40 live notes were pointer-only, with all 32 link targets dangling).
31
+ *
32
+ * The trailing hook text is deliberately NOT rescued into a note of its own: an
33
+ * index hook is written to be read next to the file it points at, so promoting
34
+ * it alone would put a decontextualized fragment in front of every other agent.
35
+ * A dream that proposes the fact itself as prose still promotes normally — only
36
+ * navigation scaffolding is dropped.
37
+ */
38
+ export declare function isIndexPointerOnly(content: string): boolean;
39
+ /**
40
+ * True when a note name reads as an instruction for editing a file rather than
41
+ * as the name of a fact ("insert after cron config, before the pty_shell
42
+ * section"). `reason` is prompted as free-form justification (`reviewer.ts`) but
43
+ * issue #386 repurposed it as the shared note's identity, so such strings became
44
+ * note names that no future occurrence of the same fact can ever match.
45
+ *
46
+ * Two signals are required — an imperative edit verb AND a positional/file
47
+ * anchor — because either alone is common in legitimate names ("Add-on billing
48
+ * is per seat", "the section header is user-visible").
49
+ */
50
+ export declare function isInstructionShapedName(name: string): boolean;
51
+ /**
52
+ * A note name derived from the fact itself — the fallback when the proposal has
53
+ * no `topic` and its `reason` reads as an editing instruction.
54
+ *
55
+ * Dropping such a promotion outright (the first cut of issue #398) silently lost
56
+ * genuine facts whose reason merely happened to start with an edit verb, e.g.
57
+ * "Update the deploy section after the API change". Naming from the content
58
+ * keeps the fact; only content with nothing nameable in it is given up on.
59
+ */
60
+ export declare function deriveNameFromContent(content: string): string | undefined;
2
61
  /**
3
62
  * Build the per-agent→shared promotion function used after the dreaming applier
4
63
  * writes an `add` to local memory (K3↔K4). Returns `undefined` when the shared
5
64
  * KB is disabled or in `propose` (dry-run) mode — callers then skip promotion.
6
65
  *
7
- * A durable add is written to the shared vault under a **content-hashed**
8
- * filename so two proposals whose reasons slug to the same name (or a recurring
9
- * nightly reason) can't silently overwrite each other, and identical content
10
- * maps to the same file (idempotent). Extracted here so both the nightly
11
- * auto-applier (src/index.ts) and the manual dashboard accept endpoint share one
12
- * implementation.
66
+ * Note identity is the proposal's `reason` (issue #386) the SAME freeform
67
+ * namespace `memory_shared_create`/`_update` use, not a content hash. A
68
+ * content-hashed name (the pre-#386 scheme) meant the same recurring fact,
69
+ * reworded slightly by the reviewer each night, hashed differently and was
70
+ * written as a brand-new note every time this is what created the
71
+ * near-duplicate pileup #386 reports. Naming by `reason` instead means the
72
+ * same recurring fact maps to the same note name across nights, landing as an
73
+ * update. A `reason` that doesn't collide by name is also checked against the
74
+ * shared vault's near-dup search before creating, and merged into the closest
75
+ * match instead of creating a disconnected duplicate when one is found.
76
+ *
77
+ * Concurrency note: the name-collision check, read, and write below are not one
78
+ * atomic operation, so two agents dreaming at the same moment and promoting
79
+ * under the exact same `reason` can race — `shared-writer.ts` already documents
80
+ * that same-file races resolve to last-write-wins, never corruption, and other
81
+ * read-modify-write callers (e.g. `memory_shared_update`) have this same
82
+ * property. Naming by `reason` instead of a content hash makes same-name
83
+ * collisions between different agents more likely than the pre-#386 scheme, but
84
+ * it's the same accepted tradeoff, not a new class of risk.
13
85
  */
14
- export declare function makeSharedPromoter(agentId: string, agentCfg: KnowledgeSharedConfig | undefined, globalCfg: KnowledgeSharedConfig | undefined): ((p: {
86
+ export declare function makeSharedPromoter(agentId: string, agentCfg: KnowledgeSharedConfig | undefined, globalCfg: KnowledgeSharedConfig | undefined, logger?: {
87
+ info: (msg: string, data?: Record<string, unknown>) => void;
88
+ }): ((p: {
15
89
  reason: string;
16
90
  content?: string;
91
+ topic?: string;
17
92
  }) => void) | undefined;
18
93
  //# sourceMappingURL=shared-promote.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-promote.d.ts","sourceRoot":"","sources":["../../../src/agent/knowledge/shared-promote.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,qBAAqB,GAAG,SAAS,EAC3C,SAAS,EAAE,qBAAqB,GAAG,SAAS,GAC3C,CAAC,CAAC,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC,GAAG,SAAS,CAQjE"}
1
+ {"version":3,"file":"shared-promote.d.ts","sourceRoot":"","sources":["../../../src/agent/knowledge/shared-promote.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EAKhB,MAAM,iBAAiB,CAAC;AAUzB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,GAAE,MAAM,EAAO,GAAG,MAAM,CAarG;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,EAC1C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,YAAY,GAAE,MAAM,EAAO,GAC1B,OAAO,CAKT;AAOD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAO3D;AAQD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUzE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,kBAAkB,CAMhC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,qBAAqB,GAAG,SAAS,EAC3C,SAAS,EAAE,qBAAqB,GAAG,SAAS,EAI5C,MAAM,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;CAAE,GACvE,CAAC,CAAC,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,CAAC,GAAG,SAAS,CAoHjF"}
@@ -1,29 +1,262 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeIntoNote = mergeIntoNote;
4
+ exports.writeCapped = writeCapped;
5
+ exports.isIndexPointerOnly = isIndexPointerOnly;
6
+ exports.isInstructionShapedName = isInstructionShapedName;
7
+ exports.deriveNameFromContent = deriveNameFromContent;
3
8
  exports.makeSharedPromoter = makeSharedPromoter;
4
- const crypto_1 = require("crypto");
5
9
  const shared_writer_1 = require("./shared-writer");
10
+ const shared_dedup_1 = require("./shared-dedup");
11
+ const shared_staleness_1 = require("./shared-staleness");
6
12
  const config_1 = require("./config");
13
+ /**
14
+ * Merge a newly-promoted fact into a note's existing content, rather than
15
+ * blindly overwriting it. Deterministic append (no extra LLM call — dreaming
16
+ * already spent its budget proposing the fact); a no-op if the addition is
17
+ * already present verbatim. `relatedNames` (issue #386 part B) adds
18
+ * `[[wikilink]]`s to OTHER near-duplicate notes found alongside the merge
19
+ * target, so the existing (previously unpopulated) `/knowledge/graph`
20
+ * dashboard gets real edges — `wiki.ts` already resolves `[[links]]` into
21
+ * graph edges, no parser change needed.
22
+ */
23
+ function mergeIntoNote(existing, addition, relatedNames = []) {
24
+ const trimmedAddition = addition.trim();
25
+ // Only link what is not linked yet: a recurring fact that merges into the same
26
+ // note night after night would otherwise stack a duplicate `Related:` line on
27
+ // every pass (issue #398 review).
28
+ const missing = relatedNames.filter((n) => !existing.includes(`[[${n}]]`));
29
+ const links = missing.length ? `\n\nRelated: ${missing.map((n) => `[[${n}]]`).join(' ')}` : '';
30
+ if (!existing.trim())
31
+ return `${trimmedAddition}${links}`;
32
+ // Already recorded verbatim: there is nothing to append — but the note must
33
+ // still gain edges it is missing, or a fact that recurs under a name it
34
+ // already occupies stays a disconnected node forever (issue #398 review).
35
+ if (existing.includes(trimmedAddition))
36
+ return links ? `${existing.trim()}${links}` : existing;
37
+ return `${existing.trim()}\n\n${trimmedAddition}${links}`;
38
+ }
39
+ /**
40
+ * Write only if the result stays within the same size cap the manual
41
+ * `memory_shared_create`/`_update` tools enforce. Unlike those tools, dreaming
42
+ * has no one to show a "content too large" error to — a note a recurring topic
43
+ * keeps merging into over months has no natural stopping point otherwise, so
44
+ * once a merge would cross the cap the note is left as-is (best-effort: a
45
+ * skipped promotion never fails the local dream, same as any other error here).
46
+ */
47
+ function writeCapped(cfg, name, content, relatedNames = []) {
48
+ const body = relatedNames.length ? mergeIntoNote('', content, relatedNames) : content;
49
+ if (body.length > shared_writer_1.MAX_SHARED_NOTE_SIZE)
50
+ return false;
51
+ (0, shared_writer_1.writeSharedNote)(cfg, name, body);
52
+ return true;
53
+ }
54
+ // ── What may be promoted, and under what name (issue #398) ─────────────────
55
+ /** A `MEMORY.md` index bullet: `- [label](target.md) — hook`. */
56
+ const INDEX_POINTER_LINE_RE = /^\s*[-*]\s*\[[^\]]*\]\([^)]*\)\s*(?:[-–—:].*)?$/;
57
+ /**
58
+ * True when the content is nothing but `MEMORY.md` index pointers.
59
+ *
60
+ * The memory-tiering convention writes a fact to its own file AND a one-line
61
+ * pointer into the `MEMORY.md` index. Both land as durable `add`s, so both used
62
+ * to be promoted — and the pointer's links resolve only inside the promoting
63
+ * agent's own workspace, making it dead weight in a shared vault (issue #398:
64
+ * 30 of 40 live notes were pointer-only, with all 32 link targets dangling).
65
+ *
66
+ * The trailing hook text is deliberately NOT rescued into a note of its own: an
67
+ * index hook is written to be read next to the file it points at, so promoting
68
+ * it alone would put a decontextualized fragment in front of every other agent.
69
+ * A dream that proposes the fact itself as prose still promotes normally — only
70
+ * navigation scaffolding is dropped.
71
+ */
72
+ function isIndexPointerOnly(content) {
73
+ const lines = content
74
+ .split('\n')
75
+ .map((l) => l.trim())
76
+ .filter(Boolean);
77
+ if (lines.length === 0)
78
+ return false;
79
+ return lines.every((l) => INDEX_POINTER_LINE_RE.test(l));
80
+ }
81
+ /** Leading verbs that make a `reason` an edit instruction rather than a topic. */
82
+ const INSTRUCTION_VERB_RE = /^(insert|append|prepend|add|put|place|move|update|replace|remove|delete|keep|group)\b/i;
83
+ /** Anchors an edit instruction points AT — a position or a file, not a subject. */
84
+ const EDIT_ANCHOR_RE = /\b(before|after|above|below|between|index|section|\.md)\b/i;
85
+ /**
86
+ * True when a note name reads as an instruction for editing a file rather than
87
+ * as the name of a fact ("insert after cron config, before the pty_shell
88
+ * section"). `reason` is prompted as free-form justification (`reviewer.ts`) but
89
+ * issue #386 repurposed it as the shared note's identity, so such strings became
90
+ * note names that no future occurrence of the same fact can ever match.
91
+ *
92
+ * Two signals are required — an imperative edit verb AND a positional/file
93
+ * anchor — because either alone is common in legitimate names ("Add-on billing
94
+ * is per seat", "the section header is user-visible").
95
+ */
96
+ function isInstructionShapedName(name) {
97
+ return INSTRUCTION_VERB_RE.test(name.trim()) && EDIT_ANCHOR_RE.test(name);
98
+ }
99
+ /**
100
+ * A note name derived from the fact itself — the fallback when the proposal has
101
+ * no `topic` and its `reason` reads as an editing instruction.
102
+ *
103
+ * Dropping such a promotion outright (the first cut of issue #398) silently lost
104
+ * genuine facts whose reason merely happened to start with an edit verb, e.g.
105
+ * "Update the deploy section after the API change". Naming from the content
106
+ * keeps the fact; only content with nothing nameable in it is given up on.
107
+ */
108
+ function deriveNameFromContent(content) {
109
+ const firstSentence = content
110
+ .replace(/^[\s>*-]+/, '')
111
+ .replace(/`+/g, '')
112
+ .split(/(?<=[.!?])\s|\n/)[0]
113
+ ?.trim();
114
+ if (!firstSentence)
115
+ return undefined;
116
+ const name = firstSentence.slice(0, 80).trim();
117
+ // Needs real topic words, not just scaffolding, to be a usable identity.
118
+ return (0, shared_dedup_1.significantTokens)(name).size >= 3 ? name : undefined;
119
+ }
7
120
  /**
8
121
  * Build the per-agent→shared promotion function used after the dreaming applier
9
122
  * writes an `add` to local memory (K3↔K4). Returns `undefined` when the shared
10
123
  * KB is disabled or in `propose` (dry-run) mode — callers then skip promotion.
11
124
  *
12
- * A durable add is written to the shared vault under a **content-hashed**
13
- * filename so two proposals whose reasons slug to the same name (or a recurring
14
- * nightly reason) can't silently overwrite each other, and identical content
15
- * maps to the same file (idempotent). Extracted here so both the nightly
16
- * auto-applier (src/index.ts) and the manual dashboard accept endpoint share one
17
- * implementation.
125
+ * Note identity is the proposal's `reason` (issue #386) the SAME freeform
126
+ * namespace `memory_shared_create`/`_update` use, not a content hash. A
127
+ * content-hashed name (the pre-#386 scheme) meant the same recurring fact,
128
+ * reworded slightly by the reviewer each night, hashed differently and was
129
+ * written as a brand-new note every time this is what created the
130
+ * near-duplicate pileup #386 reports. Naming by `reason` instead means the
131
+ * same recurring fact maps to the same note name across nights, landing as an
132
+ * update. A `reason` that doesn't collide by name is also checked against the
133
+ * shared vault's near-dup search before creating, and merged into the closest
134
+ * match instead of creating a disconnected duplicate when one is found.
135
+ *
136
+ * Concurrency note: the name-collision check, read, and write below are not one
137
+ * atomic operation, so two agents dreaming at the same moment and promoting
138
+ * under the exact same `reason` can race — `shared-writer.ts` already documents
139
+ * that same-file races resolve to last-write-wins, never corruption, and other
140
+ * read-modify-write callers (e.g. `memory_shared_update`) have this same
141
+ * property. Naming by `reason` instead of a content hash makes same-name
142
+ * collisions between different agents more likely than the pre-#386 scheme, but
143
+ * it's the same accepted tradeoff, not a new class of risk.
18
144
  */
19
- function makeSharedPromoter(agentId, agentCfg, globalCfg) {
145
+ function makeSharedPromoter(
146
+ // No longer used for NAMING — issue #386 moved note identity off the agent id,
147
+ // and issue #398 moved it to the proposal's `topic` slug (falling back to
148
+ // `reason`, then to the fact itself), so a recurring fact maps to the same
149
+ // shared note whichever agent dreamed it. Still carried because every declined
150
+ // promotion is logged with the agent that proposed it.
151
+ agentId, agentCfg, globalCfg,
152
+ // Promotion can decline for several reasons and used to do so in total silence,
153
+ // which made "why did the shared vault stop growing?" undiagnosable from the
154
+ // outside (issue #398 review). Optional so existing call sites keep working.
155
+ logger) {
20
156
  const sharedCfg = (0, config_1.resolveSharedConfig)(agentCfg, globalCfg);
21
157
  if (!sharedCfg.enabled || sharedCfg.mode !== 'auto')
22
158
  return undefined;
159
+ const skip = (why, data = {}) => {
160
+ logger?.info('shared promotion skipped', { agentId, why, ...data });
161
+ };
23
162
  return (p) => {
24
- const content = p.content ?? '';
25
- const hash = (0, crypto_1.createHash)('sha256').update(content).digest('hex').slice(0, 8);
26
- (0, shared_writer_1.writeSharedNote)(sharedCfg, `${agentId}-${p.reason}-${hash}`, content);
163
+ const content = (p.content ?? '').trim();
164
+ if (!content)
165
+ return;
166
+ // An index pointer is a per-agent navigation aid, not a shareable fact: its
167
+ // links resolve only inside the promoting agent's own workspace.
168
+ if (isIndexPointerOnly(content)) {
169
+ skip('index-pointer-only', { reason: p.reason });
170
+ return;
171
+ }
172
+ // Identity, best first: an explicit `topic` slug (mirroring the episodic
173
+ // contract), then the free-form `reason`, then the fact itself. A reason that
174
+ // reads as an editing instruction would produce a note name no later
175
+ // occurrence of the same fact could match, so it is passed over — but the
176
+ // fact is still promoted under a content-derived name.
177
+ const explicit = (p.topic ?? '').trim();
178
+ const reason = p.reason.trim();
179
+ const name = explicit ||
180
+ (reason && !isInstructionShapedName(reason) ? reason : deriveNameFromContent(content));
181
+ if (!name) {
182
+ skip('no-usable-name', { reason: p.reason });
183
+ return;
184
+ }
185
+ try {
186
+ // A `stale__` twin of THIS name means the GC retired this fact and it has
187
+ // now recurred — the recurrence itself is the promote-back signal. Resolved
188
+ // BEFORE the update/create split because both paths put a live file back at
189
+ // `name`, and once that file exists the GC's own restore refuses forever
190
+ // (`moveNoteFromStale` bails on `sharedNoteExists`). A twin left behind is
191
+ // not inert: retired notes stay indexed and searchable by design (#392), so
192
+ // the same fact would answer every query twice, permanently (issue #398
193
+ // review — the create path handled this, the update path did not).
194
+ const staleTwin = `${shared_staleness_1.STALE_NOTE_PREFIX}${name}`;
195
+ const retired = (0, shared_writer_1.sharedNoteExists)(sharedCfg, staleTwin)
196
+ ? ((0, shared_writer_1.readSharedNote)(sharedCfg, staleTwin) ?? '').replace(/^<!--[\s\S]*?-->\s*/, '').trim()
197
+ : '';
198
+ // Only drop the twin once its content is safely in the live note: a write
199
+ // refused by the size cap must leave the retired copy where it is.
200
+ const dropTwinIfWritten = (written) => {
201
+ if (written && retired)
202
+ (0, shared_writer_1.deleteSharedNoteFile)(sharedCfg, staleTwin);
203
+ };
204
+ // Same name recurred: this IS the same fact — update it, never duplicate.
205
+ if ((0, shared_writer_1.sharedNoteExists)(sharedCfg, name)) {
206
+ const existing = (0, shared_writer_1.readSharedNote)(sharedCfg, name) ?? '';
207
+ const withRetired = retired ? mergeIntoNote(existing, retired) : existing;
208
+ const written = writeCapped(sharedCfg, name, mergeIntoNote(withRetired, content));
209
+ if (!written)
210
+ skip('note-size-cap', { reason: p.reason, note: name });
211
+ dropTwinIfWritten(written);
212
+ return;
213
+ }
214
+ // New name, but the content may already live in another note. FTS is
215
+ // recall only; `filterNearDuplicates` is the precision bar that decides
216
+ // whether an UNATTENDED merge is justified. Below the bar the fact gets
217
+ // its own note — two notes are recoverable, two unrelated facts fused
218
+ // into one are not (issue #398).
219
+ //
220
+ // Retired (`stale__*`) notes are excluded as targets: they are indexed
221
+ // like any other file, and merging a fresh fact into one would write it
222
+ // straight into the set the GC has already moved out of the active vault.
223
+ const seed = (0, shared_dedup_1.buildSeedText)(name, content);
224
+ const candidates = (0, shared_dedup_1.findSimilarSharedNotes)(sharedCfg, seed, 3).filter((c) => !c.name.startsWith(shared_staleness_1.STALE_NOTE_PREFIX));
225
+ // Score against each candidate's FULL body, not the matched chunk: a long
226
+ // note that a recurring topic has merged into spreads the seed across
227
+ // chunks and would score far below its real containment.
228
+ const bodyOf = (c) => (0, shared_writer_1.readSharedNote)(sharedCfg, c.name) ?? '';
229
+ const mergeable = (0, shared_dedup_1.filterNearDuplicates)(seed, candidates, undefined, bodyOf);
230
+ // Wikilinks clear a LOWER bar than merges: a link is an additive "these
231
+ // are related" claim, while a merge asserts they are the same fact and
232
+ // destroys the distinction. Computed for BOTH outcomes — a new note that
233
+ // has related neighbours must still carry its edges, or the graph gains a
234
+ // disconnected node exactly in the 0.25–0.5 band (issue #398 review).
235
+ const linkable = (0, shared_dedup_1.filterNearDuplicates)(seed, candidates, shared_dedup_1.MIN_RELATED_CONTAINMENT, bodyOf);
236
+ if (mergeable.length > 0) {
237
+ const primary = mergeable[0];
238
+ const related = linkable.filter((c) => c.name !== primary.name);
239
+ const existing = (0, shared_writer_1.readSharedNote)(sharedCfg, primary.name) ?? '';
240
+ if (!writeCapped(sharedCfg, primary.name, mergeIntoNote(existing, content, related.map((r) => r.name)))) {
241
+ skip('note-size-cap', { reason: p.reason, note: primary.name });
242
+ }
243
+ return;
244
+ }
245
+ // Creating fresh. Folding a retired twin back in here also stops two files
246
+ // from sharing one `entry_hash`, whose single lifecycle row's `path` would
247
+ // otherwise flip between them on every index pass.
248
+ const body = retired ? mergeIntoNote(retired, content) : content;
249
+ const written = writeCapped(sharedCfg, name, body, linkable.map((r) => r.name));
250
+ if (!written)
251
+ skip('note-size-cap', { reason: p.reason, note: name });
252
+ dropTwinIfWritten(written);
253
+ }
254
+ catch (err) {
255
+ // Best-effort — a promotion failure never affects the local dream. It must
256
+ // not be invisible either: a vault that silently stops growing was the
257
+ // undiagnosable failure issue #398 set out to close.
258
+ skip('write-failed', { reason: p.reason, error: err?.message });
259
+ }
27
260
  };
28
261
  }
29
262
  //# sourceMappingURL=shared-promote.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shared-promote.js","sourceRoot":"","sources":["../../../src/agent/knowledge/shared-promote.ts"],"names":[],"mappings":";;AAiBA,gDAYC;AA7BD,mCAAoC;AACpC,mDAAkD;AAClD,qCAA+C;AAG/C;;;;;;;;;;;GAWG;AACH,SAAgB,kBAAkB,CAChC,OAAe,EACf,QAA2C,EAC3C,SAA4C;IAE5C,MAAM,SAAS,GAAG,IAAA,4BAAmB,EAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC3D,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,SAAS,CAAC;IACtE,OAAO,CAAC,CAAuC,EAAE,EAAE;QACjD,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5E,IAAA,+BAAe,EAAC,SAAS,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IACxE,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"shared-promote.js","sourceRoot":"","sources":["../../../src/agent/knowledge/shared-promote.ts"],"names":[],"mappings":"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"}