recallnest 1.4.1 → 2.2.1

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 (233) hide show
  1. package/.env +4 -0
  2. package/CLAUDE.md +7 -3
  3. package/README.md +202 -130
  4. package/README_CN.md +263 -190
  5. package/bin/recallnest.mjs +62 -0
  6. package/config.json +55 -0
  7. package/daily-recall.sh +23 -0
  8. package/integrations/setup-all.sh +31 -0
  9. package/logs/daily-recall.error.log +0 -0
  10. package/logs/daily-recall.log +30 -0
  11. package/logs/ingest-2026-04-11.log +110 -0
  12. package/logs/ingest-2026-04-12.log +90 -0
  13. package/logs/launchagent-stderr.log +0 -0
  14. package/logs/launchagent-stdout.log +0 -0
  15. package/package.json +10 -3
  16. package/src/access-tracker.ts +3 -2
  17. package/src/activity-counter.ts +109 -0
  18. package/src/adaptive-retrieval.ts +6 -5
  19. package/src/admission-control.ts +163 -0
  20. package/src/anchor-generator.ts +80 -0
  21. package/src/api-server.ts +10 -0
  22. package/src/audit-log.ts +122 -0
  23. package/src/auto-gc.ts +98 -29
  24. package/src/auto-recall.ts +33 -4
  25. package/src/capture-engine.ts +372 -23
  26. package/src/cascade-forget.ts +3 -2
  27. package/src/cli.ts +107 -5
  28. package/src/compat.ts +22 -0
  29. package/src/confidence-tracker.ts +210 -47
  30. package/src/conflict-store.ts +12 -3
  31. package/src/consolidation-engine.ts +296 -8
  32. package/src/context-collapse-renderer.ts +222 -0
  33. package/src/context-composer-stable-selection.ts +5 -1
  34. package/src/context-composer.ts +239 -4
  35. package/src/context-reconstructor.ts +404 -0
  36. package/src/conversation-importer.ts +441 -0
  37. package/src/data-checkup.ts +232 -0
  38. package/src/decay-engine.ts +84 -0
  39. package/src/distill-lock.ts +122 -0
  40. package/src/doctor.ts +8 -10
  41. package/src/dream-pipeline.ts +227 -0
  42. package/src/emotion-detector.ts +90 -0
  43. package/src/eval.ts +3 -2
  44. package/src/forget-engine.ts +269 -0
  45. package/src/frequency-tracker.ts +175 -0
  46. package/src/graph-export.ts +644 -0
  47. package/src/ingest.ts +36 -6
  48. package/src/interference-detector.ts +187 -0
  49. package/src/kg-extractor.ts +15 -24
  50. package/src/kg-store.ts +2 -1
  51. package/src/language-hook.ts +67 -0
  52. package/src/llm-client.ts +289 -6
  53. package/src/mcp-server.ts +707 -80
  54. package/src/memory-assets.ts +22 -5
  55. package/src/memory-evolution.ts +365 -0
  56. package/src/memory-lint.ts +429 -0
  57. package/src/memory-output.ts +69 -0
  58. package/src/memory-schema.ts +74 -2
  59. package/src/narrative-schema.ts +79 -0
  60. package/src/narrative-tagger.ts +164 -0
  61. package/src/pii-detector.ts +98 -0
  62. package/src/post-retrieval-filter.ts +76 -0
  63. package/src/prediction-engine.ts +253 -0
  64. package/src/preference-slots.ts +65 -2
  65. package/src/prospective-memory.ts +215 -0
  66. package/src/query-expander.ts +67 -0
  67. package/src/recall-governor.ts +183 -0
  68. package/src/retention-policy.ts +90 -0
  69. package/src/retriever.ts +828 -55
  70. package/src/rif.ts +34 -8
  71. package/src/runtime-config.ts +23 -8
  72. package/src/scope-inventory.ts +4 -3
  73. package/src/scope-policy.ts +4 -0
  74. package/src/session-distiller.ts +307 -0
  75. package/src/session-output.ts +40 -0
  76. package/src/session-schema.ts +37 -0
  77. package/src/session-store.ts +7 -2
  78. package/src/skill-engine.ts +208 -0
  79. package/src/skill-promotion.ts +293 -0
  80. package/src/skill-schema.ts +39 -0
  81. package/src/store.ts +96 -10
  82. package/src/topic-tag.ts +66 -0
  83. package/src/tracker.ts +3 -1
  84. package/src/ui-server.ts +80 -8
  85. package/src/workflow-observation-scope-review.ts +2 -1
  86. package/src/workflow-observation-store.ts +2 -1
  87. package/src/write-verifier.ts +126 -0
  88. package/.claude-plugin/CLAUDE.md +0 -29
  89. package/.claude-plugin/marketplace.json +0 -18
  90. package/.github/workflows/ci.yml +0 -28
  91. package/.mcp.json +0 -9
  92. package/CHANGELOG.md +0 -51
  93. package/DEMO.md +0 -94
  94. package/EVAL.md +0 -89
  95. package/FAILURES.md +0 -75
  96. package/OPERATIONS.md +0 -59
  97. package/ROADMAP.md +0 -198
  98. package/assets/recallnest-workbench.png +0 -0
  99. package/assets/ui/app.js +0 -807
  100. package/assets/ui/index.html +0 -154
  101. package/assets/ui/styles.css +0 -605
  102. package/assets/wechat-qr.jpg +0 -0
  103. package/daily-reflections/2026-03-05.md +0 -15
  104. package/daily-reflections/2026-03-06.md +0 -15
  105. package/daily-reflections/2026-03-07.md +0 -7
  106. package/daily-reflections/2026-03-08.md +0 -34
  107. package/daily-reflections/2026-03-09.md +0 -35
  108. package/daily-reflections/2026-03-10.md +0 -36
  109. package/daily-reflections/2026-03-11.md +0 -35
  110. package/daily-reflections/2026-03-12.md +0 -36
  111. package/daily-reflections/2026-03-13.md +0 -35
  112. package/daily-reflections/2026-03-14.md +0 -35
  113. package/daily-reflections/2026-03-15.md +0 -35
  114. package/daily-reflections/2026-03-16.md +0 -41
  115. package/daily-reflections/2026-03-17.md +0 -10
  116. package/daily-reflections/2026-03-18.md +0 -10
  117. package/daily-reflections/2026-03-19.md +0 -10
  118. package/daily-reflections/2026-03-20.md +0 -10
  119. package/daily-reflections/2026-03-21.md +0 -10
  120. package/daily-reflections/2026-03-22.md +0 -10
  121. package/daily-reflections/2026-03-23.md +0 -10
  122. package/daily-reflections/2026-03-24.md +0 -10
  123. package/daily-reflections/2026-03-25.md +0 -32
  124. package/daily-reflections/2026-03-26.md +0 -37
  125. package/daily-reflections/2026-03-27.md +0 -35
  126. package/daily-reflections/2026-03-28.md +0 -35
  127. package/daily-reflections/2026-03-29.md +0 -35
  128. package/daily-reflections/2026-03-30.md +0 -36
  129. package/daily-reflections/2026-03-31.md +0 -35
  130. package/daily-reflections/2026-04-01.md +0 -35
  131. package/docs/api-reference.md +0 -1291
  132. package/docs/architecture.md +0 -168
  133. package/docs/memory-boundary-contract.md +0 -226
  134. package/docs/memory-categories.md +0 -119
  135. package/docs/pipeline-audit-ultramemory.md +0 -177
  136. package/docs/plans/2026-03-08-query-expansion.md +0 -270
  137. package/docs/plans/2026-03-16-continuity-layer.md +0 -196
  138. package/docs/plans/mcp-tiering-plan.md +0 -126
  139. package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
  140. package/docs/self-evolution.md +0 -198
  141. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
  142. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
  143. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
  144. package/eval/continuity/results-2026-03-16.md +0 -117
  145. package/eval/continuity/results-2026-03-19.md +0 -453
  146. package/eval/continuity/runbook.md +0 -110
  147. package/eval/continuity/seed-cases.md +0 -54
  148. package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
  149. package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
  150. package/eval/reports/2026-03-06-baseline.md +0 -82
  151. package/eval/reports/query-expansion.md +0 -138
  152. package/lm +0 -19
  153. package/plan-v1.2-distributable.md +0 -105
  154. package/scripts/archive-distilled-junk.ts +0 -116
  155. package/scripts/backfill-l0l1l2.ts +0 -98
  156. package/scripts/blind-scan.ts +0 -588
  157. package/scripts/cleanup-step1-exact-dups.ts +0 -117
  158. package/scripts/cleanup-step2-template-dups.ts +0 -113
  159. package/scripts/cleanup-step3-stale-content.ts +0 -149
  160. package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
  161. package/scripts/delete-distilled-junk.ts +0 -88
  162. package/scripts/distill-facts.ts +0 -305
  163. package/scripts/health-check.ts +0 -343
  164. package/scripts/incremental-ingest.sh +0 -71
  165. package/scripts/mlp-health-check.ts +0 -382
  166. package/scripts/pull-claude-desktop.ts +0 -515
  167. package/scripts/re-ingest-cc.sh +0 -66
  168. package/scripts/rebalance-memory-health.ts +0 -181
  169. package/scripts/run-desktop-ingest.sh +0 -11
  170. package/scripts/start-server.sh +0 -12
  171. package/scripts/viz-dashboard.html +0 -423
  172. package/scripts/viz-export.ts +0 -703
  173. package/src/__tests__/access-tracker-novelty.test.ts +0 -228
  174. package/src/__tests__/auto-consolidation.test.ts +0 -168
  175. package/src/__tests__/auto-recall.test.ts +0 -131
  176. package/src/__tests__/batch-dedup.test.ts +0 -65
  177. package/src/__tests__/capture-engine.test.ts +0 -1142
  178. package/src/__tests__/capture-heuristic.test.ts +0 -171
  179. package/src/__tests__/cascade-forget.test.ts +0 -210
  180. package/src/__tests__/category-min-scores.test.ts +0 -33
  181. package/src/__tests__/confidence-tracker.test.ts +0 -196
  182. package/src/__tests__/conflict-advisor.test.ts +0 -206
  183. package/src/__tests__/conflict-escalation.test.ts +0 -149
  184. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  185. package/src/__tests__/conflict-output.test.ts +0 -85
  186. package/src/__tests__/conflict-store.test.ts +0 -374
  187. package/src/__tests__/consolidation-engine.test.ts +0 -181
  188. package/src/__tests__/context-composer-pins.test.ts +0 -64
  189. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  190. package/src/__tests__/context-renderer.test.ts +0 -91
  191. package/src/__tests__/continuity-eval.test.ts +0 -275
  192. package/src/__tests__/continuity-schema.test.ts +0 -97
  193. package/src/__tests__/core-recall-eval.test.ts +0 -235
  194. package/src/__tests__/core-summary.test.ts +0 -109
  195. package/src/__tests__/dedup-actions.test.ts +0 -58
  196. package/src/__tests__/deterministic-id.test.ts +0 -39
  197. package/src/__tests__/doctor.test.ts +0 -303
  198. package/src/__tests__/drill-down.test.ts +0 -108
  199. package/src/__tests__/embedder.test.ts +0 -122
  200. package/src/__tests__/entity-resolver.test.ts +0 -58
  201. package/src/__tests__/event-segmenter.test.ts +0 -153
  202. package/src/__tests__/hotness-blend.test.ts +0 -76
  203. package/src/__tests__/ingest.test.ts +0 -360
  204. package/src/__tests__/kg-extractor.test.ts +0 -184
  205. package/src/__tests__/kg-store.test.ts +0 -198
  206. package/src/__tests__/llm-consolidation.test.ts +0 -226
  207. package/src/__tests__/memory-boundaries.test.ts +0 -237
  208. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  209. package/src/__tests__/memory-output.test.ts +0 -215
  210. package/src/__tests__/multi-vector.test.ts +0 -242
  211. package/src/__tests__/ppr-traversal.test.ts +0 -256
  212. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  213. package/src/__tests__/preference-matcher.test.ts +0 -230
  214. package/src/__tests__/preference-slots.test.ts +0 -106
  215. package/src/__tests__/prospective-memory.test.ts +0 -283
  216. package/src/__tests__/query-entity-detector.test.ts +0 -171
  217. package/src/__tests__/query-expander.test.ts +0 -34
  218. package/src/__tests__/recall-mode.test.ts +0 -64
  219. package/src/__tests__/result-synthesizer.test.ts +0 -198
  220. package/src/__tests__/resume-context.test.ts +0 -4743
  221. package/src/__tests__/retrieval-trace.test.ts +0 -213
  222. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  223. package/src/__tests__/retroactive-boost.test.ts +0 -223
  224. package/src/__tests__/scope-inventory.test.ts +0 -200
  225. package/src/__tests__/scope-policy.test.ts +0 -69
  226. package/src/__tests__/session-checkpoint.test.ts +0 -259
  227. package/src/__tests__/session-gc.test.ts +0 -215
  228. package/src/__tests__/store-scope-audit.test.ts +0 -81
  229. package/src/__tests__/temporal-parser.test.ts +0 -241
  230. package/src/__tests__/term-registry.test.ts +0 -121
  231. package/src/__tests__/tool-output-compressor.test.ts +0 -133
  232. package/src/__tests__/version-manager.test.ts +0 -213
  233. package/src/__tests__/workflow-observation.test.ts +0 -400
@@ -1,206 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { buildConflictCandidateRecord } from "../conflict-engine.js";
4
- import { buildConflictAuditSummary, clusterConflicts, summarizeConflictAdvice } from "../conflict-advisor.js";
5
-
6
- /** Returns an ISO timestamp N days before now (defaults to 4 days = firmly "stale"). */
7
- function daysAgo(n: number): string {
8
- return new Date(Date.now() - n * 24 * 60 * 60 * 1000).toISOString();
9
- }
10
-
11
- function createConflictRecord(overrides: Record<string, unknown> = {}) {
12
- const defaultTs = daysAgo(4); // 4 days ago → "stale" (3-6 days range)
13
- return {
14
- ...buildConflictCandidateRecord({
15
- canonicalKey: "user-reply-style",
16
- category: "preferences",
17
- fingerprint: "user-reply-style--durable-1--source-1--new-text",
18
- reason: "promotion_conflicts_with_existing_durable",
19
- existing: {
20
- memoryId: "durable-1",
21
- text: "User prefers concise, direct, technical replies.",
22
- category: "preferences",
23
- scope: "memory:agent",
24
- importance: 0.84,
25
- metadata: "{\"source\":\"agent\"}",
26
- canonicalKey: "user-reply-style",
27
- },
28
- incoming: {
29
- text: "User prefers concise direct technical replies.",
30
- category: "preferences",
31
- scope: "memory:agent",
32
- importance: 0.78,
33
- metadata: "{\"source\":\"agent\"}",
34
- source: "agent",
35
- sourceMemoryId: "source-1",
36
- sourceCategory: "events",
37
- },
38
- createdAt: defaultTs,
39
- updatedAt: defaultTs,
40
- }),
41
- ...overrides,
42
- };
43
- }
44
-
45
- describe("summarizeConflictAdvice", () => {
46
- it("suggests keep_existing for text-equivalent conflicts", () => {
47
- const advice = summarizeConflictAdvice(createConflictRecord() as any);
48
-
49
- expect(advice.suggestedResolution).toBe("keep_existing");
50
- expect(advice.confidence).toBe("high");
51
- expect(advice.similarity).toBe(1);
52
- });
53
-
54
- it("suggests keep_existing for cross-category canonical key conflicts", () => {
55
- const advice = summarizeConflictAdvice(createConflictRecord({
56
- reason: "canonical_key_conflicts_with_existing_durable",
57
- category: "events",
58
- incoming: {
59
- text: "Reply-style observation imported as an event.",
60
- category: "events",
61
- scope: "memory:agent",
62
- importance: 0.78,
63
- metadata: "{\"source\":\"agent\"}",
64
- source: "agent",
65
- sourceMemoryId: "source-2",
66
- sourceCategory: "events",
67
- },
68
- }) as any);
69
-
70
- expect(advice.suggestedResolution).toBe("keep_existing");
71
- expect(["medium", "high"]).toContain(advice.confidence);
72
- expect(advice.reasons.some((reason) => reason.includes("canonical key"))).toBe(true);
73
- });
74
-
75
- it("suggests accept_incoming when the incoming text is a tighter rewrite of the same memory", () => {
76
- const advice = summarizeConflictAdvice(createConflictRecord({
77
- existing: {
78
- memoryId: "durable-1",
79
- text: "User prefers concise grounded technical replies and avoids sales language in the final copy.",
80
- category: "preferences",
81
- scope: "memory:agent",
82
- importance: 0.84,
83
- metadata: "{\"source\":\"agent\"}",
84
- canonicalKey: "user-reply-style",
85
- },
86
- incoming: {
87
- text: "User prefers concise grounded technical replies and avoids sales language.",
88
- category: "preferences",
89
- scope: "memory:agent",
90
- importance: 0.78,
91
- metadata: "{\"source\":\"agent\"}",
92
- source: "agent",
93
- sourceMemoryId: "source-1",
94
- sourceCategory: "events",
95
- },
96
- }) as any);
97
-
98
- expect(advice.suggestedResolution).toBe("accept_incoming");
99
- expect(advice.confidence).toBe("medium");
100
- expect(advice.similarity).toBeGreaterThanOrEqual(0.72);
101
- expect(advice.mergeSuggestion).toBeUndefined();
102
- });
103
-
104
- it("offers a merged wording suggestion for same-category manual_review conflicts", () => {
105
- const advice = summarizeConflictAdvice(createConflictRecord({
106
- existing: {
107
- memoryId: "durable-1",
108
- text: "User prefers concise grounded technical replies; avoid sales language.",
109
- category: "preferences",
110
- scope: "memory:agent",
111
- importance: 0.84,
112
- metadata: "{\"source\":\"agent\"}",
113
- canonicalKey: "user-reply-style",
114
- },
115
- incoming: {
116
- text: "User prefers concise grounded technical replies; keep the tone colloquial.",
117
- category: "preferences",
118
- scope: "memory:agent",
119
- importance: 0.78,
120
- metadata: "{\"source\":\"agent\"}",
121
- source: "agent",
122
- sourceMemoryId: "source-1",
123
- sourceCategory: "events",
124
- },
125
- }) as any);
126
-
127
- expect(advice.suggestedResolution).toBe("manual_review");
128
- expect(advice.mergeSuggestion).toContain("User prefers concise grounded technical replies");
129
- expect(advice.mergeSuggestion).toContain("avoid sales language");
130
- expect(advice.mergeSuggestion).toContain("keep the tone colloquial");
131
- expect(advice.reasons.some((reason) => reason.includes("merged durable wording"))).toBe(true);
132
- });
133
- });
134
-
135
- describe("clusterConflicts", () => {
136
- it("groups conflicts by canonical key, reason, and category", () => {
137
- const clusters = clusterConflicts([
138
- createConflictRecord({
139
- conflictId: "00000000-0000-0000-0000-000000000001",
140
- createdAt: daysAgo(4),
141
- updatedAt: daysAgo(4),
142
- }) as any,
143
- createConflictRecord({
144
- conflictId: "00000000-0000-0000-0000-000000000002",
145
- createdAt: daysAgo(4),
146
- updatedAt: daysAgo(3),
147
- }) as any,
148
- createConflictRecord({
149
- canonicalKey: "project-entity-owner",
150
- conflictId: "00000000-0000-0000-0000-000000000003",
151
- fingerprint: "project-entity-owner--durable-1--source-1--new-text",
152
- }) as any,
153
- ]);
154
-
155
- expect(clusters).toHaveLength(2);
156
- expect(clusters[0]?.canonicalKey).toBe("user-reply-style");
157
- expect(clusters[0]?.totalCount).toBe(2);
158
- expect(clusters[0]?.latestConflictId).toBe("00000000-0000-0000-0000-000000000002");
159
- expect(clusters[0]?.attention).toBe("stale");
160
- });
161
- });
162
-
163
- describe("buildConflictAuditSummary", () => {
164
- it("highlights escalated or stale open clusters first", () => {
165
- const summary = buildConflictAuditSummary([
166
- createConflictRecord({
167
- conflictId: "00000000-0000-0000-0000-000000000001",
168
- createdAt: daysAgo(14),
169
- updatedAt: daysAgo(14),
170
- }) as any,
171
- createConflictRecord({
172
- canonicalKey: "project-entity-owner",
173
- conflictId: "00000000-0000-0000-0000-000000000002",
174
- fingerprint: "project-entity-owner--durable-1--source-1--new-text",
175
- createdAt: daysAgo(1),
176
- updatedAt: daysAgo(1),
177
- }) as any,
178
- createConflictRecord({
179
- conflictId: "00000000-0000-0000-0000-000000000003",
180
- status: "kept-existing",
181
- resolvedAt: daysAgo(0),
182
- updatedAt: daysAgo(0),
183
- }) as any,
184
- ], 3);
185
-
186
- expect(summary.totalConflicts).toBe(3);
187
- expect(summary.openConflicts).toBe(2);
188
- expect(summary.attentionCounts.resolved).toBe(1);
189
- expect(summary.priorityClusters[0]?.attention).toBe("escalated");
190
- expect(summary.suggestedActions.some((action) => action.includes("escalated"))).toBe(true);
191
- });
192
-
193
- it("returns a no-open-conflicts message when nothing is pending", () => {
194
- const summary = buildConflictAuditSummary([
195
- createConflictRecord({
196
- conflictId: "00000000-0000-0000-0000-000000000001",
197
- status: "kept-existing",
198
- resolvedAt: "2026-03-16T00:00:00.000Z",
199
- }) as any,
200
- ], 3);
201
-
202
- expect(summary.openConflicts).toBe(0);
203
- expect(summary.priorityClusters).toHaveLength(0);
204
- expect(summary.suggestedActions).toEqual(["No open conflicts need review."]);
205
- });
206
- });
@@ -1,149 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { buildConflictCandidateRecord } from "../conflict-engine.js";
4
- import { buildConflictEscalationItem, escalateConflicts } from "../conflict-escalation.js";
5
-
6
- function createConflictRecord(overrides: Record<string, unknown> = {}) {
7
- return {
8
- ...buildConflictCandidateRecord({
9
- canonicalKey: "user-reply-style",
10
- category: "preferences",
11
- fingerprint: "user-reply-style--durable-1--source-1--new-text",
12
- reason: "promotion_conflicts_with_existing_durable",
13
- existing: {
14
- memoryId: "durable-1",
15
- text: "User prefers concise, direct, technical replies.",
16
- category: "preferences",
17
- scope: "memory:agent",
18
- importance: 0.84,
19
- metadata: "{\"source\":\"agent\"}",
20
- canonicalKey: "user-reply-style",
21
- },
22
- incoming: {
23
- text: "User prefers colloquial, grounded technical replies.",
24
- category: "preferences",
25
- scope: "memory:agent",
26
- importance: 0.78,
27
- metadata: "{\"source\":\"agent\"}",
28
- source: "agent",
29
- sourceMemoryId: "source-1",
30
- sourceCategory: "events",
31
- },
32
- createdAt: "2026-03-01T00:00:00.000Z",
33
- updatedAt: "2026-03-01T00:00:00.000Z",
34
- }),
35
- ...overrides,
36
- };
37
- }
38
-
39
- describe("buildConflictEscalationItem", () => {
40
- it("only returns an escalation item for stale or escalated open conflicts", () => {
41
- const staleItem = buildConflictEscalationItem(
42
- createConflictRecord() as any,
43
- new Date("2026-03-16T00:00:00.000Z"),
44
- );
45
- const freshItem = buildConflictEscalationItem(
46
- createConflictRecord({
47
- createdAt: "2026-03-16T00:00:00.000Z",
48
- updatedAt: "2026-03-16T00:00:00.000Z",
49
- }) as any,
50
- new Date("2026-03-16T12:00:00.000Z"),
51
- );
52
-
53
- expect(staleItem?.attention).toBe("escalated");
54
- expect(freshItem).toBeNull();
55
- });
56
- });
57
-
58
- describe("escalateConflicts", () => {
59
- it("previews eligible stale conflicts without mutating them", async () => {
60
- const records = [
61
- createConflictRecord({
62
- conflictId: "00000000-0000-0000-0000-000000000001",
63
- }),
64
- createConflictRecord({
65
- conflictId: "00000000-0000-0000-0000-000000000002",
66
- createdAt: "2026-03-16T00:00:00.000Z",
67
- updatedAt: "2026-03-16T00:00:00.000Z",
68
- }),
69
- ];
70
- const replaced: any[] = [];
71
-
72
- const result = await escalateConflicts({
73
- conflictStore: {
74
- async listRecent() {
75
- return records as any;
76
- },
77
- async replace(record: any) {
78
- replaced.push(record);
79
- return record;
80
- },
81
- } as any,
82
- }, {
83
- attention: "escalated",
84
- limit: 100,
85
- top: 10,
86
- apply: false,
87
- }, { now: new Date("2026-03-17T00:00:00.000Z") });
88
-
89
- expect(result.apply).toBe(false);
90
- expect(result.eligible).toBe(1);
91
- expect(result.escalated).toBe(0);
92
- expect(result.items[0]?.action).toBe("pending");
93
- expect(replaced).toHaveLength(0);
94
- });
95
-
96
- it("applies escalation metadata exactly once per attention bucket", async () => {
97
- const records = [
98
- createConflictRecord({
99
- conflictId: "00000000-0000-0000-0000-000000000001",
100
- }),
101
- ];
102
- const replaced: any[] = [];
103
-
104
- const result = await escalateConflicts({
105
- conflictStore: {
106
- async listRecent() {
107
- return records as any;
108
- },
109
- async replace(record: any) {
110
- replaced.push(record);
111
- return record;
112
- },
113
- } as any,
114
- }, {
115
- attention: "escalated",
116
- limit: 100,
117
- top: 10,
118
- apply: true,
119
- notes: "auto escalation test",
120
- });
121
-
122
- expect(result.escalated).toBe(1);
123
- expect(result.items[0]?.action).toBe("escalated");
124
- expect(replaced).toHaveLength(1);
125
- expect(replaced[0]?.escalationCount).toBe(1);
126
- expect(replaced[0]?.lastEscalationAttention).toBe("escalated");
127
-
128
- const second = await escalateConflicts({
129
- conflictStore: {
130
- async listRecent() {
131
- return replaced as any;
132
- },
133
- async replace(record: any) {
134
- replaced[0] = record;
135
- return record;
136
- },
137
- } as any,
138
- }, {
139
- attention: "escalated",
140
- limit: 100,
141
- top: 10,
142
- apply: true,
143
- });
144
-
145
- expect(second.escalated).toBe(0);
146
- expect(second.skipped).toBe(1);
147
- expect(second.items[0]?.action).toBe("already-escalated");
148
- });
149
- });
@@ -1,83 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { summarizeConflictLifecycle } from "../conflict-lifecycle.js";
4
- import { buildConflictCandidateRecord } from "../conflict-engine.js";
5
-
6
- function createConflictRecord(overrides: Record<string, unknown> = {}) {
7
- return {
8
- ...buildConflictCandidateRecord({
9
- canonicalKey: "user-reply-style",
10
- category: "preferences",
11
- fingerprint: "user-reply-style--durable-1--source-1--new-text",
12
- reason: "promotion_conflicts_with_existing_durable",
13
- existing: {
14
- memoryId: "durable-1",
15
- text: "User prefers concise, direct replies.",
16
- category: "preferences",
17
- scope: "memory:agent",
18
- importance: 0.84,
19
- metadata: "{\"source\":\"agent\"}",
20
- canonicalKey: "user-reply-style",
21
- },
22
- incoming: {
23
- text: "User prefers colloquial writing that stays grounded and non-salesy.",
24
- category: "preferences",
25
- scope: "memory:agent",
26
- importance: 0.78,
27
- metadata: "{\"source\":\"agent\"}",
28
- source: "agent",
29
- sourceMemoryId: "source-1",
30
- sourceCategory: "events",
31
- },
32
- createdAt: "2026-03-10T00:00:00.000Z",
33
- updatedAt: "2026-03-10T00:00:00.000Z",
34
- }),
35
- ...overrides,
36
- };
37
- }
38
-
39
- describe("summarizeConflictLifecycle", () => {
40
- it("marks newly opened conflicts as fresh", () => {
41
- const record = createConflictRecord();
42
- const summary = summarizeConflictLifecycle(record as any, new Date("2026-03-10T12:00:00.000Z"));
43
-
44
- expect(summary.attention).toBe("fresh");
45
- expect(summary.openAgeDays).toBe(0);
46
- expect(summary.reopenCount).toBe(0);
47
- expect(summary.needsAttention).toBe(false);
48
- });
49
-
50
- it("marks older or reopened conflicts as aging/stale/escalated", () => {
51
- const aging = summarizeConflictLifecycle(
52
- createConflictRecord({ createdAt: "2026-03-08T00:00:00.000Z" }) as any,
53
- new Date("2026-03-10T00:00:00.000Z"),
54
- );
55
- const stale = summarizeConflictLifecycle(
56
- createConflictRecord({ createdAt: "2026-03-05T00:00:00.000Z" }) as any,
57
- new Date("2026-03-10T00:00:00.000Z"),
58
- );
59
- const escalated = summarizeConflictLifecycle(
60
- createConflictRecord({ status: "open", reopenCount: 3 }) as any,
61
- new Date("2026-03-10T12:00:00.000Z"),
62
- );
63
-
64
- expect(aging.attention).toBe("aging");
65
- expect(stale.attention).toBe("stale");
66
- expect(stale.needsAttention).toBe(true);
67
- expect(escalated.attention).toBe("escalated");
68
- expect(escalated.needsAttention).toBe(true);
69
- });
70
-
71
- it("marks resolved conflicts as resolved", () => {
72
- const record = createConflictRecord({
73
- status: "kept-existing",
74
- resolvedAt: "2026-03-10T02:00:00.000Z",
75
- reopenCount: 1,
76
- });
77
- const summary = summarizeConflictLifecycle(record as any, new Date("2026-03-10T12:00:00.000Z"));
78
-
79
- expect(summary.attention).toBe("resolved");
80
- expect(summary.isOpen).toBe(false);
81
- expect(summary.reopenCount).toBe(1);
82
- });
83
- });
@@ -1,85 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { buildConflictAuditSummary } from "../conflict-advisor.js";
4
- import { buildConflictCandidateRecord } from "../conflict-engine.js";
5
- import { formatConflictAuditMarkdown } from "../conflict-output.js";
6
-
7
- function createConflictRecord(overrides: Record<string, unknown> = {}) {
8
- return {
9
- ...buildConflictCandidateRecord({
10
- canonicalKey: "user-reply-style",
11
- category: "preferences",
12
- fingerprint: "user-reply-style--durable-1--source-1--new-text",
13
- reason: "promotion_conflicts_with_existing_durable",
14
- existing: {
15
- memoryId: "durable-1",
16
- text: "User prefers concise, direct replies.",
17
- category: "preferences",
18
- scope: "memory:agent",
19
- importance: 0.84,
20
- metadata: "{\"source\":\"agent\"}",
21
- canonicalKey: "user-reply-style",
22
- },
23
- incoming: {
24
- text: "User prefers colloquial writing that stays grounded and non-salesy.",
25
- category: "preferences",
26
- scope: "memory:agent",
27
- importance: 0.78,
28
- metadata: "{\"source\":\"agent\"}",
29
- source: "agent",
30
- sourceMemoryId: "source-1",
31
- sourceCategory: "events",
32
- },
33
- createdAt: "2026-03-10T00:00:00.000Z",
34
- updatedAt: "2026-03-10T00:00:00.000Z",
35
- }),
36
- ...overrides,
37
- };
38
- }
39
-
40
- describe("formatConflictAuditMarkdown", () => {
41
- it("renders audit counts, filters, and priority clusters", () => {
42
- const summary = buildConflictAuditSummary([
43
- createConflictRecord({
44
- conflictId: "00000000-0000-0000-0000-000000000001",
45
- createdAt: "2026-03-01T00:00:00.000Z",
46
- updatedAt: "2026-03-01T00:00:00.000Z",
47
- }) as any,
48
- ], 3);
49
-
50
- const markdown = formatConflictAuditMarkdown(summary, {
51
- generatedAt: "2026-03-16T16:00:00.000Z",
52
- limit: 100,
53
- top: 3,
54
- status: "open",
55
- canonicalKey: "user-reply-style",
56
- });
57
-
58
- expect(markdown).toContain("# Conflict Audit");
59
- expect(markdown).toContain("Generated: 2026-03-16T16:00:00.000Z");
60
- expect(markdown).toContain("Filters: status=open, canonicalKey=user-reply-style, limit=100, top=3");
61
- expect(markdown).toContain("## Suggested Actions");
62
- expect(markdown).toContain("## Priority Clusters");
63
- expect(markdown).toContain("Cluster key:");
64
- expect(markdown).toContain("Advice:");
65
- });
66
-
67
- it("renders an empty priority-cluster section when nothing is open", () => {
68
- const summary = buildConflictAuditSummary([
69
- createConflictRecord({
70
- conflictId: "00000000-0000-0000-0000-000000000001",
71
- status: "kept-existing",
72
- resolvedAt: "2026-03-16T00:00:00.000Z",
73
- }) as any,
74
- ], 3);
75
-
76
- const markdown = formatConflictAuditMarkdown(summary, {
77
- generatedAt: "2026-03-16T16:00:00.000Z",
78
- limit: 50,
79
- top: 5,
80
- });
81
-
82
- expect(markdown).toContain("## Priority Clusters");
83
- expect(markdown).toContain("- None");
84
- });
85
- });