recallnest 2.6.1 → 3.0.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 (207) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/README.md +280 -195
  3. package/README_CN.md +266 -199
  4. package/config.json.example +8 -1
  5. package/docs/api-reference.md +1 -1
  6. package/integrations/README.md +1 -1
  7. package/integrations/agy/README.md +73 -0
  8. package/integrations/agy/setup.sh +75 -0
  9. package/integrations/setup-all.sh +1 -1
  10. package/package.json +7 -5
  11. package/src/access-tracker.ts +10 -1
  12. package/src/activity-counter.ts +69 -11
  13. package/src/age-label.ts +29 -0
  14. package/src/audit-log.ts +35 -0
  15. package/src/auto-gc.ts +97 -30
  16. package/src/capture-engine.ts +26 -4
  17. package/src/cli.ts +224 -10
  18. package/src/consolidation-engine.ts +317 -123
  19. package/src/context-composer-stable.ts +5 -1
  20. package/src/data-checkup.ts +17 -3
  21. package/src/dream-pipeline.ts +90 -12
  22. package/src/embedder.ts +44 -5
  23. package/src/env-config.ts +71 -0
  24. package/src/forget-engine.ts +8 -2
  25. package/src/gateway-server.ts +272 -0
  26. package/src/ingest.ts +248 -22
  27. package/src/kg-extractor.ts +17 -0
  28. package/src/llm-client.ts +86 -25
  29. package/src/mcp-server.ts +2 -1
  30. package/src/mcp-tools-advanced.ts +7 -2
  31. package/src/mcp-tools-core.ts +11 -1
  32. package/src/mcp-tools-governance.ts +45 -3
  33. package/src/memory-evolution.ts +16 -0
  34. package/src/memory-output.ts +47 -8
  35. package/src/memory-promotion.ts +280 -0
  36. package/src/memory-store-port.ts +32 -3
  37. package/src/memory-utility.ts +314 -0
  38. package/src/pivot-apply.ts +52 -1
  39. package/src/pivot-evidence.ts +128 -0
  40. package/src/ppr-traversal.ts +25 -1
  41. package/src/recall-ledger.ts +76 -0
  42. package/src/retriever.ts +82 -9
  43. package/src/runtime-config.ts +9 -0
  44. package/src/scope-policy.ts +22 -1
  45. package/src/session-engine.ts +11 -4
  46. package/src/session-output.ts +13 -4
  47. package/src/session-store.ts +2 -3
  48. package/src/skill-promotion.ts +218 -1
  49. package/src/store.ts +231 -71
  50. package/src/synthesis-contract.ts +286 -0
  51. package/src/version-manager.ts +83 -0
  52. package/src/workflow-observation-schema.ts +26 -0
  53. package/integrations/gemini-cli/README.md +0 -57
  54. package/integrations/gemini-cli/setup.sh +0 -64
  55. package/src/__tests__/access-tracker-novelty.test.ts +0 -304
  56. package/src/__tests__/activity-counter.test.ts +0 -125
  57. package/src/__tests__/adaptive-fidelity.test.ts +0 -102
  58. package/src/__tests__/admission-control.test.ts +0 -206
  59. package/src/__tests__/anchor-generator.test.ts +0 -67
  60. package/src/__tests__/api-server-security.test.ts +0 -102
  61. package/src/__tests__/auto-consolidation.test.ts +0 -168
  62. package/src/__tests__/auto-recall.test.ts +0 -131
  63. package/src/__tests__/babel-integration.test.ts +0 -144
  64. package/src/__tests__/batch-dedup.test.ts +0 -65
  65. package/src/__tests__/belief-history.test.ts +0 -220
  66. package/src/__tests__/canary-eval.test.ts +0 -114
  67. package/src/__tests__/capture-engine.test.ts +0 -1328
  68. package/src/__tests__/capture-heuristic.test.ts +0 -171
  69. package/src/__tests__/cascade-forget.test.ts +0 -210
  70. package/src/__tests__/category-min-scores.test.ts +0 -116
  71. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  72. package/src/__tests__/circuit-breaker.test.ts +0 -99
  73. package/src/__tests__/cluster-consolidation.test.ts +0 -945
  74. package/src/__tests__/confidence-metadata.test.ts +0 -125
  75. package/src/__tests__/confidence-tracker.test.ts +0 -261
  76. package/src/__tests__/conflict-advisor.test.ts +0 -206
  77. package/src/__tests__/conflict-escalation.test.ts +0 -149
  78. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  79. package/src/__tests__/conflict-output.test.ts +0 -85
  80. package/src/__tests__/conflict-store.test.ts +0 -418
  81. package/src/__tests__/connector-v1.test.ts +0 -181
  82. package/src/__tests__/consolidation-engine.test.ts +0 -430
  83. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  84. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  85. package/src/__tests__/context-composer-pins.test.ts +0 -64
  86. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  87. package/src/__tests__/context-reconstructor.test.ts +0 -65
  88. package/src/__tests__/context-renderer.test.ts +0 -91
  89. package/src/__tests__/continuity-eval.test.ts +0 -275
  90. package/src/__tests__/continuity-schema.test.ts +0 -97
  91. package/src/__tests__/contracts.test.ts +0 -262
  92. package/src/__tests__/conversation-importer.test.ts +0 -692
  93. package/src/__tests__/core-recall-eval.test.ts +0 -235
  94. package/src/__tests__/core-summary.test.ts +0 -109
  95. package/src/__tests__/data-checkup.test.ts +0 -276
  96. package/src/__tests__/decay-exemption.test.ts +0 -129
  97. package/src/__tests__/dedup-actions.test.ts +0 -58
  98. package/src/__tests__/dedup-guard.test.ts +0 -39
  99. package/src/__tests__/deterministic-id.test.ts +0 -39
  100. package/src/__tests__/distill-lock.test.ts +0 -305
  101. package/src/__tests__/distill-session-argshape.test.ts +0 -79
  102. package/src/__tests__/doctor.test.ts +0 -319
  103. package/src/__tests__/dream-pipeline.test.ts +0 -780
  104. package/src/__tests__/drill-down.test.ts +0 -108
  105. package/src/__tests__/embedder.test.ts +0 -122
  106. package/src/__tests__/emotion-decay.test.ts +0 -46
  107. package/src/__tests__/emotion-detector.test.ts +0 -56
  108. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  109. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  110. package/src/__tests__/entity-resolver.test.ts +0 -58
  111. package/src/__tests__/env-config.test.ts +0 -151
  112. package/src/__tests__/error-signature-boost.test.ts +0 -87
  113. package/src/__tests__/error-signature.test.ts +0 -95
  114. package/src/__tests__/error-taxonomy.test.ts +0 -129
  115. package/src/__tests__/ethics-layer.test.ts +0 -476
  116. package/src/__tests__/event-segmenter.test.ts +0 -153
  117. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  118. package/src/__tests__/frequency-tracker.test.ts +0 -113
  119. package/src/__tests__/freshness.test.ts +0 -186
  120. package/src/__tests__/graph-export.test.ts +0 -489
  121. package/src/__tests__/hotness-blend.test.ts +0 -283
  122. package/src/__tests__/ingest-chunking.test.ts +0 -44
  123. package/src/__tests__/ingest.test.ts +0 -360
  124. package/src/__tests__/interference-detector.test.ts +0 -203
  125. package/src/__tests__/kg-extractor.test.ts +0 -192
  126. package/src/__tests__/kg-store.test.ts +0 -300
  127. package/src/__tests__/llm-client-pivot.test.ts +0 -628
  128. package/src/__tests__/llm-consolidation.test.ts +0 -226
  129. package/src/__tests__/maintenance-locks.test.ts +0 -189
  130. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  131. package/src/__tests__/mcp-related-scopes.test.ts +0 -151
  132. package/src/__tests__/memory-boundaries.test.ts +0 -282
  133. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -345
  134. package/src/__tests__/memory-evolution.test.ts +0 -271
  135. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  136. package/src/__tests__/memory-lint.test.ts +0 -421
  137. package/src/__tests__/memory-output.test.ts +0 -215
  138. package/src/__tests__/memory-promotion.test.ts +0 -315
  139. package/src/__tests__/migrate-dedup-ids.test.ts +0 -65
  140. package/src/__tests__/multi-vector.test.ts +0 -242
  141. package/src/__tests__/narrative-architecture.test.ts +0 -456
  142. package/src/__tests__/noise-filter.test.ts +0 -205
  143. package/src/__tests__/noise-prototype-bank.test.ts +0 -280
  144. package/src/__tests__/obsidian-connector.test.ts +0 -363
  145. package/src/__tests__/omitted-reporting.test.ts +0 -98
  146. package/src/__tests__/pending-review.test.ts +0 -77
  147. package/src/__tests__/pii-redact.test.ts +0 -333
  148. package/src/__tests__/pivot-apply.test.ts +0 -373
  149. package/src/__tests__/pivot-distill-supervisor.test.ts +0 -457
  150. package/src/__tests__/pivot-distill.test.ts +0 -1178
  151. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  152. package/src/__tests__/ppr-traversal.test.ts +0 -256
  153. package/src/__tests__/prediction-engine.test.ts +0 -349
  154. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  155. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  156. package/src/__tests__/preference-matcher.test.ts +0 -282
  157. package/src/__tests__/preference-slots.test.ts +0 -205
  158. package/src/__tests__/prospective-memory.test.ts +0 -283
  159. package/src/__tests__/pull-from-macbook-sync-only.test.ts +0 -679
  160. package/src/__tests__/query-entity-detector.test.ts +0 -171
  161. package/src/__tests__/query-expander-alias.test.ts +0 -146
  162. package/src/__tests__/query-expander.test.ts +0 -34
  163. package/src/__tests__/recall-gate.test.ts +0 -266
  164. package/src/__tests__/recall-governor.test.ts +0 -227
  165. package/src/__tests__/recall-mode.test.ts +0 -64
  166. package/src/__tests__/resolve-db-path.test.ts +0 -130
  167. package/src/__tests__/resolve-tier.test.ts +0 -64
  168. package/src/__tests__/result-synthesizer.test.ts +0 -198
  169. package/src/__tests__/resume-context.test.ts +0 -4898
  170. package/src/__tests__/retrieval-eval.test.ts +0 -163
  171. package/src/__tests__/retrieval-trace.test.ts +0 -213
  172. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  173. package/src/__tests__/retriever-lazy-init-dispatch.test.ts +0 -123
  174. package/src/__tests__/retroactive-boost.test.ts +0 -223
  175. package/src/__tests__/salience-scoring.test.ts +0 -106
  176. package/src/__tests__/scope-inventory.test.ts +0 -200
  177. package/src/__tests__/scope-policy.test.ts +0 -69
  178. package/src/__tests__/scope-suggester.test.ts +0 -80
  179. package/src/__tests__/session-checkpoint.test.ts +0 -291
  180. package/src/__tests__/session-distiller.test.ts +0 -640
  181. package/src/__tests__/session-gc.test.ts +0 -215
  182. package/src/__tests__/skill-engine.test.ts +0 -552
  183. package/src/__tests__/skill-promotion.test.ts +0 -654
  184. package/src/__tests__/skill-verifier.test.ts +0 -122
  185. package/src/__tests__/source-heartbeat.test.ts +0 -250
  186. package/src/__tests__/stable-context-length-contract.test.ts +0 -62
  187. package/src/__tests__/store-patch-metadata.test.ts +0 -205
  188. package/src/__tests__/store-read-consistency.test.ts +0 -122
  189. package/src/__tests__/store-schema-migration.test.ts +0 -87
  190. package/src/__tests__/store-scope-audit.test.ts +0 -81
  191. package/src/__tests__/store-upsert.test.ts +0 -201
  192. package/src/__tests__/supersede-chain.test.ts +0 -154
  193. package/src/__tests__/temporal-parser.test.ts +0 -241
  194. package/src/__tests__/temporal-validity.test.ts +0 -160
  195. package/src/__tests__/term-registry-terminal-roster.test.ts +0 -127
  196. package/src/__tests__/term-registry.test.ts +0 -121
  197. package/src/__tests__/tool-error-surfacing.test.ts +0 -98
  198. package/src/__tests__/tool-output-compressor.test.ts +0 -160
  199. package/src/__tests__/topic-tag.test.ts +0 -113
  200. package/src/__tests__/trusted-memory.test.ts +0 -839
  201. package/src/__tests__/ui-server-security.test.ts +0 -93
  202. package/src/__tests__/usage-tracker.test.ts +0 -287
  203. package/src/__tests__/version-manager.test.ts +0 -213
  204. package/src/__tests__/workflow-observation-skill-outcome.test.ts +0 -352
  205. package/src/__tests__/workflow-observation.test.ts +0 -433
  206. package/src/__tests__/write-verifier.test.ts +0 -122
  207. /package/integrations/{gemini-cli/gemini-md-snippet.md → agy/agy-md-snippet.md} +0 -0
@@ -1,282 +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
- import { parseEvolution } from "../memory-evolution.js";
22
-
23
- // ---------------------------------------------------------------------------
24
- // Helpers
25
- // ---------------------------------------------------------------------------
26
-
27
- function makeEntry(id: string, text: string, category = "preferences"): MemoryEntry {
28
- return {
29
- id,
30
- text,
31
- vector: [1, 0, 0],
32
- category,
33
- scope: "project:test",
34
- importance: 0.7,
35
- timestamp: Date.now(),
36
- metadata: "{}",
37
- };
38
- }
39
-
40
- function createMockStore(searchResults: MemorySearchResult[] = []) {
41
- const updates: Array<{ id: string; text?: string; metadata?: string }> = [];
42
- const archived: MemoryEntry[] = [];
43
- const data = new Map<string, MemoryEntry>();
44
- for (const r of searchResults) {
45
- data.set(r.entry.id, { ...r.entry });
46
- }
47
-
48
- return {
49
- updates,
50
- archived,
51
- data,
52
- async vectorSearch(
53
- _vector: number[],
54
- _limit: number,
55
- _minScore: number,
56
- _scopeFilter?: string[],
57
- ) {
58
- return searchResults;
59
- },
60
- async getById(id: string) {
61
- return data.get(id) ?? null;
62
- },
63
- // Belief-history rows land here rather than in `data` — merging must archive the
64
- // version it replaces, so tests need to see what was written.
65
- async upsert(entry: MemoryEntry) {
66
- archived.push(entry);
67
- return entry;
68
- },
69
- async store(entry: MemoryEntry) {
70
- archived.push(entry);
71
- return entry;
72
- },
73
- async update(id: string, upd: { text?: string; metadata?: string }, _scope?: string[]) {
74
- const entry = data.get(id);
75
- if (!entry) return null;
76
- if (upd.text) {
77
- entry.text = upd.text;
78
- }
79
- if (upd.metadata) {
80
- entry.metadata = upd.metadata;
81
- }
82
- updates.push({ id, text: upd.text, metadata: upd.metadata });
83
- return entry;
84
- },
85
- };
86
- }
87
-
88
- const mockDeps = {
89
- embedder: {
90
- async embedPassage(text: string) {
91
- return [text.length, 1, 0];
92
- },
93
- },
94
- };
95
-
96
- function createMockLLM(dedupResult: DedupDecision, synthResult: string | null = "merged preference text"): LLMClient {
97
- return {
98
- async dedupDecision(_newText: string, _existingText: string) {
99
- return dedupResult;
100
- },
101
- async synthesizeFragments(_fragments: string[], _query: string) {
102
- return synthResult;
103
- },
104
- } as any;
105
- }
106
-
107
- // ---------------------------------------------------------------------------
108
- // matchPreference tests
109
- // ---------------------------------------------------------------------------
110
-
111
- describe("matchPreference", () => {
112
- const vector = [1, 0, 0];
113
- const scope = "project:test";
114
-
115
- it("returns create when no similar preferences found", async () => {
116
- const store = createMockStore([]);
117
- const result = await matchPreference("I prefer dark mode", vector, scope, store as any, null);
118
-
119
- expect(result.action).toBe("create");
120
- expect(result.reason).toBe("no-similar-preferences");
121
- });
122
-
123
- it("ignores superseded belief-history rows as match candidates", async () => {
124
- // store.vectorSearch() filters by scope only, so archived versions of a preference
125
- // surface here alongside live ones. Matching against one would drop the incoming
126
- // preference as a duplicate of a belief that is no longer held.
127
- const archivedRow = makeEntry("p-old", "I prefer dark mode");
128
- archivedRow.metadata = JSON.stringify({
129
- evolution: { status: "superseded", validUntil: Date.now(), supersededBy: "p-live" },
130
- });
131
- const store = createMockStore([{ entry: archivedRow, score: 0.99 }]);
132
-
133
- const result = await matchPreference("I prefer dark mode", vector, scope, store as any, null);
134
-
135
- expect(result.action).toBe("create");
136
- expect(result.reason).toBe("no-similar-preferences");
137
- });
138
-
139
- it("returns create when matches are non-preference category", async () => {
140
- const store = createMockStore([
141
- { entry: makeEntry("e1", "some entity", "entities"), score: 0.85 },
142
- ]);
143
- const result = await matchPreference("I prefer dark mode", vector, scope, store as any, null);
144
-
145
- expect(result.action).toBe("create");
146
- expect(result.reason).toBe("no-similar-preferences");
147
- });
148
-
149
- it("returns skip for high similarity without LLM", async () => {
150
- const store = createMockStore([
151
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.95 },
152
- ]);
153
- const result = await matchPreference("I like dark mode", vector, scope, store as any, null);
154
-
155
- expect(result.action).toBe("skip");
156
- expect(result.reason).toContain("no-llm");
157
- });
158
-
159
- it("returns create for moderate similarity without LLM", async () => {
160
- const store = createMockStore([
161
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.82 },
162
- ]);
163
- const result = await matchPreference("I like vim keybindings", vector, scope, store as any, null);
164
-
165
- expect(result.action).toBe("create");
166
- expect(result.reason).toContain("no-llm");
167
- });
168
-
169
- it("uses LLM to decide SKIP", async () => {
170
- const store = createMockStore([
171
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.85 },
172
- ]);
173
- const llm = createMockLLM({ action: "SKIP", reason: "same preference" });
174
-
175
- const result = await matchPreference("I like dark mode", vector, scope, store as any, llm);
176
-
177
- expect(result.action).toBe("skip");
178
- expect(result.reason).toContain("llm-skip");
179
- });
180
-
181
- it("uses LLM to decide MERGE and generates merged text", async () => {
182
- const store = createMockStore([
183
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.85 },
184
- ]);
185
- const llm = createMockLLM(
186
- { action: "MERGE", reason: "compatible preferences" },
187
- "User prefers dark mode and compact layout",
188
- );
189
-
190
- const result = await matchPreference("I also like compact layout", vector, scope, store as any, llm);
191
-
192
- expect(result.action).toBe("merge");
193
- expect(result.mergeTargetId).toBe("p1");
194
- expect(result.mergedText).toBe("User prefers dark mode and compact layout");
195
- });
196
-
197
- it("uses LLM to decide CREATE", async () => {
198
- const store = createMockStore([
199
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.80 },
200
- ]);
201
- const llm = createMockLLM({ action: "CREATE", reason: "different preference" });
202
-
203
- const result = await matchPreference("I prefer vim keybindings", vector, scope, store as any, llm);
204
-
205
- expect(result.action).toBe("create");
206
- expect(result.reason).toContain("llm-create");
207
- });
208
-
209
- it("falls back to create on LLM failure", async () => {
210
- const store = createMockStore([
211
- { entry: makeEntry("p1", "User prefers dark mode"), score: 0.85 },
212
- ]);
213
- const llm = {
214
- async dedupDecision() { throw new Error("LLM down"); },
215
- async synthesizeFragments() { return null; },
216
- } as any;
217
-
218
- const result = await matchPreference("test pref", vector, scope, store as any, llm);
219
-
220
- expect(result.action).toBe("create");
221
- expect(result.reason).toBe("llm-error");
222
- });
223
- });
224
-
225
- // ---------------------------------------------------------------------------
226
- // applyPreferenceMatch tests
227
- // ---------------------------------------------------------------------------
228
-
229
- describe("applyPreferenceMatch", () => {
230
- it("handles skip action", async () => {
231
- const store = createMockStore([]);
232
- const result = await applyPreferenceMatch(
233
- { action: "skip", reason: "duplicate" },
234
- store as any,
235
- "project:test",
236
- mockDeps,
237
- );
238
-
239
- expect(result.handled).toBe(true);
240
- expect(result.entry).toBeUndefined();
241
- });
242
-
243
- it("handles merge action — updates existing entry text", async () => {
244
- const existing = makeEntry("p1", "old preference text");
245
- const store = createMockStore([{ entry: existing, score: 0.9 }]);
246
-
247
- const result = await applyPreferenceMatch(
248
- {
249
- action: "merge",
250
- mergeTargetId: "p1",
251
- mergedText: "merged preference text",
252
- reason: "llm-merge",
253
- },
254
- store as any,
255
- "project:test",
256
- mockDeps,
257
- );
258
-
259
- expect(result.handled).toBe(true);
260
- expect(result.entry).toBeDefined();
261
- expect(store.updates.length).toBe(1);
262
- expect(store.updates[0].text).toBe("merged preference text");
263
-
264
- // Merging replaces the target's wording — the pre-merge version must survive.
265
- expect(store.archived).toHaveLength(1);
266
- expect(store.archived[0].text).toBe("old preference text");
267
- expect(parseEvolution(store.archived[0].metadata).status).toBe("superseded");
268
- expect(parseEvolution(store.updates[0].metadata).supersedes).toBe(store.archived[0].id);
269
- });
270
-
271
- it("returns not handled for create action", async () => {
272
- const store = createMockStore([]);
273
- const result = await applyPreferenceMatch(
274
- { action: "create", reason: "new" },
275
- store as any,
276
- "project:test",
277
- mockDeps,
278
- );
279
-
280
- expect(result.handled).toBe(false);
281
- });
282
- });
@@ -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
- });