recallnest 3.0.0 → 4.0.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 (246) hide show
  1. package/CHANGELOG.md +304 -0
  2. package/README.md +251 -258
  3. package/README_CN.md +226 -247
  4. package/assets/ui/app.js +6 -1
  5. package/bin/recallnest.mjs +20 -37
  6. package/docs/api-reference.md +8 -0
  7. package/docs/architecture.md +18 -13
  8. package/eval/cases-canary.json +107 -9
  9. package/integrations/examples/claude-agent-sdk/memory-agent.ts +2 -0
  10. package/integrations/examples/langchain/memory-chain.py +4 -0
  11. package/integrations/examples/openai-agents-sdk/memory-agent.py +4 -0
  12. package/package.json +12 -3
  13. package/src/activity-counter.ts +1 -0
  14. package/src/adaptive-retrieval.ts +23 -0
  15. package/src/api-server.ts +13 -11
  16. package/src/asset-sync.ts +26 -2
  17. package/src/audit-log.ts +2 -0
  18. package/src/auto-gc.ts +4 -3
  19. package/src/belief-history.ts +137 -31
  20. package/src/capture-engine.ts +459 -71
  21. package/src/cascade-forget.ts +7 -3
  22. package/src/checkpoint-mirror.ts +417 -0
  23. package/src/cli.ts +574 -54
  24. package/src/conflict-engine.ts +43 -15
  25. package/src/consolidation-engine.ts +24 -3
  26. package/src/context-collapse-renderer.ts +8 -1
  27. package/src/context-composer-stable.ts +5 -1
  28. package/src/context-composer.ts +11 -2
  29. package/src/distill-lock.ts +246 -74
  30. package/src/dream-pipeline.ts +3 -1
  31. package/src/embedder.ts +24 -0
  32. package/src/env-config.ts +136 -0
  33. package/src/flock.ts +97 -0
  34. package/src/forget-engine.ts +342 -58
  35. package/src/gateway-server.ts +4 -0
  36. package/src/ingest-plan.ts +56 -0
  37. package/src/ingest.ts +294 -121
  38. package/src/kg-contradiction.ts +753 -0
  39. package/src/kg-extractor.ts +17 -0
  40. package/src/kg-store.ts +1 -1
  41. package/src/known-item-eval.ts +455 -0
  42. package/src/llm-client.ts +13 -26
  43. package/src/llm-consolidation.ts +2 -2
  44. package/src/mcp-tool-deps.ts +6 -0
  45. package/src/mcp-tools-advanced.ts +80 -45
  46. package/src/mcp-tools-core.ts +23 -17
  47. package/src/mcp-tools-governance.ts +13 -7
  48. package/src/memory-assets.ts +144 -8
  49. package/src/memory-boundaries.ts +19 -0
  50. package/src/memory-evolution.ts +9 -0
  51. package/src/memory-lint.ts +1 -1
  52. package/src/memory-output.ts +311 -14
  53. package/src/memory-reconcile.ts +1437 -0
  54. package/src/memory-schema.ts +11 -0
  55. package/src/memory-store-port.ts +1 -0
  56. package/src/minis-archive.ts +151 -0
  57. package/src/noise-filter.ts +15 -2
  58. package/src/pii-detector.ts +31 -1
  59. package/src/pivot-apply.ts +55 -5
  60. package/src/pivot-evidence.ts +128 -0
  61. package/src/ppr-traversal.ts +25 -1
  62. package/src/preference-matcher.ts +40 -28
  63. package/src/prospective-memory.ts +7 -4
  64. package/src/retriever.ts +297 -57
  65. package/src/retroactive-boost.ts +3 -2
  66. package/src/runtime-config.ts +15 -1
  67. package/src/scope-policy.ts +1 -0
  68. package/src/session-distiller.ts +6 -2
  69. package/src/session-engine.ts +11 -4
  70. package/src/session-output.ts +20 -1
  71. package/src/session-schema.ts +5 -2
  72. package/src/session-store.ts +59 -12
  73. package/src/skill-promotion.ts +67 -1
  74. package/src/store-receipts.ts +109 -0
  75. package/src/store-write-budget.ts +37 -0
  76. package/src/store.ts +298 -57
  77. package/src/text-fingerprint.ts +25 -0
  78. package/src/trigger-store.ts +443 -0
  79. package/src/ui-server.ts +22 -21
  80. package/src/version-manager.ts +19 -5
  81. package/src/write-verifier.ts +16 -11
  82. package/src/__tests__/access-tracker-novelty.test.ts +0 -324
  83. package/src/__tests__/activity-counter.test.ts +0 -125
  84. package/src/__tests__/adaptive-fidelity.test.ts +0 -102
  85. package/src/__tests__/admission-control.test.ts +0 -206
  86. package/src/__tests__/age-label.test.ts +0 -33
  87. package/src/__tests__/anchor-generator.test.ts +0 -67
  88. package/src/__tests__/api-server-security.test.ts +0 -102
  89. package/src/__tests__/auto-consolidation.test.ts +0 -168
  90. package/src/__tests__/auto-recall.test.ts +0 -131
  91. package/src/__tests__/babel-integration.test.ts +0 -144
  92. package/src/__tests__/batch-dedup.test.ts +0 -65
  93. package/src/__tests__/belief-history.test.ts +0 -220
  94. package/src/__tests__/canary-eval.test.ts +0 -114
  95. package/src/__tests__/capture-engine.test.ts +0 -1328
  96. package/src/__tests__/capture-heuristic.test.ts +0 -171
  97. package/src/__tests__/cascade-forget.test.ts +0 -210
  98. package/src/__tests__/category-min-scores.test.ts +0 -116
  99. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  100. package/src/__tests__/circuit-breaker.test.ts +0 -99
  101. package/src/__tests__/cluster-consolidation.test.ts +0 -1236
  102. package/src/__tests__/confidence-metadata.test.ts +0 -125
  103. package/src/__tests__/confidence-tracker.test.ts +0 -261
  104. package/src/__tests__/conflict-advisor.test.ts +0 -206
  105. package/src/__tests__/conflict-escalation.test.ts +0 -149
  106. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  107. package/src/__tests__/conflict-output.test.ts +0 -85
  108. package/src/__tests__/conflict-store.test.ts +0 -418
  109. package/src/__tests__/connector-v1.test.ts +0 -181
  110. package/src/__tests__/consolidation-engine.test.ts +0 -520
  111. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  112. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  113. package/src/__tests__/context-composer-pins.test.ts +0 -64
  114. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  115. package/src/__tests__/context-reconstructor.test.ts +0 -65
  116. package/src/__tests__/context-renderer.test.ts +0 -91
  117. package/src/__tests__/continuity-eval.test.ts +0 -275
  118. package/src/__tests__/continuity-schema.test.ts +0 -97
  119. package/src/__tests__/contracts.test.ts +0 -262
  120. package/src/__tests__/conversation-importer.test.ts +0 -692
  121. package/src/__tests__/core-recall-eval.test.ts +0 -235
  122. package/src/__tests__/core-summary.test.ts +0 -109
  123. package/src/__tests__/data-checkup.test.ts +0 -304
  124. package/src/__tests__/decay-exemption.test.ts +0 -129
  125. package/src/__tests__/dedup-actions.test.ts +0 -58
  126. package/src/__tests__/dedup-guard.test.ts +0 -39
  127. package/src/__tests__/deterministic-id.test.ts +0 -39
  128. package/src/__tests__/distill-lock.test.ts +0 -305
  129. package/src/__tests__/distill-session-argshape.test.ts +0 -79
  130. package/src/__tests__/doctor.test.ts +0 -319
  131. package/src/__tests__/dream-pipeline.test.ts +0 -820
  132. package/src/__tests__/dream-scope-isolation.test.ts +0 -386
  133. package/src/__tests__/dream-write-batching.test.ts +0 -211
  134. package/src/__tests__/drill-down.test.ts +0 -108
  135. package/src/__tests__/embedder.test.ts +0 -122
  136. package/src/__tests__/emotion-decay.test.ts +0 -46
  137. package/src/__tests__/emotion-detector.test.ts +0 -56
  138. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  139. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  140. package/src/__tests__/entity-resolver.test.ts +0 -58
  141. package/src/__tests__/env-config-invariant.test.ts +0 -80
  142. package/src/__tests__/env-config.test.ts +0 -211
  143. package/src/__tests__/error-signature-boost.test.ts +0 -87
  144. package/src/__tests__/error-signature.test.ts +0 -95
  145. package/src/__tests__/error-taxonomy.test.ts +0 -129
  146. package/src/__tests__/ethics-layer.test.ts +0 -476
  147. package/src/__tests__/event-segmenter.test.ts +0 -153
  148. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  149. package/src/__tests__/frequency-tracker.test.ts +0 -113
  150. package/src/__tests__/freshness.test.ts +0 -186
  151. package/src/__tests__/graph-export.test.ts +0 -489
  152. package/src/__tests__/hotness-blend.test.ts +0 -283
  153. package/src/__tests__/ingest-chunking.test.ts +0 -44
  154. package/src/__tests__/ingest-scope-prefix.test.ts +0 -69
  155. package/src/__tests__/ingest.test.ts +0 -360
  156. package/src/__tests__/interference-detector.test.ts +0 -203
  157. package/src/__tests__/kg-extractor.test.ts +0 -192
  158. package/src/__tests__/kg-store.test.ts +0 -300
  159. package/src/__tests__/llm-client-pivot.test.ts +0 -628
  160. package/src/__tests__/llm-consolidation.test.ts +0 -226
  161. package/src/__tests__/maintenance-locks.test.ts +0 -197
  162. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  163. package/src/__tests__/mcp-related-scopes.test.ts +0 -151
  164. package/src/__tests__/memory-boundaries.test.ts +0 -282
  165. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -362
  166. package/src/__tests__/memory-evolution.test.ts +0 -271
  167. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  168. package/src/__tests__/memory-lint.test.ts +0 -421
  169. package/src/__tests__/memory-output.test.ts +0 -224
  170. package/src/__tests__/memory-promotion.test.ts +0 -315
  171. package/src/__tests__/memory-utility.test.ts +0 -256
  172. package/src/__tests__/migrate-dedup-ids.test.ts +0 -65
  173. package/src/__tests__/multi-vector.test.ts +0 -242
  174. package/src/__tests__/narrative-architecture.test.ts +0 -456
  175. package/src/__tests__/noise-filter.test.ts +0 -205
  176. package/src/__tests__/noise-prototype-bank.test.ts +0 -280
  177. package/src/__tests__/obsidian-connector.test.ts +0 -363
  178. package/src/__tests__/omitted-reporting.test.ts +0 -98
  179. package/src/__tests__/openai-contract.test.ts +0 -513
  180. package/src/__tests__/pending-review.test.ts +0 -77
  181. package/src/__tests__/pii-redact.test.ts +0 -333
  182. package/src/__tests__/pivot-apply.test.ts +0 -373
  183. package/src/__tests__/pivot-distill-supervisor.test.ts +0 -457
  184. package/src/__tests__/pivot-distill.test.ts +0 -1178
  185. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  186. package/src/__tests__/ppr-traversal.test.ts +0 -256
  187. package/src/__tests__/prediction-engine.test.ts +0 -349
  188. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  189. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  190. package/src/__tests__/preference-matcher.test.ts +0 -282
  191. package/src/__tests__/preference-slots.test.ts +0 -205
  192. package/src/__tests__/promotion-distinct-sources.test.ts +0 -162
  193. package/src/__tests__/prospective-memory.test.ts +0 -283
  194. package/src/__tests__/pull-from-macbook-sync-only.test.ts +0 -679
  195. package/src/__tests__/query-entity-detector.test.ts +0 -171
  196. package/src/__tests__/query-expander-alias.test.ts +0 -146
  197. package/src/__tests__/query-expander.test.ts +0 -34
  198. package/src/__tests__/recall-gate.test.ts +0 -266
  199. package/src/__tests__/recall-governor.test.ts +0 -227
  200. package/src/__tests__/recall-mode.test.ts +0 -64
  201. package/src/__tests__/resolve-db-path.test.ts +0 -130
  202. package/src/__tests__/resolve-tier.test.ts +0 -64
  203. package/src/__tests__/result-synthesizer.test.ts +0 -198
  204. package/src/__tests__/resume-context.test.ts +0 -4898
  205. package/src/__tests__/retrieval-eval.test.ts +0 -163
  206. package/src/__tests__/retrieval-trace.test.ts +0 -213
  207. package/src/__tests__/retrieve-audit-revision.test.ts +0 -255
  208. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  209. package/src/__tests__/retriever-lazy-init-dispatch.test.ts +0 -123
  210. package/src/__tests__/retroactive-boost.test.ts +0 -223
  211. package/src/__tests__/salience-scoring.test.ts +0 -106
  212. package/src/__tests__/scope-inventory.test.ts +0 -200
  213. package/src/__tests__/scope-policy.test.ts +0 -69
  214. package/src/__tests__/scope-suggester.test.ts +0 -80
  215. package/src/__tests__/session-checkpoint.test.ts +0 -291
  216. package/src/__tests__/session-distiller.test.ts +0 -640
  217. package/src/__tests__/session-gc.test.ts +0 -215
  218. package/src/__tests__/skill-engine.test.ts +0 -552
  219. package/src/__tests__/skill-promotion.test.ts +0 -654
  220. package/src/__tests__/skill-verifier.test.ts +0 -122
  221. package/src/__tests__/source-heartbeat.test.ts +0 -254
  222. package/src/__tests__/stable-context-length-contract.test.ts +0 -62
  223. package/src/__tests__/store-patch-metadata.test.ts +0 -235
  224. package/src/__tests__/store-read-consistency.test.ts +0 -122
  225. package/src/__tests__/store-schema-migration.test.ts +0 -87
  226. package/src/__tests__/store-scope-audit.test.ts +0 -81
  227. package/src/__tests__/store-upsert.test.ts +0 -201
  228. package/src/__tests__/supersede-chain.test.ts +0 -154
  229. package/src/__tests__/synthesis-contract.test.ts +0 -252
  230. package/src/__tests__/synthesis-promotion.test.ts +0 -369
  231. package/src/__tests__/temporal-parser.test.ts +0 -241
  232. package/src/__tests__/temporal-validity.test.ts +0 -160
  233. package/src/__tests__/term-registry-terminal-roster.test.ts +0 -127
  234. package/src/__tests__/term-registry.test.ts +0 -121
  235. package/src/__tests__/tool-error-surfacing.test.ts +0 -98
  236. package/src/__tests__/tool-output-compressor.test.ts +0 -160
  237. package/src/__tests__/topic-tag.test.ts +0 -113
  238. package/src/__tests__/trusted-memory.test.ts +0 -867
  239. package/src/__tests__/ui-server-security.test.ts +0 -93
  240. package/src/__tests__/usage-tracker.test.ts +0 -287
  241. package/src/__tests__/utility-weight.test.ts +0 -57
  242. package/src/__tests__/verbatim-self-recall.test.ts +0 -279
  243. package/src/__tests__/version-manager.test.ts +0 -248
  244. package/src/__tests__/workflow-observation-skill-outcome.test.ts +0 -352
  245. package/src/__tests__/workflow-observation.test.ts +0 -433
  246. package/src/__tests__/write-verifier.test.ts +0 -122
@@ -1,352 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import { persistSkill, recordSkillOutcome } from "../skill-engine.js";
4
- import { WorkflowObservationInputSchema } from "../workflow-observation-schema.js";
5
- import type { MemoryEntry } from "../store.js";
6
-
7
- const TEST_SCOPE = "project:test";
8
-
9
- function createMockStore() {
10
- const entries: MemoryEntry[] = [];
11
- let seq = 1;
12
-
13
- return {
14
- entries,
15
- store: {
16
- async store(entry: Omit<MemoryEntry, "id" | "timestamp"> & { id?: string }): Promise<MemoryEntry> {
17
- const stored: MemoryEntry = {
18
- ...entry,
19
- id: entry.id || `auto-${String(seq).padStart(12, "0")}`,
20
- timestamp: 1_700_000_000_000 + seq,
21
- metadata: entry.metadata || "{}",
22
- };
23
- seq += 1;
24
- entries.push(stored);
25
- return stored;
26
- },
27
- async update(id: string, updates: Partial<MemoryEntry>): Promise<MemoryEntry | null> {
28
- const index = entries.findIndex((e) => e.id === id);
29
- if (index < 0) return null;
30
- entries[index] = {
31
- ...entries[index],
32
- ...updates,
33
- timestamp: updates.timestamp ?? entries[index].timestamp,
34
- };
35
- return entries[index];
36
- },
37
- async getById(id: string): Promise<MemoryEntry | null> {
38
- // Match real store.getById behavior: support full UUID + 8+ hex prefix,
39
- // return null on ambiguous prefix (matches >1).
40
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
41
- const prefixRegex = /^[0-9a-f]{8,}$/i;
42
- const isFullId = uuidRegex.test(id);
43
- const isPrefix = !isFullId && prefixRegex.test(id);
44
- if (!isFullId && !isPrefix) return null;
45
- if (isFullId) return entries.find((e) => e.id === id) || null;
46
- const matches = entries.filter((e) => e.id.toLowerCase().startsWith(id.toLowerCase()));
47
- if (matches.length === 0) return null;
48
- if (matches.length > 1) return null; // ambiguous prefix
49
- return matches[0];
50
- },
51
- async vectorSearch(): Promise<never[]> {
52
- return [];
53
- },
54
- },
55
- };
56
- }
57
-
58
- function createMockEmbedder() {
59
- return {
60
- async embedPassage(text: string): Promise<number[]> {
61
- return [text.length, 1, 0];
62
- },
63
- };
64
- }
65
-
66
- function validSkillInput(overrides: Record<string, unknown> = {}) {
67
- return {
68
- name: "deploy_production",
69
- description: "Deploy to production environment with safety checks",
70
- triggerPattern: "When user says 'deploy to prod' or 'release'",
71
- implementationType: "instruction_sequence" as const,
72
- implementation: "1. Confirm production target. 2. Run pre-deploy checks. 3. Tag release. 4. Notify channel.",
73
- scope: TEST_SCOPE,
74
- source: "agent" as const,
75
- tags: ["deploy", "production"],
76
- ...overrides,
77
- };
78
- }
79
-
80
- function getSkillMeta(entry: MemoryEntry): Record<string, unknown> {
81
- const meta = JSON.parse(entry.metadata || "{}");
82
- return (meta.skill || {}) as Record<string, unknown>;
83
- }
84
-
85
- // ---------------------------------------------------------------------------
86
- // WorkflowObservationInputSchema accepts skillId
87
- // ---------------------------------------------------------------------------
88
-
89
- describe("WorkflowObservationInputSchema skillId field", () => {
90
- it("accepts optional skillId on input", () => {
91
- const parsed = WorkflowObservationInputSchema.parse({
92
- workflowId: "skill_run",
93
- summary: "ran deploy_production",
94
- skillId: "a1b2c3d4-deadbeef-0000-0000-000000000000",
95
- });
96
- expect(parsed.skillId).toBe("a1b2c3d4-deadbeef-0000-0000-000000000000");
97
- });
98
-
99
- it("allows omission of skillId (backward-compatible)", () => {
100
- const parsed = WorkflowObservationInputSchema.parse({
101
- workflowId: "resume_context",
102
- summary: "fresh window recovered continuity",
103
- });
104
- expect(parsed.skillId).toBeUndefined();
105
- });
106
- });
107
-
108
- // ---------------------------------------------------------------------------
109
- // recordSkillOutcome — outcome mapping
110
- // ---------------------------------------------------------------------------
111
-
112
- describe("recordSkillOutcome outcome mapping", () => {
113
- it("success outcome bumps successCount and leaves failureCount unchanged", async () => {
114
- const mock = createMockStore();
115
- const embedder = createMockEmbedder();
116
- const stored = await persistSkill(mock.store, embedder, validSkillInput());
117
-
118
- const result = await recordSkillOutcome(mock.store, stored.id, "success");
119
-
120
- expect(result.updated).toBe(true);
121
- if (!result.updated) throw new Error("guard");
122
- expect(result.successCount).toBe(1);
123
- expect(result.failureCount).toBe(0);
124
- expect(result.lastRefinedAt).toBeTypeOf("string");
125
-
126
- const skill = getSkillMeta(mock.entries[0]);
127
- expect(skill.successCount).toBe(1);
128
- expect(skill.failureCount).toBe(0);
129
- });
130
-
131
- it("failure outcome bumps failureCount and leaves successCount unchanged", async () => {
132
- const mock = createMockStore();
133
- const embedder = createMockEmbedder();
134
- const stored = await persistSkill(mock.store, embedder, validSkillInput({ name: "skill_fail" }));
135
-
136
- const result = await recordSkillOutcome(mock.store, stored.id, "failure");
137
-
138
- expect(result.updated).toBe(true);
139
- if (!result.updated) throw new Error("guard");
140
- expect(result.successCount).toBe(0);
141
- expect(result.failureCount).toBe(1);
142
- });
143
-
144
- it("corrected outcome maps to failureCount +1 (counts as failure in binary mapping)", async () => {
145
- const mock = createMockStore();
146
- const embedder = createMockEmbedder();
147
- const stored = await persistSkill(mock.store, embedder, validSkillInput({ name: "skill_corrected" }));
148
-
149
- const result = await recordSkillOutcome(mock.store, stored.id, "corrected");
150
-
151
- expect(result.updated).toBe(true);
152
- if (!result.updated) throw new Error("guard");
153
- expect(result.successCount).toBe(0);
154
- expect(result.failureCount).toBe(1);
155
- });
156
-
157
- it("missed outcome maps to failureCount +1 (counts as failure in binary mapping)", async () => {
158
- const mock = createMockStore();
159
- const embedder = createMockEmbedder();
160
- const stored = await persistSkill(mock.store, embedder, validSkillInput({ name: "skill_missed" }));
161
-
162
- const result = await recordSkillOutcome(mock.store, stored.id, "missed");
163
-
164
- expect(result.updated).toBe(true);
165
- if (!result.updated) throw new Error("guard");
166
- expect(result.successCount).toBe(0);
167
- expect(result.failureCount).toBe(1);
168
- });
169
-
170
- it("accumulates counts across multiple outcomes on the same skill", async () => {
171
- const mock = createMockStore();
172
- const embedder = createMockEmbedder();
173
- const stored = await persistSkill(mock.store, embedder, validSkillInput({ name: "skill_accumulate" }));
174
-
175
- await recordSkillOutcome(mock.store, stored.id, "success");
176
- await recordSkillOutcome(mock.store, stored.id, "success");
177
- await recordSkillOutcome(mock.store, stored.id, "failure");
178
- await recordSkillOutcome(mock.store, stored.id, "corrected");
179
- const final = await recordSkillOutcome(mock.store, stored.id, "success");
180
-
181
- expect(final.updated).toBe(true);
182
- if (!final.updated) throw new Error("guard");
183
- expect(final.successCount).toBe(3);
184
- expect(final.failureCount).toBe(2);
185
- });
186
- });
187
-
188
- // ---------------------------------------------------------------------------
189
- // recordSkillOutcome — error handling
190
- // ---------------------------------------------------------------------------
191
-
192
- describe("recordSkillOutcome prefix lookup (v2.5.1)", () => {
193
- it("resolves 8+ hex prefix to full UUID and bumps successCount", async () => {
194
- const mock = createMockStore();
195
- const embedder = createMockEmbedder();
196
- const stored = await persistSkill(mock.store, embedder, validSkillInput({ name: "skill_prefix_ok" }));
197
-
198
- // store_skill returns full UUID — agent might paste back only the 8-char short alias
199
- // (as displayed in the legacy `Stored skill xxx` line). recordSkillOutcome must resolve.
200
- const prefix = stored.id.slice(0, 8);
201
- expect(prefix.length).toBe(8);
202
- expect(prefix).not.toBe(stored.id); // sanity: prefix is shorter
203
-
204
- const result = await recordSkillOutcome(mock.store, prefix, "success");
205
-
206
- expect(result.updated).toBe(true);
207
- if (!result.updated) throw new Error("guard");
208
- expect(result.successCount).toBe(1);
209
- expect(result.failureCount).toBe(0);
210
-
211
- const skill = getSkillMeta(mock.entries[0]);
212
- expect(skill.successCount).toBe(1);
213
- });
214
-
215
- it("returns skill_not_found for ambiguous prefix (matches >1 skill)", async () => {
216
- const mock = createMockStore();
217
- const embedder = createMockEmbedder();
218
-
219
- // Force two skills with same 8-char prefix by injecting explicit collision into mock.
220
- // We seed entries directly so we can control IDs.
221
- mock.entries.push({
222
- id: "deadbeef-aaaa-bbbb-cccc-000000000001",
223
- text: "skill A",
224
- vector: [1, 1, 0],
225
- category: "patterns",
226
- scope: "project:test",
227
- importance: 0.85,
228
- timestamp: 1_700_000_000_000,
229
- metadata: JSON.stringify({ skill: { name: "skill_a", implementation: "...", successCount: 0, failureCount: 0 } }),
230
- });
231
- mock.entries.push({
232
- id: "deadbeef-aaaa-bbbb-cccc-000000000002",
233
- text: "skill B",
234
- vector: [1, 1, 0],
235
- category: "patterns",
236
- scope: "project:test",
237
- importance: 0.85,
238
- timestamp: 1_700_000_000_001,
239
- metadata: JSON.stringify({ skill: { name: "skill_b", implementation: "...", successCount: 0, failureCount: 0 } }),
240
- });
241
-
242
- const result = await recordSkillOutcome(mock.store, "deadbeef", "success");
243
-
244
- // Ambiguous prefix → getById returns null → recordSkillOutcome returns skill_not_found
245
- expect(result.updated).toBe(false);
246
- if (result.updated) throw new Error("guard");
247
- expect(result.reason).toBe("skill_not_found");
248
- });
249
- });
250
-
251
- describe("recordSkillOutcome error handling", () => {
252
- it("returns skill_not_found when skillId does not exist (does not throw)", async () => {
253
- const mock = createMockStore();
254
- const result = await recordSkillOutcome(mock.store, "nonexistent-skill-id", "success");
255
- expect(result.updated).toBe(false);
256
- if (result.updated) throw new Error("guard");
257
- expect(result.reason).toBe("skill_not_found");
258
- });
259
-
260
- it("returns not_a_skill when entry exists but category is not 'patterns'", async () => {
261
- const mock = createMockStore();
262
- // Use a valid UUID format — v2.5.1 getById rejects non-hex IDs (matches real store behavior).
263
- const eventId = "aaaaaaaa-bbbb-cccc-dddd-000000000001";
264
- await mock.store.store({
265
- id: eventId,
266
- text: "ordinary event memory",
267
- vector: [1, 2, 3],
268
- category: "events",
269
- scope: TEST_SCOPE,
270
- importance: 0.5,
271
- metadata: JSON.stringify({ skill: { name: "ghost", implementation: "..." } }),
272
- });
273
-
274
- const result = await recordSkillOutcome(mock.store, eventId, "success");
275
- expect(result.updated).toBe(false);
276
- if (result.updated) throw new Error("guard");
277
- expect(result.reason).toBe("not_a_skill");
278
- });
279
-
280
- it("returns skill_metadata_missing when category is 'patterns' but skill metadata is absent", async () => {
281
- const mock = createMockStore();
282
- // Use a valid UUID format — v2.5.1 getById rejects non-hex IDs.
283
- const patternId = "bbbbbbbb-cccc-dddd-eeee-000000000002";
284
- await mock.store.store({
285
- id: patternId,
286
- text: "generic pattern",
287
- vector: [1, 2, 3],
288
- category: "patterns",
289
- scope: TEST_SCOPE,
290
- importance: 0.5,
291
- metadata: JSON.stringify({ tags: ["generic"] }),
292
- });
293
-
294
- const result = await recordSkillOutcome(mock.store, patternId, "success");
295
- expect(result.updated).toBe(false);
296
- if (result.updated) throw new Error("guard");
297
- expect(result.reason).toBe("skill_metadata_missing");
298
- });
299
- });
300
-
301
- // ---------------------------------------------------------------------------
302
- // recordSkillOutcome — metadata integrity
303
- // ---------------------------------------------------------------------------
304
-
305
- describe("recordSkillOutcome metadata integrity", () => {
306
- it("preserves all other skill fields when updating counts", async () => {
307
- const mock = createMockStore();
308
- const embedder = createMockEmbedder();
309
- const input = validSkillInput({ name: "skill_integrity", verification: "check exit code is 0" });
310
- const stored = await persistSkill(mock.store, embedder, input);
311
-
312
- await recordSkillOutcome(mock.store, stored.id, "success");
313
-
314
- const skill = getSkillMeta(mock.entries[0]);
315
- expect(skill.name).toBe("skill_integrity");
316
- expect(skill.description).toBe(input.description);
317
- expect(skill.triggerPattern).toBe(input.triggerPattern);
318
- expect(skill.implementationType).toBe("instruction_sequence");
319
- expect(skill.implementation).toBe(input.implementation);
320
- expect(skill.verification).toBe("check exit code is 0");
321
- });
322
-
323
- it("preserves top-level metadata fields (tags, source, evolution) when updating counts", async () => {
324
- const mock = createMockStore();
325
- const embedder = createMockEmbedder();
326
- const stored = await persistSkill(mock.store, embedder, validSkillInput({ name: "skill_top_meta" }));
327
-
328
- await recordSkillOutcome(mock.store, stored.id, "failure");
329
-
330
- const meta = JSON.parse(mock.entries[0].metadata || "{}");
331
- expect(meta.source).toBe("agent");
332
- expect(meta.tags).toEqual(["deploy", "production"]);
333
- expect(meta.evolution).toBeDefined();
334
- expect(meta.canonicalKey).toBe("patterns:skill:skill_top_meta");
335
- });
336
-
337
- it("sets lastRefinedAt to an ISO-8601 timestamp on each update", async () => {
338
- const mock = createMockStore();
339
- const embedder = createMockEmbedder();
340
- const stored = await persistSkill(mock.store, embedder, validSkillInput({ name: "skill_timestamp" }));
341
-
342
- const before = Date.now();
343
- const result = await recordSkillOutcome(mock.store, stored.id, "success");
344
- const after = Date.now();
345
-
346
- expect(result.updated).toBe(true);
347
- if (!result.updated) throw new Error("guard");
348
- const refinedAt = Date.parse(result.lastRefinedAt);
349
- expect(refinedAt).toBeGreaterThanOrEqual(before);
350
- expect(refinedAt).toBeLessThanOrEqual(after);
351
- });
352
- });