recallnest 2.2.0 → 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 (232) hide show
  1. package/.env +4 -0
  2. package/config.json +55 -0
  3. package/logs/daily-recall.error.log +0 -0
  4. package/logs/daily-recall.log +30 -0
  5. package/logs/ingest-2026-04-11.log +110 -0
  6. package/logs/ingest-2026-04-12.log +90 -0
  7. package/logs/launchagent-stderr.log +0 -0
  8. package/logs/launchagent-stdout.log +0 -0
  9. package/package.json +1 -1
  10. package/.claude-plugin/CLAUDE.md +0 -29
  11. package/.claude-plugin/marketplace.json +0 -18
  12. package/.dockerignore +0 -11
  13. package/.github/workflows/ci.yml +0 -28
  14. package/.mcp.json +0 -9
  15. package/CHANGELOG.md +0 -51
  16. package/DEMO.md +0 -94
  17. package/Dockerfile +0 -20
  18. package/EVAL.md +0 -89
  19. package/FAILURES.md +0 -75
  20. package/OPERATIONS.md +0 -59
  21. package/ROADMAP.md +0 -245
  22. package/SKILL.md +0 -250
  23. package/assets/dashboard.png +0 -0
  24. package/assets/knowledge-graph.png +0 -0
  25. package/assets/recallnest-workbench.png +0 -0
  26. package/assets/screenshots/ui-full.png +0 -0
  27. package/assets/screenshots/ui-overview.png +0 -0
  28. package/assets/screenshots/ui-search.png +0 -0
  29. package/assets/ui/app.js +0 -1031
  30. package/assets/ui/index.html +0 -224
  31. package/assets/ui/styles.css +0 -781
  32. package/assets/wechat-qr.jpg +0 -0
  33. package/bun.lock +0 -337
  34. package/daily-reflections/2026-03-05.md +0 -15
  35. package/daily-reflections/2026-03-06.md +0 -15
  36. package/daily-reflections/2026-03-07.md +0 -7
  37. package/daily-reflections/2026-03-08.md +0 -34
  38. package/daily-reflections/2026-03-09.md +0 -35
  39. package/daily-reflections/2026-03-10.md +0 -36
  40. package/daily-reflections/2026-03-11.md +0 -35
  41. package/daily-reflections/2026-03-12.md +0 -36
  42. package/daily-reflections/2026-03-13.md +0 -35
  43. package/daily-reflections/2026-03-14.md +0 -35
  44. package/daily-reflections/2026-03-15.md +0 -35
  45. package/daily-reflections/2026-03-16.md +0 -41
  46. package/daily-reflections/2026-03-17.md +0 -10
  47. package/daily-reflections/2026-03-18.md +0 -10
  48. package/daily-reflections/2026-03-19.md +0 -10
  49. package/daily-reflections/2026-03-20.md +0 -10
  50. package/daily-reflections/2026-03-21.md +0 -10
  51. package/daily-reflections/2026-03-22.md +0 -10
  52. package/daily-reflections/2026-03-23.md +0 -10
  53. package/daily-reflections/2026-03-24.md +0 -10
  54. package/daily-reflections/2026-03-25.md +0 -32
  55. package/daily-reflections/2026-03-26.md +0 -37
  56. package/daily-reflections/2026-03-27.md +0 -35
  57. package/daily-reflections/2026-03-28.md +0 -35
  58. package/daily-reflections/2026-03-29.md +0 -35
  59. package/daily-reflections/2026-03-30.md +0 -36
  60. package/daily-reflections/2026-03-31.md +0 -35
  61. package/daily-reflections/2026-04-01.md +0 -35
  62. package/daily-reflections/2026-04-02.md +0 -35
  63. package/daily-reflections/2026-04-03.md +0 -37
  64. package/daily-reflections/2026-04-04.md +0 -37
  65. package/daily-reflections/2026-04-05.md +0 -35
  66. package/daily-reflections/2026-04-06.md +0 -36
  67. package/daily-reflections/2026-04-07.md +0 -35
  68. package/daily-reflections/2026-04-08.md +0 -35
  69. package/daily-reflections/2026-04-12.md +0 -35
  70. package/data/alias-map.example.json +0 -22
  71. package/docs/api-reference.md +0 -1291
  72. package/docs/architecture.md +0 -168
  73. package/docs/memory-boundary-contract.md +0 -226
  74. package/docs/memory-categories.md +0 -119
  75. package/docs/pipeline-audit-ultramemory.md +0 -177
  76. package/docs/plans/2026-03-08-query-expansion.md +0 -270
  77. package/docs/plans/2026-03-16-continuity-layer.md +0 -196
  78. package/docs/plans/2026-04-01-recall-boost.md +0 -100
  79. package/docs/plans/mcp-tiering-plan.md +0 -126
  80. package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
  81. package/docs/self-evolution.md +0 -198
  82. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
  83. package/docs/superpowers/plans/2026-04-11-constructive-retrieval-emotion.md +0 -1195
  84. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
  85. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
  86. package/docs/superpowers/specs/2026-04-11-constructive-retrieval-emotion-design.md +0 -398
  87. package/eval/continuity/results-2026-03-16.md +0 -117
  88. package/eval/continuity/results-2026-03-19.md +0 -453
  89. package/eval/continuity/runbook.md +0 -110
  90. package/eval/continuity/seed-cases.md +0 -54
  91. package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
  92. package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
  93. package/eval/reports/2026-03-06-baseline.md +0 -82
  94. package/eval/reports/query-expansion.md +0 -138
  95. package/glama.json +0 -6
  96. package/lm +0 -19
  97. package/plan-philosophy-upgrades.md +0 -363
  98. package/plan-product-upgrades.md +0 -155
  99. package/plan-v1.2-distributable.md +0 -105
  100. package/plan.md +0 -140
  101. package/research-memory-evolution.md +0 -440
  102. package/research-rl-agent-memory.md +0 -330
  103. package/scripts/archive-distilled-junk.ts +0 -116
  104. package/scripts/backfill-anchors.ts +0 -130
  105. package/scripts/backfill-l0l1l2.ts +0 -98
  106. package/scripts/blind-scan.ts +0 -588
  107. package/scripts/cleanup-step1-exact-dups.ts +0 -117
  108. package/scripts/cleanup-step2-template-dups.ts +0 -113
  109. package/scripts/cleanup-step3-stale-content.ts +0 -149
  110. package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
  111. package/scripts/delete-distilled-junk.ts +0 -88
  112. package/scripts/diagnose-vector-search.ts +0 -132
  113. package/scripts/distill-facts.ts +0 -305
  114. package/scripts/health-check.ts +0 -343
  115. package/scripts/incremental-ingest.sh +0 -71
  116. package/scripts/migrate-language.ts +0 -75
  117. package/scripts/mlp-health-check.ts +0 -382
  118. package/scripts/pull-claude-desktop.ts +0 -515
  119. package/scripts/re-ingest-cc.sh +0 -66
  120. package/scripts/rebalance-memory-health.ts +0 -181
  121. package/scripts/run-desktop-ingest.sh +0 -11
  122. package/scripts/start-server.sh +0 -12
  123. package/scripts/verify-emotion-decay.ts +0 -148
  124. package/scripts/viz-dashboard.html +0 -423
  125. package/scripts/viz-export.ts +0 -703
  126. package/src/__tests__/access-tracker-novelty.test.ts +0 -228
  127. package/src/__tests__/activity-counter.test.ts +0 -96
  128. package/src/__tests__/admission-control.test.ts +0 -206
  129. package/src/__tests__/anchor-generator.test.ts +0 -67
  130. package/src/__tests__/auto-consolidation.test.ts +0 -168
  131. package/src/__tests__/auto-recall.test.ts +0 -131
  132. package/src/__tests__/babel-integration.test.ts +0 -139
  133. package/src/__tests__/batch-dedup.test.ts +0 -65
  134. package/src/__tests__/capture-engine.test.ts +0 -1144
  135. package/src/__tests__/capture-heuristic.test.ts +0 -171
  136. package/src/__tests__/cascade-forget.test.ts +0 -210
  137. package/src/__tests__/category-min-scores.test.ts +0 -33
  138. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  139. package/src/__tests__/circuit-breaker.test.ts +0 -99
  140. package/src/__tests__/cluster-consolidation.test.ts +0 -712
  141. package/src/__tests__/confidence-metadata.test.ts +0 -125
  142. package/src/__tests__/confidence-tracker.test.ts +0 -249
  143. package/src/__tests__/conflict-advisor.test.ts +0 -206
  144. package/src/__tests__/conflict-escalation.test.ts +0 -149
  145. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  146. package/src/__tests__/conflict-output.test.ts +0 -85
  147. package/src/__tests__/conflict-store.test.ts +0 -374
  148. package/src/__tests__/consolidation-engine.test.ts +0 -181
  149. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  150. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  151. package/src/__tests__/context-composer-pins.test.ts +0 -64
  152. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  153. package/src/__tests__/context-reconstructor.test.ts +0 -65
  154. package/src/__tests__/context-renderer.test.ts +0 -91
  155. package/src/__tests__/continuity-eval.test.ts +0 -275
  156. package/src/__tests__/continuity-schema.test.ts +0 -97
  157. package/src/__tests__/conversation-importer.test.ts +0 -692
  158. package/src/__tests__/core-recall-eval.test.ts +0 -235
  159. package/src/__tests__/core-summary.test.ts +0 -109
  160. package/src/__tests__/data-checkup.test.ts +0 -225
  161. package/src/__tests__/decay-exemption.test.ts +0 -84
  162. package/src/__tests__/dedup-actions.test.ts +0 -58
  163. package/src/__tests__/dedup-guard.test.ts +0 -39
  164. package/src/__tests__/deterministic-id.test.ts +0 -39
  165. package/src/__tests__/distill-lock.test.ts +0 -176
  166. package/src/__tests__/doctor.test.ts +0 -303
  167. package/src/__tests__/dream-pipeline.test.ts +0 -232
  168. package/src/__tests__/drill-down.test.ts +0 -108
  169. package/src/__tests__/embedder.test.ts +0 -122
  170. package/src/__tests__/emotion-decay.test.ts +0 -46
  171. package/src/__tests__/emotion-detector.test.ts +0 -56
  172. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  173. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  174. package/src/__tests__/entity-resolver.test.ts +0 -58
  175. package/src/__tests__/ethics-layer.test.ts +0 -476
  176. package/src/__tests__/event-segmenter.test.ts +0 -153
  177. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  178. package/src/__tests__/frequency-tracker.test.ts +0 -113
  179. package/src/__tests__/graph-export.test.ts +0 -489
  180. package/src/__tests__/hotness-blend.test.ts +0 -76
  181. package/src/__tests__/ingest.test.ts +0 -360
  182. package/src/__tests__/interference-detector.test.ts +0 -203
  183. package/src/__tests__/kg-extractor.test.ts +0 -184
  184. package/src/__tests__/kg-store.test.ts +0 -198
  185. package/src/__tests__/llm-consolidation.test.ts +0 -226
  186. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  187. package/src/__tests__/memory-boundaries.test.ts +0 -237
  188. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -243
  189. package/src/__tests__/memory-evolution.test.ts +0 -271
  190. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  191. package/src/__tests__/memory-lint.test.ts +0 -271
  192. package/src/__tests__/memory-output.test.ts +0 -215
  193. package/src/__tests__/multi-vector.test.ts +0 -242
  194. package/src/__tests__/narrative-architecture.test.ts +0 -456
  195. package/src/__tests__/pending-review.test.ts +0 -77
  196. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  197. package/src/__tests__/ppr-traversal.test.ts +0 -256
  198. package/src/__tests__/prediction-engine.test.ts +0 -349
  199. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  200. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  201. package/src/__tests__/preference-matcher.test.ts +0 -230
  202. package/src/__tests__/preference-slots.test.ts +0 -205
  203. package/src/__tests__/prospective-memory.test.ts +0 -283
  204. package/src/__tests__/query-entity-detector.test.ts +0 -171
  205. package/src/__tests__/query-expander-alias.test.ts +0 -89
  206. package/src/__tests__/query-expander.test.ts +0 -34
  207. package/src/__tests__/recall-governor.test.ts +0 -227
  208. package/src/__tests__/recall-mode.test.ts +0 -64
  209. package/src/__tests__/result-synthesizer.test.ts +0 -198
  210. package/src/__tests__/resume-context.test.ts +0 -4898
  211. package/src/__tests__/retrieval-trace.test.ts +0 -213
  212. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  213. package/src/__tests__/retroactive-boost.test.ts +0 -223
  214. package/src/__tests__/salience-scoring.test.ts +0 -106
  215. package/src/__tests__/scope-inventory.test.ts +0 -200
  216. package/src/__tests__/scope-policy.test.ts +0 -69
  217. package/src/__tests__/session-checkpoint.test.ts +0 -259
  218. package/src/__tests__/session-distiller.test.ts +0 -611
  219. package/src/__tests__/session-gc.test.ts +0 -215
  220. package/src/__tests__/skill-engine.test.ts +0 -546
  221. package/src/__tests__/skill-promotion.test.ts +0 -349
  222. package/src/__tests__/store-scope-audit.test.ts +0 -81
  223. package/src/__tests__/supersede-chain.test.ts +0 -154
  224. package/src/__tests__/temporal-parser.test.ts +0 -241
  225. package/src/__tests__/temporal-validity.test.ts +0 -160
  226. package/src/__tests__/term-registry.test.ts +0 -121
  227. package/src/__tests__/tool-output-compressor.test.ts +0 -133
  228. package/src/__tests__/topic-tag.test.ts +0 -113
  229. package/src/__tests__/trusted-memory.test.ts +0 -828
  230. package/src/__tests__/version-manager.test.ts +0 -213
  231. package/src/__tests__/workflow-observation.test.ts +0 -400
  232. package/src/__tests__/write-verifier.test.ts +0 -122
@@ -1,228 +0,0 @@
1
- /**
2
- * Tests for Tier 3.2: Conditional access reinforcement (prediction error gating).
3
- *
4
- * Validates that:
5
- * 1. High-similarity (low-novelty) results are NOT reinforced
6
- * 2. Low-similarity (high-novelty) results ARE reinforced
7
- * 3. Cooldown prevents duplicate reinforcement within the window
8
- * 4. When no scores provided, all entries are reinforced (backward compat)
9
- * 5. shouldReinforce() logic is correct in isolation
10
- */
11
- import { describe, expect, it, beforeEach } from "bun:test";
12
- import { AccessTracker, DEFAULT_ACCESS_TRACKER_CONFIG } from "../access-tracker.js";
13
- import type { MemoryEntry } from "../store.js";
14
-
15
- // ---------------------------------------------------------------------------
16
- // Helpers
17
- // ---------------------------------------------------------------------------
18
-
19
- function makeEntry(id: string, overrides: Partial<MemoryEntry> = {}): MemoryEntry {
20
- return {
21
- id,
22
- text: `memory-${id}`,
23
- vector: [1, 0, 0],
24
- category: "events",
25
- scope: "project:test",
26
- importance: 0.7,
27
- timestamp: Date.now(),
28
- metadata: JSON.stringify({ accessCount: 0, lastAccessedAt: 0 }),
29
- ...overrides,
30
- };
31
- }
32
-
33
- function createMockStore(entries: MemoryEntry[]) {
34
- const data = new Map(entries.map(e => [e.id, { ...e }]));
35
- const updates: Array<{ id: string; metadata?: string }> = [];
36
-
37
- return {
38
- updates,
39
- data,
40
- async getById(id: string) {
41
- return data.get(id) ?? null;
42
- },
43
- async update(id: string, upd: { metadata?: string }) {
44
- const entry = data.get(id);
45
- if (!entry) return null;
46
- if (upd.metadata) {
47
- entry.metadata = upd.metadata;
48
- updates.push({ id, metadata: upd.metadata });
49
- }
50
- return entry;
51
- },
52
- };
53
- }
54
-
55
- // ---------------------------------------------------------------------------
56
- // shouldReinforce() unit tests
57
- // ---------------------------------------------------------------------------
58
-
59
- describe("shouldReinforce", () => {
60
- let tracker: AccessTracker;
61
-
62
- beforeEach(() => {
63
- tracker = new AccessTracker(
64
- createMockStore([]) as any,
65
- { ...DEFAULT_ACCESS_TRACKER_CONFIG, noveltyThreshold: 0.35, cooldownMs: 300_000 },
66
- );
67
- });
68
-
69
- it("rejects high-similarity (low-novelty) results", () => {
70
- // similarity 0.90 → novelty 0.10 < threshold 0.35 → reject
71
- expect(tracker.shouldReinforce("a", 0.90)).toBe(false);
72
- });
73
-
74
- it("rejects exact match (similarity 1.0)", () => {
75
- expect(tracker.shouldReinforce("a", 1.0)).toBe(false);
76
- });
77
-
78
- it("accepts low-similarity (high-novelty) results", () => {
79
- // similarity 0.50 → novelty 0.50 > threshold 0.35 → accept
80
- expect(tracker.shouldReinforce("a", 0.50)).toBe(true);
81
- });
82
-
83
- it("accepts borderline novelty (exactly at threshold)", () => {
84
- // similarity 0.65 → novelty 0.35 = threshold 0.35 → accept (not strictly less)
85
- expect(tracker.shouldReinforce("a", 0.65)).toBe(true);
86
- });
87
-
88
- it("rejects same entry within cooldown window", () => {
89
- // First call: accepted
90
- expect(tracker.shouldReinforce("a", 0.50)).toBe(true);
91
- // Simulate recording to set cooldown
92
- tracker.recordAccess(["a"], [0.50]);
93
- // Second call within cooldown: rejected
94
- expect(tracker.shouldReinforce("a", 0.50)).toBe(false);
95
- });
96
-
97
- it("accepts different entry even if another is on cooldown", () => {
98
- tracker.recordAccess(["a"], [0.50]);
99
- expect(tracker.shouldReinforce("b", 0.50)).toBe(true);
100
- });
101
-
102
- it("bypasses novelty gate when threshold is 0", () => {
103
- const noGateTracker = new AccessTracker(
104
- createMockStore([]) as any,
105
- { ...DEFAULT_ACCESS_TRACKER_CONFIG, noveltyThreshold: 0, cooldownMs: 0 },
106
- );
107
- // Even high similarity passes
108
- expect(noGateTracker.shouldReinforce("a", 0.99)).toBe(true);
109
- });
110
- });
111
-
112
- // ---------------------------------------------------------------------------
113
- // recordAccess() with scores
114
- // ---------------------------------------------------------------------------
115
-
116
- describe("recordAccess with novelty gating", () => {
117
- it("only queues novel entries for flush", async () => {
118
- const entries = [makeEntry("a"), makeEntry("b"), makeEntry("c")];
119
- const store = createMockStore(entries);
120
- const tracker = new AccessTracker(store as any, {
121
- ...DEFAULT_ACCESS_TRACKER_CONFIG,
122
- flushIntervalMs: 0, // immediate flush for testing
123
- noveltyThreshold: 0.35,
124
- cooldownMs: 0,
125
- });
126
-
127
- // scores: a=0.90 (low novelty, reject), b=0.50 (novel, accept), c=0.40 (novel, accept)
128
- tracker.recordAccess(["a", "b", "c"], [0.90, 0.50, 0.40]);
129
-
130
- await tracker.flush();
131
-
132
- // Only b and c should have been updated
133
- const updatedIds = store.updates.map(u => u.id);
134
- expect(updatedIds).not.toContain("a");
135
- expect(updatedIds).toContain("b");
136
- expect(updatedIds).toContain("c");
137
- });
138
-
139
- it("reinforces all entries when no scores provided (backward compat)", async () => {
140
- const entries = [makeEntry("a"), makeEntry("b")];
141
- const store = createMockStore(entries);
142
- const tracker = new AccessTracker(store as any, {
143
- ...DEFAULT_ACCESS_TRACKER_CONFIG,
144
- flushIntervalMs: 0,
145
- noveltyThreshold: 0.35,
146
- cooldownMs: 0,
147
- });
148
-
149
- // No scores → all reinforced
150
- tracker.recordAccess(["a", "b"]);
151
- await tracker.flush();
152
-
153
- const updatedIds = store.updates.map(u => u.id);
154
- expect(updatedIds).toContain("a");
155
- expect(updatedIds).toContain("b");
156
- });
157
-
158
- it("respects cooldown across multiple recordAccess calls", async () => {
159
- const entries = [makeEntry("x")];
160
- const store = createMockStore(entries);
161
- const tracker = new AccessTracker(store as any, {
162
- ...DEFAULT_ACCESS_TRACKER_CONFIG,
163
- flushIntervalMs: 0,
164
- noveltyThreshold: 0.35,
165
- cooldownMs: 600_000, // 10 min cooldown
166
- });
167
-
168
- // First call: novel enough → queued
169
- tracker.recordAccess(["x"], [0.50]);
170
- await tracker.flush();
171
- expect(store.updates.length).toBe(1);
172
-
173
- // Second call: same entry within cooldown → not queued
174
- tracker.recordAccess(["x"], [0.50]);
175
- await tracker.flush();
176
- // Still only 1 update (no new flush)
177
- expect(store.updates.length).toBe(1);
178
- });
179
-
180
- it("skips flush scheduling when all entries are gated out", () => {
181
- const store = createMockStore([]);
182
- const tracker = new AccessTracker(store as any, {
183
- ...DEFAULT_ACCESS_TRACKER_CONFIG,
184
- flushIntervalMs: 5000,
185
- noveltyThreshold: 0.35,
186
- cooldownMs: 0,
187
- });
188
-
189
- // All scores too high (low novelty) → nothing queued
190
- tracker.recordAccess(["a", "b"], [0.95, 0.90]);
191
- expect(tracker.pendingCount).toBe(0);
192
- });
193
-
194
- it("accessCount increments correctly after novelty-gated flush", async () => {
195
- const entries = [makeEntry("m1")];
196
- const store = createMockStore(entries);
197
- const tracker = new AccessTracker(store as any, {
198
- ...DEFAULT_ACCESS_TRACKER_CONFIG,
199
- flushIntervalMs: 0,
200
- noveltyThreshold: 0.35,
201
- cooldownMs: 0,
202
- });
203
-
204
- tracker.recordAccess(["m1"], [0.40]); // novel → accepted
205
- await tracker.flush();
206
-
207
- const updated = store.data.get("m1")!;
208
- const meta = JSON.parse(updated.metadata);
209
- expect(meta.accessCount).toBe(1);
210
- });
211
- });
212
-
213
- // ---------------------------------------------------------------------------
214
- // destroy() cleans cooldown
215
- // ---------------------------------------------------------------------------
216
-
217
- describe("destroy cleans cooldown", () => {
218
- it("clears cooldown map on destroy", () => {
219
- const tracker = new AccessTracker(
220
- createMockStore([]) as any,
221
- { ...DEFAULT_ACCESS_TRACKER_CONFIG, cooldownMs: 600_000 },
222
- );
223
- tracker.recordAccess(["a"], [0.40]);
224
- tracker.destroy();
225
- // After destroy, same entry should be reinforceable again
226
- expect(tracker.shouldReinforce("a", 0.40)).toBe(true);
227
- });
228
- });
@@ -1,96 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "bun:test";
2
- import { join } from "node:path";
3
- import { mkdirSync, rmSync, existsSync } from "node:fs";
4
- import {
5
- incrementWriteCount,
6
- getWriteCount,
7
- resetWriteCount,
8
- getDistillTier,
9
- type ActivityCounterConfig,
10
- } from "../activity-counter.js";
11
-
12
- const TMP_DIR = join(import.meta.dir, "../../.tmp-activity-test");
13
- const testConfig: Partial<ActivityCounterConfig> = {
14
- statsPath: join(TMP_DIR, "activity-stats.json"),
15
- lightThreshold: 3,
16
- standardThreshold: 10,
17
- deepThreshold: 20,
18
- };
19
-
20
- describe("activity-counter (HP-3)", () => {
21
- beforeEach(() => {
22
- mkdirSync(TMP_DIR, { recursive: true });
23
- // Ensure clean state
24
- const p = testConfig.statsPath!;
25
- if (existsSync(p)) rmSync(p);
26
- });
27
-
28
- afterEach(() => {
29
- rmSync(TMP_DIR, { recursive: true, force: true });
30
- });
31
-
32
- describe("incrementWriteCount", () => {
33
- it("starts at 0 and increments by 1", () => {
34
- expect(getWriteCount(testConfig)).toBe(0);
35
- expect(incrementWriteCount(1, testConfig)).toBe(1);
36
- expect(incrementWriteCount(1, testConfig)).toBe(2);
37
- expect(getWriteCount(testConfig)).toBe(2);
38
- });
39
-
40
- it("increments by arbitrary n", () => {
41
- incrementWriteCount(5, testConfig);
42
- expect(getWriteCount(testConfig)).toBe(5);
43
- incrementWriteCount(3, testConfig);
44
- expect(getWriteCount(testConfig)).toBe(8);
45
- });
46
- });
47
-
48
- describe("resetWriteCount", () => {
49
- it("resets count to 0", () => {
50
- incrementWriteCount(7, testConfig);
51
- expect(getWriteCount(testConfig)).toBe(7);
52
- resetWriteCount(testConfig);
53
- expect(getWriteCount(testConfig)).toBe(0);
54
- });
55
- });
56
-
57
- describe("getDistillTier", () => {
58
- it("returns 'none' when below light threshold", () => {
59
- incrementWriteCount(2, testConfig);
60
- expect(getDistillTier(testConfig)).toBe("none");
61
- });
62
-
63
- it("returns 'light' at light threshold", () => {
64
- incrementWriteCount(3, testConfig);
65
- expect(getDistillTier(testConfig)).toBe("light");
66
- });
67
-
68
- it("returns 'standard' at standard threshold", () => {
69
- incrementWriteCount(10, testConfig);
70
- expect(getDistillTier(testConfig)).toBe("standard");
71
- });
72
-
73
- it("returns 'deep' at deep threshold", () => {
74
- incrementWriteCount(20, testConfig);
75
- expect(getDistillTier(testConfig)).toBe("deep");
76
- });
77
-
78
- it("returns 'deep' well above deep threshold", () => {
79
- incrementWriteCount(100, testConfig);
80
- expect(getDistillTier(testConfig)).toBe("deep");
81
- });
82
- });
83
-
84
- describe("resilience", () => {
85
- it("handles missing stats file gracefully", () => {
86
- expect(getWriteCount(testConfig)).toBe(0);
87
- expect(getDistillTier(testConfig)).toBe("none");
88
- });
89
-
90
- it("handles corrupt stats file gracefully", () => {
91
- const { writeFileSync } = require("node:fs");
92
- writeFileSync(testConfig.statsPath!, "not-json{{{");
93
- expect(getWriteCount(testConfig)).toBe(0);
94
- });
95
- });
96
- });
@@ -1,206 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import {
4
- checkAdmission,
5
- resolveAdmissionConfig,
6
- ScopeRateLimiter,
7
- } from "../admission-control.js";
8
-
9
- // ---------------------------------------------------------------------------
10
- // resolveAdmissionConfig
11
- // ---------------------------------------------------------------------------
12
-
13
- describe("resolveAdmissionConfig", () => {
14
- it("returns defaults when no overrides", () => {
15
- const cfg = resolveAdmissionConfig();
16
- expect(cfg.minTextLength).toBe(10);
17
- expect(cfg.noiseFilterEnabled).toBe(true);
18
- expect(cfg.minImportance).toBe(0.2);
19
- expect(cfg.maxWritesPerScope).toBe(50);
20
- expect(cfg.rateLimitWindowMs).toBe(60_000);
21
- });
22
-
23
- it("merges partial overrides", () => {
24
- const cfg = resolveAdmissionConfig({ minImportance: 0.5 });
25
- expect(cfg.minImportance).toBe(0.5);
26
- expect(cfg.minTextLength).toBe(10); // unchanged
27
- });
28
- });
29
-
30
- // ---------------------------------------------------------------------------
31
- // checkAdmission — text length
32
- // ---------------------------------------------------------------------------
33
-
34
- describe("checkAdmission — text length", () => {
35
- it("rejects text shorter than minTextLength", () => {
36
- const result = checkAdmission("short", 0.8, "project:test");
37
- expect(result.verdict).toBe("rejected");
38
- expect(result.reason).toBe("text_too_short");
39
- });
40
-
41
- it("rejects empty text", () => {
42
- const result = checkAdmission("", 0.8, "project:test");
43
- expect(result.verdict).toBe("rejected");
44
- expect(result.reason).toBe("text_too_short");
45
- });
46
-
47
- it("rejects whitespace-only text", () => {
48
- const result = checkAdmission(" ", 0.8, "project:test");
49
- expect(result.verdict).toBe("rejected");
50
- expect(result.reason).toBe("text_too_short");
51
- });
52
-
53
- it("accepts text at exactly minTextLength", () => {
54
- const result = checkAdmission("abcdefghij", 0.8, "project:test");
55
- expect(result.verdict).toBe("accepted");
56
- });
57
-
58
- it("respects custom minTextLength", () => {
59
- const result = checkAdmission("hello world", 0.8, "project:test", undefined, { minTextLength: 20 });
60
- expect(result.verdict).toBe("rejected");
61
- expect(result.reason).toBe("text_too_short");
62
- });
63
- });
64
-
65
- // ---------------------------------------------------------------------------
66
- // checkAdmission — noise filter
67
- // ---------------------------------------------------------------------------
68
-
69
- describe("checkAdmission — noise filter", () => {
70
- it("rejects denial patterns", () => {
71
- const result = checkAdmission("I don't have any information about that topic", 0.8, "project:test");
72
- expect(result.verdict).toBe("rejected");
73
- expect(result.reason).toBe("noise_detected");
74
- });
75
-
76
- it("rejects boilerplate greetings", () => {
77
- const result = checkAdmission("hello there!", 0.8, "project:test");
78
- expect(result.verdict).toBe("rejected");
79
- expect(result.reason).toBe("noise_detected");
80
- });
81
-
82
- it("can be disabled via config", () => {
83
- const result = checkAdmission(
84
- "I don't have any information about that topic",
85
- 0.8,
86
- "project:test",
87
- undefined,
88
- { noiseFilterEnabled: false },
89
- );
90
- expect(result.verdict).toBe("accepted");
91
- });
92
- });
93
-
94
- // ---------------------------------------------------------------------------
95
- // checkAdmission — importance floor
96
- // ---------------------------------------------------------------------------
97
-
98
- describe("checkAdmission — importance floor", () => {
99
- it("rejects importance below default threshold (0.2)", () => {
100
- const result = checkAdmission("This is a valid memory text", 0.1, "project:test");
101
- expect(result.verdict).toBe("rejected");
102
- expect(result.reason).toBe("importance_too_low");
103
- });
104
-
105
- it("accepts importance at exactly the threshold", () => {
106
- const result = checkAdmission("This is a valid memory text", 0.2, "project:test");
107
- expect(result.verdict).toBe("accepted");
108
- });
109
-
110
- it("accepts importance above threshold", () => {
111
- const result = checkAdmission("This is a valid memory text", 0.8, "project:test");
112
- expect(result.verdict).toBe("accepted");
113
- });
114
-
115
- it("respects custom minImportance", () => {
116
- const result = checkAdmission("This is a valid memory text", 0.4, "project:test", undefined, { minImportance: 0.5 });
117
- expect(result.verdict).toBe("rejected");
118
- expect(result.reason).toBe("importance_too_low");
119
- });
120
- });
121
-
122
- // ---------------------------------------------------------------------------
123
- // ScopeRateLimiter
124
- // ---------------------------------------------------------------------------
125
-
126
- describe("ScopeRateLimiter", () => {
127
- it("allows writes within limit", () => {
128
- const limiter = new ScopeRateLimiter();
129
- const cfg = resolveAdmissionConfig({ maxWritesPerScope: 3 });
130
- expect(limiter.check("s1", cfg)).toBe(true);
131
- expect(limiter.check("s1", cfg)).toBe(true);
132
- expect(limiter.check("s1", cfg)).toBe(true);
133
- });
134
-
135
- it("blocks writes exceeding limit", () => {
136
- const limiter = new ScopeRateLimiter();
137
- const cfg = resolveAdmissionConfig({ maxWritesPerScope: 2 });
138
- expect(limiter.check("s1", cfg)).toBe(true);
139
- expect(limiter.check("s1", cfg)).toBe(true);
140
- expect(limiter.check("s1", cfg)).toBe(false); // 3rd blocked
141
- });
142
-
143
- it("tracks scopes independently", () => {
144
- const limiter = new ScopeRateLimiter();
145
- const cfg = resolveAdmissionConfig({ maxWritesPerScope: 1 });
146
- expect(limiter.check("s1", cfg)).toBe(true);
147
- expect(limiter.check("s2", cfg)).toBe(true);
148
- expect(limiter.check("s1", cfg)).toBe(false); // s1 exhausted
149
- expect(limiter.check("s2", cfg)).toBe(false); // s2 exhausted
150
- });
151
-
152
- it("reports correct size", () => {
153
- const limiter = new ScopeRateLimiter();
154
- const cfg = resolveAdmissionConfig();
155
- limiter.check("a", cfg);
156
- limiter.check("b", cfg);
157
- expect(limiter.size).toBe(2);
158
- });
159
-
160
- it("clears all state", () => {
161
- const limiter = new ScopeRateLimiter();
162
- const cfg = resolveAdmissionConfig();
163
- limiter.check("a", cfg);
164
- limiter.clear();
165
- expect(limiter.size).toBe(0);
166
- });
167
- });
168
-
169
- // ---------------------------------------------------------------------------
170
- // checkAdmission — rate limiting
171
- // ---------------------------------------------------------------------------
172
-
173
- describe("checkAdmission — rate limiting", () => {
174
- it("rejects when rate limit exceeded", () => {
175
- const limiter = new ScopeRateLimiter();
176
- const cfg = { maxWritesPerScope: 2 } as const;
177
- // First two pass
178
- checkAdmission("valid memory text here", 0.8, "project:test", limiter, cfg);
179
- checkAdmission("another valid memory text", 0.8, "project:test", limiter, cfg);
180
- // Third is rate limited
181
- const result = checkAdmission("yet another valid text", 0.8, "project:test", limiter, cfg);
182
- expect(result.verdict).toBe("rejected");
183
- expect(result.reason).toBe("rate_limited");
184
- });
185
-
186
- it("skips rate limiting when no limiter provided", () => {
187
- const result = checkAdmission("valid memory text here", 0.8, "project:test");
188
- expect(result.verdict).toBe("accepted");
189
- });
190
- });
191
-
192
- // ---------------------------------------------------------------------------
193
- // checkAdmission — happy path
194
- // ---------------------------------------------------------------------------
195
-
196
- describe("checkAdmission — happy path", () => {
197
- it("accepts valid memory with all checks passing", () => {
198
- const result = checkAdmission(
199
- "User prefers dark mode in all applications",
200
- 0.8,
201
- "project:test",
202
- );
203
- expect(result.verdict).toBe("accepted");
204
- expect(result.reason).toBeUndefined();
205
- });
206
- });
@@ -1,67 +0,0 @@
1
- import { describe, it, expect } from "bun:test";
2
- import { generateAnchor } from "../anchor-generator.js";
3
-
4
- describe("generateAnchor", () => {
5
- it("returns undefined for short text (≤80 chars)", () => {
6
- expect(generateAnchor("轮巡仓库每日检查")).toBeUndefined();
7
- expect(generateAnchor("Short text that fits in 80 chars easily")).toBeUndefined();
8
- });
9
-
10
- it("extracts first sentence from long text", () => {
11
- const text = "每天轮巡4个win4r仓库,检查Issue和PR。" +
12
- "包括 ClawTeam-OpenClaw、openclaw-a2a-gateway、memory-lancedb-pro、UltraMemory。" +
13
- "Issue 用中文回复,PR 用英文 review。不要重复回复已处理的。";
14
- const anchor = generateAnchor(text);
15
- expect(anchor).toBeDefined();
16
- expect(anchor!.length).toBeLessThanOrEqual(80);
17
- expect(anchor).toBe("每天轮巡4个win4r仓库,检查Issue和PR");
18
- });
19
-
20
- it("uses workflow pattern title from metadata", () => {
21
- const text = "A very long workflow pattern description that spans multiple lines and paragraphs explaining the full procedure";
22
- const metadata = {
23
- workflowPattern: {
24
- title: "Daily repo patrol",
25
- trigger: "Every morning",
26
- },
27
- };
28
- const anchor = generateAnchor(text, metadata);
29
- expect(anchor).toBe("Daily repo patrol");
30
- });
31
-
32
- it("uses case memory title from metadata", () => {
33
- const text = "A very long case description that spans multiple lines and paragraphs explaining the full case and its resolution";
34
- const metadata = {
35
- caseMemory: {
36
- title: "Fix dedup over-kill in ingest",
37
- problem: "72% of chunks were being dropped",
38
- },
39
- };
40
- const anchor = generateAnchor(text, metadata);
41
- expect(anchor).toBe("Fix dedup over-kill in ingest");
42
- });
43
-
44
- it("truncates long first sentences at CJK char boundary", () => {
45
- const text = "这是一个非常非常非常非常非常非常非常非常非常非常非常非常非常非常长的第一句话," +
46
- "超过了八十个字符的限制需要被截断到合适的长度。" +
47
- "第二句话在这里提供更多上下文信息用于测试截断功能是否正常工作。";
48
- const anchor = generateAnchor(text);
49
- expect(anchor).toBeDefined();
50
- expect(anchor!.length).toBeLessThanOrEqual(80);
51
- });
52
-
53
- it("truncates long English sentences at word boundary", () => {
54
- const text = "This is a very long first sentence that definitely exceeds the eighty character limit by quite a significant margin. Second sentence here.";
55
- const anchor = generateAnchor(text);
56
- expect(anchor).toBeDefined();
57
- expect(anchor!.length).toBeLessThanOrEqual(80);
58
- // Should not end mid-word
59
- expect(anchor!).not.toMatch(/\s$/);
60
- });
61
-
62
- it("handles text with only newline separators", () => {
63
- const text = "First line of important information that is a reasonable sentence\nSecond line with more details and context that expands on the first point";
64
- const anchor = generateAnchor(text);
65
- expect(anchor).toBe("First line of important information that is a reasonable sentence");
66
- });
67
- });