recallnest 2.2.0 → 2.3.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 (249) hide show
  1. package/.env +4 -0
  2. package/CLAUDE.md +1 -1
  3. package/README.md +30 -5
  4. package/README_CN.md +30 -5
  5. package/config.json +55 -0
  6. package/connectors/examples/email-adapter.ts +73 -0
  7. package/connectors/examples/logs-adapter.ts +103 -0
  8. package/connectors/examples/rss-adapter.ts +95 -0
  9. package/logs/daily-recall.error.log +0 -0
  10. package/logs/daily-recall.log +60 -0
  11. package/logs/ingest-2026-04-11.log +110 -0
  12. package/logs/ingest-2026-04-12.log +90 -0
  13. package/logs/ingest-2026-04-13.log +87 -0
  14. package/logs/launchagent-stderr.log +0 -0
  15. package/logs/launchagent-stdout.log +0 -0
  16. package/package.json +1 -1
  17. package/src/cli.ts +30 -0
  18. package/src/connector-types.ts +57 -0
  19. package/src/conversation-importer.ts +25 -1
  20. package/src/data-checkup.ts +31 -1
  21. package/src/doctor.ts +29 -0
  22. package/src/ingest.ts +203 -0
  23. package/src/mcp-server.ts +2 -2
  24. package/src/obsidian-connector.ts +282 -0
  25. package/src/session-schema.ts +2 -2
  26. package/src/source-heartbeat.ts +108 -0
  27. package/.claude-plugin/CLAUDE.md +0 -29
  28. package/.claude-plugin/marketplace.json +0 -18
  29. package/.dockerignore +0 -11
  30. package/.github/workflows/ci.yml +0 -28
  31. package/.mcp.json +0 -9
  32. package/CHANGELOG.md +0 -51
  33. package/DEMO.md +0 -94
  34. package/Dockerfile +0 -20
  35. package/EVAL.md +0 -89
  36. package/FAILURES.md +0 -75
  37. package/OPERATIONS.md +0 -59
  38. package/ROADMAP.md +0 -245
  39. package/SKILL.md +0 -250
  40. package/assets/dashboard.png +0 -0
  41. package/assets/knowledge-graph.png +0 -0
  42. package/assets/recallnest-workbench.png +0 -0
  43. package/assets/screenshots/ui-full.png +0 -0
  44. package/assets/screenshots/ui-overview.png +0 -0
  45. package/assets/screenshots/ui-search.png +0 -0
  46. package/assets/ui/app.js +0 -1031
  47. package/assets/ui/index.html +0 -224
  48. package/assets/ui/styles.css +0 -781
  49. package/assets/wechat-qr.jpg +0 -0
  50. package/bun.lock +0 -337
  51. package/daily-reflections/2026-03-05.md +0 -15
  52. package/daily-reflections/2026-03-06.md +0 -15
  53. package/daily-reflections/2026-03-07.md +0 -7
  54. package/daily-reflections/2026-03-08.md +0 -34
  55. package/daily-reflections/2026-03-09.md +0 -35
  56. package/daily-reflections/2026-03-10.md +0 -36
  57. package/daily-reflections/2026-03-11.md +0 -35
  58. package/daily-reflections/2026-03-12.md +0 -36
  59. package/daily-reflections/2026-03-13.md +0 -35
  60. package/daily-reflections/2026-03-14.md +0 -35
  61. package/daily-reflections/2026-03-15.md +0 -35
  62. package/daily-reflections/2026-03-16.md +0 -41
  63. package/daily-reflections/2026-03-17.md +0 -10
  64. package/daily-reflections/2026-03-18.md +0 -10
  65. package/daily-reflections/2026-03-19.md +0 -10
  66. package/daily-reflections/2026-03-20.md +0 -10
  67. package/daily-reflections/2026-03-21.md +0 -10
  68. package/daily-reflections/2026-03-22.md +0 -10
  69. package/daily-reflections/2026-03-23.md +0 -10
  70. package/daily-reflections/2026-03-24.md +0 -10
  71. package/daily-reflections/2026-03-25.md +0 -32
  72. package/daily-reflections/2026-03-26.md +0 -37
  73. package/daily-reflections/2026-03-27.md +0 -35
  74. package/daily-reflections/2026-03-28.md +0 -35
  75. package/daily-reflections/2026-03-29.md +0 -35
  76. package/daily-reflections/2026-03-30.md +0 -36
  77. package/daily-reflections/2026-03-31.md +0 -35
  78. package/daily-reflections/2026-04-01.md +0 -35
  79. package/daily-reflections/2026-04-02.md +0 -35
  80. package/daily-reflections/2026-04-03.md +0 -37
  81. package/daily-reflections/2026-04-04.md +0 -37
  82. package/daily-reflections/2026-04-05.md +0 -35
  83. package/daily-reflections/2026-04-06.md +0 -36
  84. package/daily-reflections/2026-04-07.md +0 -35
  85. package/daily-reflections/2026-04-08.md +0 -35
  86. package/daily-reflections/2026-04-12.md +0 -35
  87. package/data/alias-map.example.json +0 -22
  88. package/docs/api-reference.md +0 -1291
  89. package/docs/architecture.md +0 -168
  90. package/docs/memory-boundary-contract.md +0 -226
  91. package/docs/memory-categories.md +0 -119
  92. package/docs/pipeline-audit-ultramemory.md +0 -177
  93. package/docs/plans/2026-03-08-query-expansion.md +0 -270
  94. package/docs/plans/2026-03-16-continuity-layer.md +0 -196
  95. package/docs/plans/2026-04-01-recall-boost.md +0 -100
  96. package/docs/plans/mcp-tiering-plan.md +0 -126
  97. package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
  98. package/docs/self-evolution.md +0 -198
  99. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
  100. package/docs/superpowers/plans/2026-04-11-constructive-retrieval-emotion.md +0 -1195
  101. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
  102. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
  103. package/docs/superpowers/specs/2026-04-11-constructive-retrieval-emotion-design.md +0 -398
  104. package/eval/continuity/results-2026-03-16.md +0 -117
  105. package/eval/continuity/results-2026-03-19.md +0 -453
  106. package/eval/continuity/runbook.md +0 -110
  107. package/eval/continuity/seed-cases.md +0 -54
  108. package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
  109. package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
  110. package/eval/reports/2026-03-06-baseline.md +0 -82
  111. package/eval/reports/query-expansion.md +0 -138
  112. package/glama.json +0 -6
  113. package/lm +0 -19
  114. package/plan-philosophy-upgrades.md +0 -363
  115. package/plan-product-upgrades.md +0 -155
  116. package/plan-v1.2-distributable.md +0 -105
  117. package/plan.md +0 -140
  118. package/research-memory-evolution.md +0 -440
  119. package/research-rl-agent-memory.md +0 -330
  120. package/scripts/archive-distilled-junk.ts +0 -116
  121. package/scripts/backfill-anchors.ts +0 -130
  122. package/scripts/backfill-l0l1l2.ts +0 -98
  123. package/scripts/blind-scan.ts +0 -588
  124. package/scripts/cleanup-step1-exact-dups.ts +0 -117
  125. package/scripts/cleanup-step2-template-dups.ts +0 -113
  126. package/scripts/cleanup-step3-stale-content.ts +0 -149
  127. package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
  128. package/scripts/delete-distilled-junk.ts +0 -88
  129. package/scripts/diagnose-vector-search.ts +0 -132
  130. package/scripts/distill-facts.ts +0 -305
  131. package/scripts/health-check.ts +0 -343
  132. package/scripts/incremental-ingest.sh +0 -71
  133. package/scripts/migrate-language.ts +0 -75
  134. package/scripts/mlp-health-check.ts +0 -382
  135. package/scripts/pull-claude-desktop.ts +0 -515
  136. package/scripts/re-ingest-cc.sh +0 -66
  137. package/scripts/rebalance-memory-health.ts +0 -181
  138. package/scripts/run-desktop-ingest.sh +0 -11
  139. package/scripts/start-server.sh +0 -12
  140. package/scripts/verify-emotion-decay.ts +0 -148
  141. package/scripts/viz-dashboard.html +0 -423
  142. package/scripts/viz-export.ts +0 -703
  143. package/src/__tests__/access-tracker-novelty.test.ts +0 -228
  144. package/src/__tests__/activity-counter.test.ts +0 -96
  145. package/src/__tests__/admission-control.test.ts +0 -206
  146. package/src/__tests__/anchor-generator.test.ts +0 -67
  147. package/src/__tests__/auto-consolidation.test.ts +0 -168
  148. package/src/__tests__/auto-recall.test.ts +0 -131
  149. package/src/__tests__/babel-integration.test.ts +0 -139
  150. package/src/__tests__/batch-dedup.test.ts +0 -65
  151. package/src/__tests__/capture-engine.test.ts +0 -1144
  152. package/src/__tests__/capture-heuristic.test.ts +0 -171
  153. package/src/__tests__/cascade-forget.test.ts +0 -210
  154. package/src/__tests__/category-min-scores.test.ts +0 -33
  155. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  156. package/src/__tests__/circuit-breaker.test.ts +0 -99
  157. package/src/__tests__/cluster-consolidation.test.ts +0 -712
  158. package/src/__tests__/confidence-metadata.test.ts +0 -125
  159. package/src/__tests__/confidence-tracker.test.ts +0 -249
  160. package/src/__tests__/conflict-advisor.test.ts +0 -206
  161. package/src/__tests__/conflict-escalation.test.ts +0 -149
  162. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  163. package/src/__tests__/conflict-output.test.ts +0 -85
  164. package/src/__tests__/conflict-store.test.ts +0 -374
  165. package/src/__tests__/consolidation-engine.test.ts +0 -181
  166. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  167. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  168. package/src/__tests__/context-composer-pins.test.ts +0 -64
  169. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  170. package/src/__tests__/context-reconstructor.test.ts +0 -65
  171. package/src/__tests__/context-renderer.test.ts +0 -91
  172. package/src/__tests__/continuity-eval.test.ts +0 -275
  173. package/src/__tests__/continuity-schema.test.ts +0 -97
  174. package/src/__tests__/conversation-importer.test.ts +0 -692
  175. package/src/__tests__/core-recall-eval.test.ts +0 -235
  176. package/src/__tests__/core-summary.test.ts +0 -109
  177. package/src/__tests__/data-checkup.test.ts +0 -225
  178. package/src/__tests__/decay-exemption.test.ts +0 -84
  179. package/src/__tests__/dedup-actions.test.ts +0 -58
  180. package/src/__tests__/dedup-guard.test.ts +0 -39
  181. package/src/__tests__/deterministic-id.test.ts +0 -39
  182. package/src/__tests__/distill-lock.test.ts +0 -176
  183. package/src/__tests__/doctor.test.ts +0 -303
  184. package/src/__tests__/dream-pipeline.test.ts +0 -232
  185. package/src/__tests__/drill-down.test.ts +0 -108
  186. package/src/__tests__/embedder.test.ts +0 -122
  187. package/src/__tests__/emotion-decay.test.ts +0 -46
  188. package/src/__tests__/emotion-detector.test.ts +0 -56
  189. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  190. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  191. package/src/__tests__/entity-resolver.test.ts +0 -58
  192. package/src/__tests__/ethics-layer.test.ts +0 -476
  193. package/src/__tests__/event-segmenter.test.ts +0 -153
  194. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  195. package/src/__tests__/frequency-tracker.test.ts +0 -113
  196. package/src/__tests__/graph-export.test.ts +0 -489
  197. package/src/__tests__/hotness-blend.test.ts +0 -76
  198. package/src/__tests__/ingest.test.ts +0 -360
  199. package/src/__tests__/interference-detector.test.ts +0 -203
  200. package/src/__tests__/kg-extractor.test.ts +0 -184
  201. package/src/__tests__/kg-store.test.ts +0 -198
  202. package/src/__tests__/llm-consolidation.test.ts +0 -226
  203. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  204. package/src/__tests__/memory-boundaries.test.ts +0 -237
  205. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -243
  206. package/src/__tests__/memory-evolution.test.ts +0 -271
  207. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  208. package/src/__tests__/memory-lint.test.ts +0 -271
  209. package/src/__tests__/memory-output.test.ts +0 -215
  210. package/src/__tests__/multi-vector.test.ts +0 -242
  211. package/src/__tests__/narrative-architecture.test.ts +0 -456
  212. package/src/__tests__/pending-review.test.ts +0 -77
  213. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  214. package/src/__tests__/ppr-traversal.test.ts +0 -256
  215. package/src/__tests__/prediction-engine.test.ts +0 -349
  216. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  217. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  218. package/src/__tests__/preference-matcher.test.ts +0 -230
  219. package/src/__tests__/preference-slots.test.ts +0 -205
  220. package/src/__tests__/prospective-memory.test.ts +0 -283
  221. package/src/__tests__/query-entity-detector.test.ts +0 -171
  222. package/src/__tests__/query-expander-alias.test.ts +0 -89
  223. package/src/__tests__/query-expander.test.ts +0 -34
  224. package/src/__tests__/recall-governor.test.ts +0 -227
  225. package/src/__tests__/recall-mode.test.ts +0 -64
  226. package/src/__tests__/result-synthesizer.test.ts +0 -198
  227. package/src/__tests__/resume-context.test.ts +0 -4898
  228. package/src/__tests__/retrieval-trace.test.ts +0 -213
  229. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  230. package/src/__tests__/retroactive-boost.test.ts +0 -223
  231. package/src/__tests__/salience-scoring.test.ts +0 -106
  232. package/src/__tests__/scope-inventory.test.ts +0 -200
  233. package/src/__tests__/scope-policy.test.ts +0 -69
  234. package/src/__tests__/session-checkpoint.test.ts +0 -259
  235. package/src/__tests__/session-distiller.test.ts +0 -611
  236. package/src/__tests__/session-gc.test.ts +0 -215
  237. package/src/__tests__/skill-engine.test.ts +0 -546
  238. package/src/__tests__/skill-promotion.test.ts +0 -349
  239. package/src/__tests__/store-scope-audit.test.ts +0 -81
  240. package/src/__tests__/supersede-chain.test.ts +0 -154
  241. package/src/__tests__/temporal-parser.test.ts +0 -241
  242. package/src/__tests__/temporal-validity.test.ts +0 -160
  243. package/src/__tests__/term-registry.test.ts +0 -121
  244. package/src/__tests__/tool-output-compressor.test.ts +0 -133
  245. package/src/__tests__/topic-tag.test.ts +0 -113
  246. package/src/__tests__/trusted-memory.test.ts +0 -828
  247. package/src/__tests__/version-manager.test.ts +0 -213
  248. package/src/__tests__/workflow-observation.test.ts +0 -400
  249. package/src/__tests__/write-verifier.test.ts +0 -122
@@ -1,230 +0,0 @@
1
- /**
2
- * Tests for Tier 3.6: Preference Pattern Quick Matching
3
- *
4
- * Validates:
5
- * 1. matchPreference returns "create" when no similar preferences exist
6
- * 2. matchPreference returns "skip" for near-duplicate preferences (no LLM)
7
- * 3. matchPreference uses LLM when available for decision
8
- * 4. matchPreference returns "merge" with merged text from LLM
9
- * 5. matchPreference handles LLM failure gracefully
10
- * 6. applyPreferenceMatch handles merge and skip correctly
11
- * 7. Non-preference categories are not filtered
12
- */
13
- import { describe, expect, it } from "bun:test";
14
- import {
15
- matchPreference,
16
- applyPreferenceMatch,
17
- DEFAULT_PREFERENCE_MATCHER_CONFIG,
18
- } from "../preference-matcher.js";
19
- import type { MemoryEntry, MemorySearchResult } from "../store.js";
20
- import type { LLMClient, DedupDecision } from "../llm-client.js";
21
-
22
- // ---------------------------------------------------------------------------
23
- // Helpers
24
- // ---------------------------------------------------------------------------
25
-
26
- function makeEntry(id: string, text: string, category = "preferences"): MemoryEntry {
27
- return {
28
- id,
29
- text,
30
- vector: [1, 0, 0],
31
- category,
32
- scope: "project:test",
33
- importance: 0.7,
34
- timestamp: Date.now(),
35
- metadata: "{}",
36
- };
37
- }
38
-
39
- function createMockStore(searchResults: MemorySearchResult[] = []) {
40
- const updates: Array<{ id: string; text?: string }> = [];
41
- const data = new Map<string, MemoryEntry>();
42
- for (const r of searchResults) {
43
- data.set(r.entry.id, { ...r.entry });
44
- }
45
-
46
- return {
47
- updates,
48
- data,
49
- async vectorSearch(
50
- _vector: number[],
51
- _limit: number,
52
- _minScore: number,
53
- _scopeFilter?: string[],
54
- ) {
55
- return searchResults;
56
- },
57
- async update(id: string, upd: { text?: string; metadata?: string }, _scope?: string[]) {
58
- const entry = data.get(id);
59
- if (!entry) return null;
60
- if (upd.text) {
61
- entry.text = upd.text;
62
- updates.push({ id, text: upd.text });
63
- }
64
- return entry;
65
- },
66
- };
67
- }
68
-
69
- function createMockLLM(dedupResult: DedupDecision, synthResult: string | null = "merged preference text"): LLMClient {
70
- return {
71
- async dedupDecision(_newText: string, _existingText: string) {
72
- return dedupResult;
73
- },
74
- async synthesizeFragments(_fragments: string[], _query: string) {
75
- return synthResult;
76
- },
77
- } as any;
78
- }
79
-
80
- // ---------------------------------------------------------------------------
81
- // matchPreference tests
82
- // ---------------------------------------------------------------------------
83
-
84
- describe("matchPreference", () => {
85
- const vector = [1, 0, 0];
86
- const scope = "project:test";
87
-
88
- it("returns create when no similar preferences found", async () => {
89
- const store = createMockStore([]);
90
- const result = await matchPreference("I prefer dark mode", vector, scope, store as any, null);
91
-
92
- expect(result.action).toBe("create");
93
- expect(result.reason).toBe("no-similar-preferences");
94
- });
95
-
96
- it("returns create when matches are non-preference category", async () => {
97
- const store = createMockStore([
98
- { entry: makeEntry("e1", "some entity", "entities"), score: 0.85 },
99
- ]);
100
- const result = await matchPreference("I prefer dark mode", vector, scope, store as any, null);
101
-
102
- expect(result.action).toBe("create");
103
- expect(result.reason).toBe("no-similar-preferences");
104
- });
105
-
106
- it("returns skip for high similarity without LLM", async () => {
107
- const store = createMockStore([
108
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.95 },
109
- ]);
110
- const result = await matchPreference("I like dark mode", vector, scope, store as any, null);
111
-
112
- expect(result.action).toBe("skip");
113
- expect(result.reason).toContain("no-llm");
114
- });
115
-
116
- it("returns create for moderate similarity without LLM", async () => {
117
- const store = createMockStore([
118
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.82 },
119
- ]);
120
- const result = await matchPreference("I like vim keybindings", vector, scope, store as any, null);
121
-
122
- expect(result.action).toBe("create");
123
- expect(result.reason).toContain("no-llm");
124
- });
125
-
126
- it("uses LLM to decide SKIP", async () => {
127
- const store = createMockStore([
128
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.85 },
129
- ]);
130
- const llm = createMockLLM({ action: "SKIP", reason: "same preference" });
131
-
132
- const result = await matchPreference("I like dark mode", vector, scope, store as any, llm);
133
-
134
- expect(result.action).toBe("skip");
135
- expect(result.reason).toContain("llm-skip");
136
- });
137
-
138
- it("uses LLM to decide MERGE and generates merged text", async () => {
139
- const store = createMockStore([
140
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.85 },
141
- ]);
142
- const llm = createMockLLM(
143
- { action: "MERGE", reason: "compatible preferences" },
144
- "User prefers dark mode and compact layout",
145
- );
146
-
147
- const result = await matchPreference("I also like compact layout", vector, scope, store as any, llm);
148
-
149
- expect(result.action).toBe("merge");
150
- expect(result.mergeTargetId).toBe("p1");
151
- expect(result.mergedText).toBe("User prefers dark mode and compact layout");
152
- });
153
-
154
- it("uses LLM to decide CREATE", async () => {
155
- const store = createMockStore([
156
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.80 },
157
- ]);
158
- const llm = createMockLLM({ action: "CREATE", reason: "different preference" });
159
-
160
- const result = await matchPreference("I prefer vim keybindings", vector, scope, store as any, llm);
161
-
162
- expect(result.action).toBe("create");
163
- expect(result.reason).toContain("llm-create");
164
- });
165
-
166
- it("falls back to create on LLM failure", async () => {
167
- const store = createMockStore([
168
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.85 },
169
- ]);
170
- const llm = {
171
- async dedupDecision() { throw new Error("LLM down"); },
172
- async synthesizeFragments() { return null; },
173
- } as any;
174
-
175
- const result = await matchPreference("test pref", vector, scope, store as any, llm);
176
-
177
- expect(result.action).toBe("create");
178
- expect(result.reason).toBe("llm-error");
179
- });
180
- });
181
-
182
- // ---------------------------------------------------------------------------
183
- // applyPreferenceMatch tests
184
- // ---------------------------------------------------------------------------
185
-
186
- describe("applyPreferenceMatch", () => {
187
- it("handles skip action", async () => {
188
- const store = createMockStore([]);
189
- const result = await applyPreferenceMatch(
190
- { action: "skip", reason: "duplicate" },
191
- store as any,
192
- "project:test",
193
- );
194
-
195
- expect(result.handled).toBe(true);
196
- expect(result.entry).toBeUndefined();
197
- });
198
-
199
- it("handles merge action — updates existing entry text", async () => {
200
- const existing = makeEntry("p1", "old preference text");
201
- const store = createMockStore([{ entry: existing, score: 0.9 }]);
202
-
203
- const result = await applyPreferenceMatch(
204
- {
205
- action: "merge",
206
- mergeTargetId: "p1",
207
- mergedText: "merged preference text",
208
- reason: "llm-merge",
209
- },
210
- store as any,
211
- "project:test",
212
- );
213
-
214
- expect(result.handled).toBe(true);
215
- expect(result.entry).toBeDefined();
216
- expect(store.updates.length).toBe(1);
217
- expect(store.updates[0].text).toBe("merged preference text");
218
- });
219
-
220
- it("returns not handled for create action", async () => {
221
- const store = createMockStore([]);
222
- const result = await applyPreferenceMatch(
223
- { action: "create", reason: "new" },
224
- store as any,
225
- "project:test",
226
- );
227
-
228
- expect(result.handled).toBe(false);
229
- });
230
- });
@@ -1,205 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import {
4
- inferPreferenceSlot,
5
- inferAtomicBrandItemPreferenceSlot,
6
- parseBrandItemPreference,
7
- inferReplyStylePreferenceSlot,
8
- inferToolChoicePreferenceSlot,
9
- inferImplicitUsageSlot,
10
- samePreferenceSlot,
11
- } from "../preference-slots.js";
12
-
13
- describe("preference slots", () => {
14
- it("parses Chinese and English brand-item preferences", () => {
15
- expect(parseBrandItemPreference("我喜欢喝星巴克的抹茶拿铁")).toEqual({
16
- brand: "星巴克",
17
- items: ["抹茶拿铁"],
18
- aggregate: false,
19
- });
20
-
21
- expect(parseBrandItemPreference("我喜欢吃麦当劳的麦旋风、板烧鸡腿堡和藤椒鸡派")).toEqual({
22
- brand: "麦当劳",
23
- items: ["麦旋风", "板烧鸡腿堡", "藤椒鸡派"],
24
- aggregate: true,
25
- });
26
-
27
- expect(parseBrandItemPreference("I like the Big Mac from McDonald's")).toEqual({
28
- brand: "mcdonald's",
29
- items: ["bigmac"],
30
- aggregate: false,
31
- });
32
-
33
- expect(parseBrandItemPreference("我们刚讨论过星巴克的抹茶拿铁做法")).toBeNull();
34
- });
35
-
36
- it("infers atomic brand-item slots and skips aggregate preferences", () => {
37
- expect(inferAtomicBrandItemPreferenceSlot("我喜欢喝星巴克的抹茶拿铁")).toEqual({
38
- type: "brand-item",
39
- brand: "星巴克",
40
- item: "抹茶拿铁",
41
- });
42
-
43
- expect(inferAtomicBrandItemPreferenceSlot("I like Big Mac from McDonald's")).toEqual({
44
- type: "brand-item",
45
- brand: "mcdonald's",
46
- item: "bigmac",
47
- });
48
-
49
- expect(samePreferenceSlot(
50
- inferAtomicBrandItemPreferenceSlot("I like the Big Mac from McDonald's"),
51
- inferAtomicBrandItemPreferenceSlot("I like Big Mac from McDonald's"),
52
- )).toBe(true);
53
-
54
- expect(inferAtomicBrandItemPreferenceSlot("我喜欢吃麦当劳的麦旋风、板烧鸡腿堡")).toBeNull();
55
- });
56
-
57
- it("infers reply-style traits from explicit reply-style preferences", () => {
58
- expect(inferReplyStylePreferenceSlot("User prefers concise, direct replies.")).toEqual({
59
- type: "reply-style",
60
- traits: ["concise", "direct"],
61
- });
62
-
63
- expect(inferReplyStylePreferenceSlot("用户不接受浮夸/营销腔,语气要口语化、不端着。")).toEqual({
64
- type: "reply-style",
65
- traits: ["colloquial", "grounded"],
66
- });
67
- });
68
-
69
- it("can infer reply-style slots from compact trait phrases", () => {
70
- expect(inferPreferenceSlot("用户偏好短句直说。")).toEqual({
71
- type: "reply-style",
72
- traits: ["concise", "direct"],
73
- });
74
- });
75
-
76
- it("ignores descriptive non-preference text for reply-style and tool-choice parsing", () => {
77
- expect(inferReplyStylePreferenceSlot("这段文案简洁直接,先别改。")).toBeNull();
78
- expect(inferReplyStylePreferenceSlot("这段文案挺口语化,先别改。")).toBeNull();
79
- expect(inferToolChoicePreferenceSlot("文档里写了 uses Bun over Node 的迁移说明。")).toBeNull();
80
- });
81
-
82
- it("compares reply-style slots by normalized trait sets", () => {
83
- expect(samePreferenceSlot(
84
- inferReplyStylePreferenceSlot("User prefers concise, direct replies."),
85
- inferReplyStylePreferenceSlot("User prefers direct concise responses."),
86
- )).toBe(true);
87
-
88
- expect(samePreferenceSlot(
89
- inferReplyStylePreferenceSlot("User prefers concise, direct replies."),
90
- inferReplyStylePreferenceSlot("User prefers colloquial grounded replies."),
91
- )).toBe(false);
92
- });
93
-
94
- it("infers tool-choice slots from explicit comparative preferences", () => {
95
- expect(inferToolChoicePreferenceSlot("Uses Bun over Node.")).toEqual({
96
- type: "tool-choice",
97
- preferredTool: "bun",
98
- avoidedTool: "node",
99
- });
100
-
101
- expect(inferToolChoicePreferenceSlot("更喜欢用 rg 而不是 grep")).toEqual({
102
- type: "tool-choice",
103
- preferredTool: "rg",
104
- avoidedTool: "grep",
105
- });
106
- });
107
-
108
- it("detects implicit usage preferences from 'I use X' patterns", () => {
109
- expect(inferImplicitUsageSlot("I use Adobe Premiere Pro for video editing.")).toEqual({
110
- type: "implicit-usage",
111
- subject: "adobepremierepro",
112
- });
113
-
114
- expect(inferImplicitUsageSlot("User uses Python for data analysis.")).toEqual({
115
- type: "implicit-usage",
116
- subject: "python",
117
- });
118
-
119
- expect(inferImplicitUsageSlot("I have been using VS Code.")).toEqual({
120
- type: "implicit-usage",
121
- subject: "vscode",
122
- });
123
- });
124
-
125
- it("detects implicit ownership preferences from 'I have X' patterns", () => {
126
- expect(inferImplicitUsageSlot("I have a Sony A7III camera")).toEqual({
127
- type: "implicit-usage",
128
- subject: "sonya7iii",
129
- });
130
-
131
- expect(inferImplicitUsageSlot("User bought a Canon EOS R5 camera")).toEqual({
132
- type: "implicit-usage",
133
- subject: "canoneosr5",
134
- });
135
- });
136
-
137
- it("detects implicit activity preferences from 'I enjoy X' patterns", () => {
138
- expect(inferImplicitUsageSlot("I enjoy hiking.")).toEqual({
139
- type: "implicit-usage",
140
- subject: "hiking",
141
- });
142
-
143
- expect(inferImplicitUsageSlot("User loves ocean views.")).toEqual({
144
- type: "implicit-usage",
145
- subject: "oceanviews",
146
- });
147
-
148
- expect(inferImplicitUsageSlot("I really like photography.")).toEqual({
149
- type: "implicit-usage",
150
- subject: "photography",
151
- });
152
- });
153
-
154
- it("detects implicit interest preferences from 'I'm learning X' patterns", () => {
155
- expect(inferImplicitUsageSlot("I'm learning Spanish.")).toEqual({
156
- type: "implicit-usage",
157
- subject: "spanish",
158
- });
159
-
160
- expect(inferImplicitUsageSlot("User is interested in machine learning.")).toEqual({
161
- type: "implicit-usage",
162
- subject: "machinelearning",
163
- });
164
- });
165
-
166
- it("detects Chinese implicit usage preferences", () => {
167
- expect(inferImplicitUsageSlot("我在用Figma做设计。")).toEqual({
168
- type: "implicit-usage",
169
- subject: "figma",
170
- });
171
-
172
- expect(inferImplicitUsageSlot("我在学西班牙语。")).toEqual({
173
- type: "implicit-usage",
174
- subject: "西班牙语",
175
- });
176
- });
177
-
178
- it("rejects short/empty implicit usage subjects", () => {
179
- // "I use it" → subject "it" normalizes to 2 chars — at threshold
180
- expect(inferImplicitUsageSlot("I use a.")).toBeNull();
181
- });
182
-
183
- it("explicit patterns take priority over implicit in inferPreferenceSlot", () => {
184
- // "I like Big Mac from McDonald's" → brand-item, not implicit-usage
185
- expect(inferPreferenceSlot("I like Big Mac from McDonald's")?.type).toBe("brand-item");
186
-
187
- // "Uses Bun over Node." → tool-choice, not implicit-usage
188
- expect(inferPreferenceSlot("Uses Bun over Node.")?.type).toBe("tool-choice");
189
-
190
- // "I use Adobe Premiere Pro for video editing." → implicit-usage (no explicit pattern matches)
191
- expect(inferPreferenceSlot("I use Adobe Premiere Pro for video editing.")?.type).toBe("implicit-usage");
192
- });
193
-
194
- it("compares implicit-usage slots by normalized subject", () => {
195
- expect(samePreferenceSlot(
196
- inferImplicitUsageSlot("I use Adobe Premiere Pro."),
197
- inferImplicitUsageSlot("User uses Adobe Premiere Pro for editing."),
198
- )).toBe(true);
199
-
200
- expect(samePreferenceSlot(
201
- inferImplicitUsageSlot("I use Premiere Pro."),
202
- inferImplicitUsageSlot("I use DaVinci Resolve."),
203
- )).toBe(false);
204
- });
205
- });