@soleri/core 2.0.2 → 2.4.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 (226) hide show
  1. package/dist/brain/brain.d.ts +14 -50
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +207 -16
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +86 -0
  6. package/dist/brain/intelligence.d.ts.map +1 -0
  7. package/dist/brain/intelligence.js +771 -0
  8. package/dist/brain/intelligence.js.map +1 -0
  9. package/dist/brain/types.d.ts +197 -0
  10. package/dist/brain/types.d.ts.map +1 -0
  11. package/dist/brain/types.js +2 -0
  12. package/dist/brain/types.js.map +1 -0
  13. package/dist/cognee/client.d.ts +35 -0
  14. package/dist/cognee/client.d.ts.map +1 -0
  15. package/dist/cognee/client.js +291 -0
  16. package/dist/cognee/client.js.map +1 -0
  17. package/dist/cognee/types.d.ts +46 -0
  18. package/dist/cognee/types.d.ts.map +1 -0
  19. package/dist/cognee/types.js +3 -0
  20. package/dist/cognee/types.js.map +1 -0
  21. package/dist/control/identity-manager.d.ts +22 -0
  22. package/dist/control/identity-manager.d.ts.map +1 -0
  23. package/dist/control/identity-manager.js +233 -0
  24. package/dist/control/identity-manager.js.map +1 -0
  25. package/dist/control/intent-router.d.ts +32 -0
  26. package/dist/control/intent-router.d.ts.map +1 -0
  27. package/dist/control/intent-router.js +242 -0
  28. package/dist/control/intent-router.js.map +1 -0
  29. package/dist/control/types.d.ts +68 -0
  30. package/dist/control/types.d.ts.map +1 -0
  31. package/dist/control/types.js +9 -0
  32. package/dist/control/types.js.map +1 -0
  33. package/dist/curator/curator.d.ts +29 -0
  34. package/dist/curator/curator.d.ts.map +1 -1
  35. package/dist/curator/curator.js +142 -5
  36. package/dist/curator/curator.js.map +1 -1
  37. package/dist/facades/types.d.ts +1 -1
  38. package/dist/governance/governance.d.ts +42 -0
  39. package/dist/governance/governance.d.ts.map +1 -0
  40. package/dist/governance/governance.js +488 -0
  41. package/dist/governance/governance.js.map +1 -0
  42. package/dist/governance/index.d.ts +3 -0
  43. package/dist/governance/index.d.ts.map +1 -0
  44. package/dist/governance/index.js +2 -0
  45. package/dist/governance/index.js.map +1 -0
  46. package/dist/governance/types.d.ts +102 -0
  47. package/dist/governance/types.d.ts.map +1 -0
  48. package/dist/governance/types.js +3 -0
  49. package/dist/governance/types.js.map +1 -0
  50. package/dist/index.d.ts +35 -3
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +32 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/llm/llm-client.d.ts.map +1 -1
  55. package/dist/llm/llm-client.js +9 -2
  56. package/dist/llm/llm-client.js.map +1 -1
  57. package/dist/logging/logger.d.ts +37 -0
  58. package/dist/logging/logger.d.ts.map +1 -0
  59. package/dist/logging/logger.js +145 -0
  60. package/dist/logging/logger.js.map +1 -0
  61. package/dist/logging/types.d.ts +19 -0
  62. package/dist/logging/types.d.ts.map +1 -0
  63. package/dist/logging/types.js +2 -0
  64. package/dist/logging/types.js.map +1 -0
  65. package/dist/loop/loop-manager.d.ts +49 -0
  66. package/dist/loop/loop-manager.d.ts.map +1 -0
  67. package/dist/loop/loop-manager.js +105 -0
  68. package/dist/loop/loop-manager.js.map +1 -0
  69. package/dist/loop/types.d.ts +35 -0
  70. package/dist/loop/types.d.ts.map +1 -0
  71. package/dist/loop/types.js +8 -0
  72. package/dist/loop/types.js.map +1 -0
  73. package/dist/planning/gap-analysis.d.ts +29 -0
  74. package/dist/planning/gap-analysis.d.ts.map +1 -0
  75. package/dist/planning/gap-analysis.js +265 -0
  76. package/dist/planning/gap-analysis.js.map +1 -0
  77. package/dist/planning/gap-types.d.ts +29 -0
  78. package/dist/planning/gap-types.d.ts.map +1 -0
  79. package/dist/planning/gap-types.js +28 -0
  80. package/dist/planning/gap-types.js.map +1 -0
  81. package/dist/planning/planner.d.ts +150 -1
  82. package/dist/planning/planner.d.ts.map +1 -1
  83. package/dist/planning/planner.js +365 -2
  84. package/dist/planning/planner.js.map +1 -1
  85. package/dist/project/project-registry.d.ts +79 -0
  86. package/dist/project/project-registry.d.ts.map +1 -0
  87. package/dist/project/project-registry.js +276 -0
  88. package/dist/project/project-registry.js.map +1 -0
  89. package/dist/project/types.d.ts +28 -0
  90. package/dist/project/types.d.ts.map +1 -0
  91. package/dist/project/types.js +5 -0
  92. package/dist/project/types.js.map +1 -0
  93. package/dist/runtime/admin-extra-ops.d.ts +13 -0
  94. package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
  95. package/dist/runtime/admin-extra-ops.js +284 -0
  96. package/dist/runtime/admin-extra-ops.js.map +1 -0
  97. package/dist/runtime/admin-ops.d.ts +15 -0
  98. package/dist/runtime/admin-ops.d.ts.map +1 -0
  99. package/dist/runtime/admin-ops.js +322 -0
  100. package/dist/runtime/admin-ops.js.map +1 -0
  101. package/dist/runtime/capture-ops.d.ts +15 -0
  102. package/dist/runtime/capture-ops.d.ts.map +1 -0
  103. package/dist/runtime/capture-ops.js +345 -0
  104. package/dist/runtime/capture-ops.js.map +1 -0
  105. package/dist/runtime/core-ops.d.ts +7 -3
  106. package/dist/runtime/core-ops.d.ts.map +1 -1
  107. package/dist/runtime/core-ops.js +646 -15
  108. package/dist/runtime/core-ops.js.map +1 -1
  109. package/dist/runtime/curator-extra-ops.d.ts +9 -0
  110. package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
  111. package/dist/runtime/curator-extra-ops.js +59 -0
  112. package/dist/runtime/curator-extra-ops.js.map +1 -0
  113. package/dist/runtime/domain-ops.d.ts.map +1 -1
  114. package/dist/runtime/domain-ops.js +59 -13
  115. package/dist/runtime/domain-ops.js.map +1 -1
  116. package/dist/runtime/grading-ops.d.ts +14 -0
  117. package/dist/runtime/grading-ops.d.ts.map +1 -0
  118. package/dist/runtime/grading-ops.js +105 -0
  119. package/dist/runtime/grading-ops.js.map +1 -0
  120. package/dist/runtime/loop-ops.d.ts +13 -0
  121. package/dist/runtime/loop-ops.d.ts.map +1 -0
  122. package/dist/runtime/loop-ops.js +179 -0
  123. package/dist/runtime/loop-ops.js.map +1 -0
  124. package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
  125. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
  126. package/dist/runtime/memory-cross-project-ops.js +165 -0
  127. package/dist/runtime/memory-cross-project-ops.js.map +1 -0
  128. package/dist/runtime/memory-extra-ops.d.ts +13 -0
  129. package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
  130. package/dist/runtime/memory-extra-ops.js +173 -0
  131. package/dist/runtime/memory-extra-ops.js.map +1 -0
  132. package/dist/runtime/orchestrate-ops.d.ts +17 -0
  133. package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
  134. package/dist/runtime/orchestrate-ops.js +240 -0
  135. package/dist/runtime/orchestrate-ops.js.map +1 -0
  136. package/dist/runtime/planning-extra-ops.d.ts +17 -0
  137. package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
  138. package/dist/runtime/planning-extra-ops.js +300 -0
  139. package/dist/runtime/planning-extra-ops.js.map +1 -0
  140. package/dist/runtime/project-ops.d.ts +15 -0
  141. package/dist/runtime/project-ops.d.ts.map +1 -0
  142. package/dist/runtime/project-ops.js +181 -0
  143. package/dist/runtime/project-ops.js.map +1 -0
  144. package/dist/runtime/runtime.d.ts.map +1 -1
  145. package/dist/runtime/runtime.js +48 -1
  146. package/dist/runtime/runtime.js.map +1 -1
  147. package/dist/runtime/types.d.ts +23 -0
  148. package/dist/runtime/types.d.ts.map +1 -1
  149. package/dist/runtime/vault-extra-ops.d.ts +9 -0
  150. package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
  151. package/dist/runtime/vault-extra-ops.js +195 -0
  152. package/dist/runtime/vault-extra-ops.js.map +1 -0
  153. package/dist/telemetry/telemetry.d.ts +48 -0
  154. package/dist/telemetry/telemetry.d.ts.map +1 -0
  155. package/dist/telemetry/telemetry.js +87 -0
  156. package/dist/telemetry/telemetry.js.map +1 -0
  157. package/dist/vault/vault.d.ts +94 -0
  158. package/dist/vault/vault.d.ts.map +1 -1
  159. package/dist/vault/vault.js +340 -1
  160. package/dist/vault/vault.js.map +1 -1
  161. package/package.json +1 -1
  162. package/src/__tests__/admin-extra-ops.test.ts +420 -0
  163. package/src/__tests__/admin-ops.test.ts +271 -0
  164. package/src/__tests__/brain-intelligence.test.ts +828 -0
  165. package/src/__tests__/brain.test.ts +396 -27
  166. package/src/__tests__/capture-ops.test.ts +509 -0
  167. package/src/__tests__/cognee-client.test.ts +524 -0
  168. package/src/__tests__/core-ops.test.ts +341 -49
  169. package/src/__tests__/curator-extra-ops.test.ts +359 -0
  170. package/src/__tests__/curator.test.ts +126 -31
  171. package/src/__tests__/domain-ops.test.ts +111 -9
  172. package/src/__tests__/governance.test.ts +522 -0
  173. package/src/__tests__/grading-ops.test.ts +340 -0
  174. package/src/__tests__/identity-manager.test.ts +243 -0
  175. package/src/__tests__/intent-router.test.ts +222 -0
  176. package/src/__tests__/logger.test.ts +200 -0
  177. package/src/__tests__/loop-ops.test.ts +398 -0
  178. package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
  179. package/src/__tests__/memory-extra-ops.test.ts +352 -0
  180. package/src/__tests__/orchestrate-ops.test.ts +284 -0
  181. package/src/__tests__/planner.test.ts +331 -0
  182. package/src/__tests__/planning-extra-ops.test.ts +548 -0
  183. package/src/__tests__/project-ops.test.ts +367 -0
  184. package/src/__tests__/runtime.test.ts +13 -11
  185. package/src/__tests__/vault-extra-ops.test.ts +407 -0
  186. package/src/brain/brain.ts +308 -72
  187. package/src/brain/intelligence.ts +1230 -0
  188. package/src/brain/types.ts +214 -0
  189. package/src/cognee/client.ts +352 -0
  190. package/src/cognee/types.ts +62 -0
  191. package/src/control/identity-manager.ts +354 -0
  192. package/src/control/intent-router.ts +326 -0
  193. package/src/control/types.ts +102 -0
  194. package/src/curator/curator.ts +265 -15
  195. package/src/governance/governance.ts +698 -0
  196. package/src/governance/index.ts +18 -0
  197. package/src/governance/types.ts +111 -0
  198. package/src/index.ts +128 -3
  199. package/src/llm/llm-client.ts +18 -24
  200. package/src/logging/logger.ts +154 -0
  201. package/src/logging/types.ts +21 -0
  202. package/src/loop/loop-manager.ts +130 -0
  203. package/src/loop/types.ts +44 -0
  204. package/src/planning/gap-analysis.ts +506 -0
  205. package/src/planning/gap-types.ts +58 -0
  206. package/src/planning/planner.ts +478 -2
  207. package/src/project/project-registry.ts +358 -0
  208. package/src/project/types.ts +31 -0
  209. package/src/runtime/admin-extra-ops.ts +307 -0
  210. package/src/runtime/admin-ops.ts +329 -0
  211. package/src/runtime/capture-ops.ts +385 -0
  212. package/src/runtime/core-ops.ts +747 -26
  213. package/src/runtime/curator-extra-ops.ts +71 -0
  214. package/src/runtime/domain-ops.ts +65 -13
  215. package/src/runtime/grading-ops.ts +121 -0
  216. package/src/runtime/loop-ops.ts +194 -0
  217. package/src/runtime/memory-cross-project-ops.ts +192 -0
  218. package/src/runtime/memory-extra-ops.ts +186 -0
  219. package/src/runtime/orchestrate-ops.ts +272 -0
  220. package/src/runtime/planning-extra-ops.ts +327 -0
  221. package/src/runtime/project-ops.ts +196 -0
  222. package/src/runtime/runtime.ts +54 -1
  223. package/src/runtime/types.ts +23 -0
  224. package/src/runtime/vault-extra-ops.ts +225 -0
  225. package/src/telemetry/telemetry.ts +118 -0
  226. package/src/vault/vault.ts +412 -1
@@ -0,0 +1,359 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { createAgentRuntime } from '../runtime/runtime.js';
3
+ import { createCuratorExtraOps } from '../runtime/curator-extra-ops.js';
4
+ import type { AgentRuntime } from '../runtime/types.js';
5
+ import type { OpDefinition } from '../facades/types.js';
6
+
7
+ describe('createCuratorExtraOps', () => {
8
+ let runtime: AgentRuntime;
9
+ let ops: OpDefinition[];
10
+
11
+ function findOp(name: string): OpDefinition {
12
+ const op = ops.find((o) => o.name === name);
13
+ if (!op) throw new Error(`Op "${name}" not found`);
14
+ return op;
15
+ }
16
+
17
+ afterEach(() => {
18
+ runtime?.close();
19
+ });
20
+
21
+ function setup() {
22
+ runtime = createAgentRuntime({
23
+ agentId: 'test-curator-extra',
24
+ vaultPath: ':memory:',
25
+ });
26
+ ops = createCuratorExtraOps(runtime);
27
+ }
28
+
29
+ it('should return 4 ops', () => {
30
+ setup();
31
+ expect(ops).toHaveLength(4);
32
+ const names = ops.map((o) => o.name);
33
+ expect(names).toEqual([
34
+ 'curator_entry_history',
35
+ 'curator_record_snapshot',
36
+ 'curator_queue_stats',
37
+ 'curator_enrich',
38
+ ]);
39
+ });
40
+
41
+ // ─── curator_record_snapshot ───────────────────────────────────
42
+
43
+ describe('curator_record_snapshot', () => {
44
+ it('should record a snapshot and return historyId', async () => {
45
+ setup();
46
+ runtime.vault.seed([
47
+ {
48
+ id: 'snap-1',
49
+ type: 'pattern',
50
+ domain: 'testing',
51
+ title: 'Snapshot Test',
52
+ severity: 'warning',
53
+ description: 'A test entry for snapshots.',
54
+ tags: ['test'],
55
+ },
56
+ ]);
57
+ const result = (await findOp('curator_record_snapshot').handler({
58
+ entryId: 'snap-1',
59
+ changedBy: 'user',
60
+ changeReason: 'manual snapshot',
61
+ })) as { recorded: boolean; historyId: number };
62
+
63
+ expect(result.recorded).toBe(true);
64
+ expect(result.historyId).toBeGreaterThan(0);
65
+ });
66
+
67
+ it('should return recorded false for missing entry', async () => {
68
+ setup();
69
+ const result = (await findOp('curator_record_snapshot').handler({
70
+ entryId: 'nonexistent',
71
+ })) as { recorded: boolean; historyId: number };
72
+
73
+ expect(result.recorded).toBe(false);
74
+ expect(result.historyId).toBe(-1);
75
+ });
76
+ });
77
+
78
+ // ─── curator_entry_history ─────────────────────────────────────
79
+
80
+ describe('curator_entry_history', () => {
81
+ it('should return version history with 2 snapshots in order', async () => {
82
+ setup();
83
+ runtime.vault.seed([
84
+ {
85
+ id: 'hist-1',
86
+ type: 'pattern',
87
+ domain: 'testing',
88
+ title: 'History Test',
89
+ severity: 'warning',
90
+ description: 'Entry for history test.',
91
+ tags: ['test'],
92
+ },
93
+ ]);
94
+
95
+ // Record two snapshots
96
+ runtime.curator.recordSnapshot('hist-1', 'user', 'first snapshot');
97
+ runtime.curator.recordSnapshot('hist-1', 'system', 'second snapshot');
98
+
99
+ const result = (await findOp('curator_entry_history').handler({
100
+ entryId: 'hist-1',
101
+ })) as {
102
+ entryId: string;
103
+ history: Array<{
104
+ historyId: number;
105
+ entryId: string;
106
+ snapshot: { title: string };
107
+ changedBy: string;
108
+ changeReason: string | null;
109
+ createdAt: number;
110
+ }>;
111
+ count: number;
112
+ };
113
+
114
+ expect(result.entryId).toBe('hist-1');
115
+ expect(result.count).toBe(2);
116
+ expect(result.history).toHaveLength(2);
117
+ // First snapshot first (ASC order)
118
+ expect(result.history[0].changedBy).toBe('user');
119
+ expect(result.history[0].changeReason).toBe('first snapshot');
120
+ expect(result.history[0].snapshot.title).toBe('History Test');
121
+ expect(result.history[1].changedBy).toBe('system');
122
+ expect(result.history[1].changeReason).toBe('second snapshot');
123
+ });
124
+
125
+ it('should return empty history for entry with no snapshots', async () => {
126
+ setup();
127
+ runtime.vault.seed([
128
+ {
129
+ id: 'hist-2',
130
+ type: 'pattern',
131
+ domain: 'testing',
132
+ title: 'No History',
133
+ severity: 'warning',
134
+ description: 'Entry with no history.',
135
+ tags: ['test'],
136
+ },
137
+ ]);
138
+
139
+ const result = (await findOp('curator_entry_history').handler({
140
+ entryId: 'hist-2',
141
+ })) as { count: number };
142
+
143
+ expect(result.count).toBe(0);
144
+ });
145
+ });
146
+
147
+ // ─── curator_queue_stats ───────────────────────────────────────
148
+
149
+ describe('curator_queue_stats', () => {
150
+ it('should return correct grooming stats', async () => {
151
+ setup();
152
+ // Seed 3 entries
153
+ runtime.vault.seed([
154
+ {
155
+ id: 'qs-1',
156
+ type: 'pattern',
157
+ domain: 'testing',
158
+ title: 'Queue A',
159
+ severity: 'warning',
160
+ description: 'Test.',
161
+ tags: ['test'],
162
+ },
163
+ {
164
+ id: 'qs-2',
165
+ type: 'pattern',
166
+ domain: 'testing',
167
+ title: 'Queue B',
168
+ severity: 'warning',
169
+ description: 'Test.',
170
+ tags: ['test'],
171
+ },
172
+ {
173
+ id: 'qs-3',
174
+ type: 'pattern',
175
+ domain: 'testing',
176
+ title: 'Queue C',
177
+ severity: 'warning',
178
+ description: 'Test.',
179
+ tags: ['test'],
180
+ },
181
+ ]);
182
+
183
+ // Groom only 2 of the 3
184
+ runtime.curator.groomEntry('qs-1');
185
+ runtime.curator.groomEntry('qs-2');
186
+
187
+ const result = (await findOp('curator_queue_stats').handler({})) as {
188
+ totalEntries: number;
189
+ groomedEntries: number;
190
+ ungroomedEntries: number;
191
+ staleEntries: number;
192
+ freshEntries: number;
193
+ avgDaysSinceGroom: number;
194
+ };
195
+
196
+ expect(result.totalEntries).toBe(3);
197
+ expect(result.groomedEntries).toBe(2);
198
+ expect(result.ungroomedEntries).toBe(1);
199
+ // Just groomed, so they should be fresh
200
+ expect(result.freshEntries).toBe(2);
201
+ expect(result.staleEntries).toBe(0);
202
+ expect(result.avgDaysSinceGroom).toBeGreaterThanOrEqual(0);
203
+ expect(result.avgDaysSinceGroom).toBeLessThan(1);
204
+ });
205
+
206
+ it('should return zeroes for empty vault', async () => {
207
+ setup();
208
+ const result = (await findOp('curator_queue_stats').handler({})) as {
209
+ totalEntries: number;
210
+ groomedEntries: number;
211
+ ungroomedEntries: number;
212
+ };
213
+
214
+ expect(result.totalEntries).toBe(0);
215
+ expect(result.groomedEntries).toBe(0);
216
+ expect(result.ungroomedEntries).toBe(0);
217
+ });
218
+ });
219
+
220
+ // ─── curator_enrich ────────────────────────────────────────────
221
+
222
+ describe('curator_enrich', () => {
223
+ it('should enrich entry with messy metadata', async () => {
224
+ setup();
225
+ runtime.vault.seed([
226
+ {
227
+ id: 'enrich-1',
228
+ type: 'pattern',
229
+ domain: 'testing',
230
+ title: 'avoid using any types',
231
+ severity: 'suggestion',
232
+ description: ' You should avoid using any types in TypeScript. ',
233
+ tags: ['TypeScript', ' testing ', 'typescript'],
234
+ },
235
+ ]);
236
+
237
+ const result = (await findOp('curator_enrich').handler({
238
+ entryId: 'enrich-1',
239
+ })) as {
240
+ enriched: boolean;
241
+ changes: Array<{ field: string; before: string; after: string }>;
242
+ };
243
+
244
+ expect(result.enriched).toBe(true);
245
+ expect(result.changes.length).toBeGreaterThan(0);
246
+
247
+ // Check specific changes
248
+ const fieldNames = result.changes.map((c) => c.field);
249
+
250
+ // Title should be capitalized
251
+ expect(fieldNames).toContain('title');
252
+ const titleChange = result.changes.find((c) => c.field === 'title')!;
253
+ expect(titleChange.after).toBe('Avoid using any types');
254
+
255
+ // Tags should be normalized (lowercase, trimmed, deduped)
256
+ expect(fieldNames).toContain('tags');
257
+ const tagChange = result.changes.find((c) => c.field === 'tags')!;
258
+ const normalizedTags = JSON.parse(tagChange.after);
259
+ expect(normalizedTags).toEqual(['typescript', 'testing']);
260
+
261
+ // Type should be inferred as anti-pattern (starts with "avoid")
262
+ expect(fieldNames).toContain('type');
263
+ const typeChange = result.changes.find((c) => c.field === 'type')!;
264
+ expect(typeChange.after).toBe('anti-pattern');
265
+
266
+ // Description should be trimmed
267
+ expect(fieldNames).toContain('description');
268
+ const descChange = result.changes.find((c) => c.field === 'description')!;
269
+ expect(descChange.after).toBe('You should avoid using any types in TypeScript.');
270
+
271
+ // Verify the entry was actually updated in the vault
272
+ const updated = runtime.vault.get('enrich-1');
273
+ expect(updated).not.toBeNull();
274
+ expect(updated!.type).toBe('anti-pattern');
275
+ expect(updated!.title).toBe('Avoid using any types');
276
+
277
+ // Verify a snapshot was recorded
278
+ const history = runtime.curator.getVersionHistory('enrich-1');
279
+ expect(history.length).toBeGreaterThan(0);
280
+ });
281
+
282
+ it('should return enriched false for clean entry', async () => {
283
+ setup();
284
+ runtime.vault.seed([
285
+ {
286
+ id: 'enrich-2',
287
+ type: 'pattern',
288
+ domain: 'testing',
289
+ title: 'Clean entry with proper metadata',
290
+ severity: 'warning',
291
+ description: 'This entry is already clean.',
292
+ tags: ['clean', 'testing'],
293
+ },
294
+ ]);
295
+
296
+ const result = (await findOp('curator_enrich').handler({
297
+ entryId: 'enrich-2',
298
+ })) as {
299
+ enriched: boolean;
300
+ changes: Array<{ field: string; before: string; after: string }>;
301
+ };
302
+
303
+ expect(result.enriched).toBe(false);
304
+ expect(result.changes).toEqual([]);
305
+ });
306
+
307
+ it('should return enriched false for missing entry', async () => {
308
+ setup();
309
+ const result = (await findOp('curator_enrich').handler({
310
+ entryId: 'nonexistent',
311
+ })) as { enriched: boolean };
312
+
313
+ expect(result.enriched).toBe(false);
314
+ });
315
+
316
+ it('should infer severity from critical keywords', async () => {
317
+ setup();
318
+ runtime.vault.seed([
319
+ {
320
+ id: 'enrich-3',
321
+ type: 'rule',
322
+ domain: 'security',
323
+ title: 'Never expose API keys',
324
+ severity: 'suggestion',
325
+ description: 'API keys must not be committed to version control.',
326
+ tags: ['security'],
327
+ },
328
+ ]);
329
+
330
+ const result = (await findOp('curator_enrich').handler({
331
+ entryId: 'enrich-3',
332
+ })) as {
333
+ enriched: boolean;
334
+ changes: Array<{ field: string; before: string; after: string }>;
335
+ };
336
+
337
+ expect(result.enriched).toBe(true);
338
+ const severityChange = result.changes.find((c) => c.field === 'severity');
339
+ expect(severityChange).toBeDefined();
340
+ expect(severityChange!.after).toBe('critical');
341
+ });
342
+ });
343
+
344
+ // ─── Auth levels ───────────────────────────────────────────────
345
+
346
+ describe('auth levels', () => {
347
+ it('should use read auth for query ops', () => {
348
+ setup();
349
+ expect(findOp('curator_entry_history').auth).toBe('read');
350
+ expect(findOp('curator_queue_stats').auth).toBe('read');
351
+ });
352
+
353
+ it('should use write auth for mutation ops', () => {
354
+ setup();
355
+ expect(findOp('curator_record_snapshot').auth).toBe('write');
356
+ expect(findOp('curator_enrich').auth).toBe('write');
357
+ });
358
+ });
359
+ });
@@ -121,8 +121,16 @@ describe('Curator', () => {
121
121
  describe('Duplicate Detection', () => {
122
122
  it('should detect duplicates above threshold', () => {
123
123
  vault.seed([
124
- makeEntry({ id: 'dup-1', title: 'Use semantic tokens for colors', description: 'Always use semantic tokens instead of raw hex values for color styling.' }),
125
- makeEntry({ id: 'dup-2', title: 'Use semantic tokens for color values', description: 'Prefer semantic color tokens over raw hex or rgb values in styling.' }),
124
+ makeEntry({
125
+ id: 'dup-1',
126
+ title: 'Use semantic tokens for colors',
127
+ description: 'Always use semantic tokens instead of raw hex values for color styling.',
128
+ }),
129
+ makeEntry({
130
+ id: 'dup-2',
131
+ title: 'Use semantic tokens for color values',
132
+ description: 'Prefer semantic color tokens over raw hex or rgb values in styling.',
133
+ }),
126
134
  ]);
127
135
  const results = curator.detectDuplicates(undefined, 0.3);
128
136
  expect(results.length).toBeGreaterThan(0);
@@ -134,8 +142,16 @@ describe('Curator', () => {
134
142
 
135
143
  it('should not detect duplicates below threshold', () => {
136
144
  vault.seed([
137
- makeEntry({ id: 'uniq-1', title: 'Database indexing strategies', description: 'Create indices on frequently queried columns.' }),
138
- makeEntry({ id: 'uniq-2', title: 'React component lifecycle', description: 'Use useEffect for side effects in functional components.' }),
145
+ makeEntry({
146
+ id: 'uniq-1',
147
+ title: 'Database indexing strategies',
148
+ description: 'Create indices on frequently queried columns.',
149
+ }),
150
+ makeEntry({
151
+ id: 'uniq-2',
152
+ title: 'React component lifecycle',
153
+ description: 'Use useEffect for side effects in functional components.',
154
+ }),
139
155
  ]);
140
156
  const results = curator.detectDuplicates(undefined, 0.8);
141
157
  expect(results.length).toBe(0);
@@ -143,9 +159,21 @@ describe('Curator', () => {
143
159
 
144
160
  it('should detect duplicates for a specific entry', () => {
145
161
  vault.seed([
146
- makeEntry({ id: 'spec-1', title: 'Authentication with JWT tokens', description: 'Use JSON Web Tokens for stateless authentication.' }),
147
- makeEntry({ id: 'spec-2', title: 'JWT token authentication pattern', description: 'Implement JWT-based authentication for API endpoints.' }),
148
- makeEntry({ id: 'spec-3', title: 'Database connection pooling', description: 'Use connection pools for efficient database access.' }),
162
+ makeEntry({
163
+ id: 'spec-1',
164
+ title: 'Authentication with JWT tokens',
165
+ description: 'Use JSON Web Tokens for stateless authentication.',
166
+ }),
167
+ makeEntry({
168
+ id: 'spec-2',
169
+ title: 'JWT token authentication pattern',
170
+ description: 'Implement JWT-based authentication for API endpoints.',
171
+ }),
172
+ makeEntry({
173
+ id: 'spec-3',
174
+ title: 'Database connection pooling',
175
+ description: 'Use connection pools for efficient database access.',
176
+ }),
149
177
  ]);
150
178
  const results = curator.detectDuplicates('spec-1', 0.3);
151
179
  expect(results.length).toBe(1);
@@ -154,8 +182,16 @@ describe('Curator', () => {
154
182
 
155
183
  it('should suggest merge for high similarity', () => {
156
184
  vault.seed([
157
- makeEntry({ id: 'merge-1', title: 'Validate user input', description: 'Always validate and sanitize user input before processing.' }),
158
- makeEntry({ id: 'merge-2', title: 'Validate user input', description: 'Always validate and sanitize user input before processing.' }),
185
+ makeEntry({
186
+ id: 'merge-1',
187
+ title: 'Validate user input',
188
+ description: 'Always validate and sanitize user input before processing.',
189
+ }),
190
+ makeEntry({
191
+ id: 'merge-2',
192
+ title: 'Validate user input',
193
+ description: 'Always validate and sanitize user input before processing.',
194
+ }),
159
195
  ]);
160
196
  const results = curator.detectDuplicates(undefined, 0.3);
161
197
  if (results.length > 0 && results[0].matches.length > 0) {
@@ -185,7 +221,8 @@ describe('Curator', () => {
185
221
  id: 'ap-inline',
186
222
  type: 'anti-pattern',
187
223
  title: 'Avoid inline styles for styling',
188
- description: 'Never use inline styles — prefer CSS classes or Tailwind utilities for styling.',
224
+ description:
225
+ 'Never use inline styles — prefer CSS classes or Tailwind utilities for styling.',
189
226
  tags: ['styling'],
190
227
  }),
191
228
  ]);
@@ -219,8 +256,18 @@ describe('Curator', () => {
219
256
 
220
257
  it('should respect UNIQUE constraint — no duplicate contradictions', () => {
221
258
  vault.seed([
222
- makeEntry({ id: 'p-dup', type: 'pattern', title: 'Use inline styles', description: 'Apply inline styles for dynamic values.' }),
223
- makeEntry({ id: 'ap-dup', type: 'anti-pattern', title: 'Avoid inline styles', description: 'Do not use inline styles.' }),
259
+ makeEntry({
260
+ id: 'p-dup',
261
+ type: 'pattern',
262
+ title: 'Use inline styles',
263
+ description: 'Apply inline styles for dynamic values.',
264
+ }),
265
+ makeEntry({
266
+ id: 'ap-dup',
267
+ type: 'anti-pattern',
268
+ title: 'Avoid inline styles',
269
+ description: 'Do not use inline styles.',
270
+ }),
224
271
  ]);
225
272
  curator.detectContradictions(0.2);
226
273
  const first = curator.getContradictions();
@@ -231,8 +278,18 @@ describe('Curator', () => {
231
278
 
232
279
  it('should resolve a contradiction', () => {
233
280
  vault.seed([
234
- makeEntry({ id: 'p-res', type: 'pattern', title: 'Use inline styles', description: 'Apply inline styles.' }),
235
- makeEntry({ id: 'ap-res', type: 'anti-pattern', title: 'Avoid inline styles', description: 'Do not use inline styles.' }),
281
+ makeEntry({
282
+ id: 'p-res',
283
+ type: 'pattern',
284
+ title: 'Use inline styles',
285
+ description: 'Apply inline styles.',
286
+ }),
287
+ makeEntry({
288
+ id: 'ap-res',
289
+ type: 'anti-pattern',
290
+ title: 'Avoid inline styles',
291
+ description: 'Do not use inline styles.',
292
+ }),
236
293
  ]);
237
294
  curator.detectContradictions(0.2);
238
295
  const all = curator.getContradictions();
@@ -245,8 +302,18 @@ describe('Curator', () => {
245
302
 
246
303
  it('should dismiss a contradiction', () => {
247
304
  vault.seed([
248
- makeEntry({ id: 'p-dis', type: 'pattern', title: 'Use inline styles', description: 'Apply inline styles.' }),
249
- makeEntry({ id: 'ap-dis', type: 'anti-pattern', title: 'Avoid inline styles', description: 'Do not use inline styles.' }),
305
+ makeEntry({
306
+ id: 'p-dis',
307
+ type: 'pattern',
308
+ title: 'Use inline styles',
309
+ description: 'Apply inline styles.',
310
+ }),
311
+ makeEntry({
312
+ id: 'ap-dis',
313
+ type: 'anti-pattern',
314
+ title: 'Avoid inline styles',
315
+ description: 'Do not use inline styles.',
316
+ }),
250
317
  ]);
251
318
  curator.detectContradictions(0.2);
252
319
  const all = curator.getContradictions();
@@ -256,8 +323,18 @@ describe('Curator', () => {
256
323
 
257
324
  it('should list by status', () => {
258
325
  vault.seed([
259
- makeEntry({ id: 'p-ls', type: 'pattern', title: 'Use inline styles', description: 'Apply inline styles.' }),
260
- makeEntry({ id: 'ap-ls', type: 'anti-pattern', title: 'Avoid inline styles', description: 'Do not use inline styles.' }),
326
+ makeEntry({
327
+ id: 'p-ls',
328
+ type: 'pattern',
329
+ title: 'Use inline styles',
330
+ description: 'Apply inline styles.',
331
+ }),
332
+ makeEntry({
333
+ id: 'ap-ls',
334
+ type: 'anti-pattern',
335
+ title: 'Avoid inline styles',
336
+ description: 'Do not use inline styles.',
337
+ }),
261
338
  ]);
262
339
  curator.detectContradictions(0.2);
263
340
  const open = curator.getContradictions('open');
@@ -295,7 +372,9 @@ describe('Curator', () => {
295
372
  curator.groomEntry('groom-state');
296
373
 
297
374
  const db = vault.getDb();
298
- const row = db.prepare('SELECT * FROM curator_entry_state WHERE entry_id = ?').get('groom-state') as Record<string, unknown>;
375
+ const row = db
376
+ .prepare('SELECT * FROM curator_entry_state WHERE entry_id = ?')
377
+ .get('groom-state') as Record<string, unknown>;
299
378
  expect(row).toBeDefined();
300
379
  expect(row.status).toBe('active');
301
380
  expect(row.last_groomed_at).not.toBeNull();
@@ -336,8 +415,16 @@ describe('Curator', () => {
336
415
 
337
416
  it('should find issues in dry-run', () => {
338
417
  vault.seed([
339
- makeEntry({ id: 'con-1', title: 'Validate user input thoroughly', description: 'Always validate and sanitize all user input before processing.' }),
340
- makeEntry({ id: 'con-2', title: 'Validate user input thoroughly', description: 'Always validate and sanitize all user input before processing.' }),
418
+ makeEntry({
419
+ id: 'con-1',
420
+ title: 'Validate user input thoroughly',
421
+ description: 'Always validate and sanitize all user input before processing.',
422
+ }),
423
+ makeEntry({
424
+ id: 'con-2',
425
+ title: 'Validate user input thoroughly',
426
+ description: 'Always validate and sanitize all user input before processing.',
427
+ }),
341
428
  ]);
342
429
  const result = curator.consolidate({ dryRun: true, duplicateThreshold: 0.3 });
343
430
  expect(result.dryRun).toBe(true);
@@ -369,14 +456,24 @@ describe('Curator', () => {
369
456
  expect(result.mutations).toBeGreaterThan(0);
370
457
 
371
458
  // Check that entry state was archived
372
- const row = db.prepare('SELECT status FROM curator_entry_state WHERE entry_id = ?').get('stale-con') as { status: string };
459
+ const row = db
460
+ .prepare('SELECT status FROM curator_entry_state WHERE entry_id = ?')
461
+ .get('stale-con') as { status: string };
373
462
  expect(row.status).toBe('archived');
374
463
  });
375
464
 
376
465
  it('should remove duplicates when not dry-run', () => {
377
466
  vault.seed([
378
- makeEntry({ id: 'rem-1', title: 'Duplicate pattern for removal', description: 'This is a duplicate pattern that should be removed during consolidation.' }),
379
- makeEntry({ id: 'rem-2', title: 'Duplicate pattern for removal', description: 'This is a duplicate pattern that should be removed during consolidation.' }),
467
+ makeEntry({
468
+ id: 'rem-1',
469
+ title: 'Duplicate pattern for removal',
470
+ description: 'This is a duplicate pattern that should be removed during consolidation.',
471
+ }),
472
+ makeEntry({
473
+ id: 'rem-2',
474
+ title: 'Duplicate pattern for removal',
475
+ description: 'This is a duplicate pattern that should be removed during consolidation.',
476
+ }),
380
477
  ]);
381
478
  const result = curator.consolidate({ dryRun: false, duplicateThreshold: 0.3 });
382
479
  expect(result.mutations).toBeGreaterThan(0);
@@ -440,9 +537,7 @@ describe('Curator', () => {
440
537
  });
441
538
 
442
539
  it('should penalize for missing entry types', () => {
443
- vault.seed([
444
- makeEntry({ id: 'h-p1', type: 'pattern', tags: ['x'] }),
445
- ]);
540
+ vault.seed([makeEntry({ id: 'h-p1', type: 'pattern', tags: ['x'] })]);
446
541
  curator.groomAll();
447
542
  const result = curator.healthAudit();
448
543
  expect(result.score).toBeLessThan(100);
@@ -451,9 +546,7 @@ describe('Curator', () => {
451
546
  });
452
547
 
453
548
  it('should recommend actions for issues', () => {
454
- vault.seed([
455
- makeEntry({ id: 'h-rec', type: 'pattern', tags: [] }),
456
- ]);
549
+ vault.seed([makeEntry({ id: 'h-rec', type: 'pattern', tags: [] })]);
457
550
  const result = curator.healthAudit();
458
551
  expect(result.recommendations.length).toBeGreaterThan(0);
459
552
  });
@@ -461,7 +554,9 @@ describe('Curator', () => {
461
554
  it('should handle empty vault gracefully', () => {
462
555
  const result = curator.healthAudit();
463
556
  expect(result.score).toBe(100);
464
- expect(result.recommendations).toContain('Vault is empty — add knowledge entries to get started.');
557
+ expect(result.recommendations).toContain(
558
+ 'Vault is empty — add knowledge entries to get started.',
559
+ );
465
560
  });
466
561
 
467
562
  it('should include tag health metrics', () => {