@soleri/core 2.1.0 → 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 (207) hide show
  1. package/dist/brain/brain.d.ts +3 -1
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +60 -4
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +36 -1
  6. package/dist/brain/intelligence.d.ts.map +1 -1
  7. package/dist/brain/intelligence.js +119 -14
  8. package/dist/brain/intelligence.js.map +1 -1
  9. package/dist/brain/types.d.ts +32 -0
  10. package/dist/brain/types.d.ts.map +1 -1
  11. package/dist/control/identity-manager.d.ts +22 -0
  12. package/dist/control/identity-manager.d.ts.map +1 -0
  13. package/dist/control/identity-manager.js +233 -0
  14. package/dist/control/identity-manager.js.map +1 -0
  15. package/dist/control/intent-router.d.ts +32 -0
  16. package/dist/control/intent-router.d.ts.map +1 -0
  17. package/dist/control/intent-router.js +242 -0
  18. package/dist/control/intent-router.js.map +1 -0
  19. package/dist/control/types.d.ts +68 -0
  20. package/dist/control/types.d.ts.map +1 -0
  21. package/dist/control/types.js +9 -0
  22. package/dist/control/types.js.map +1 -0
  23. package/dist/curator/curator.d.ts +29 -0
  24. package/dist/curator/curator.d.ts.map +1 -1
  25. package/dist/curator/curator.js +135 -0
  26. package/dist/curator/curator.js.map +1 -1
  27. package/dist/facades/types.d.ts +1 -1
  28. package/dist/governance/governance.d.ts +42 -0
  29. package/dist/governance/governance.d.ts.map +1 -0
  30. package/dist/governance/governance.js +488 -0
  31. package/dist/governance/governance.js.map +1 -0
  32. package/dist/governance/index.d.ts +3 -0
  33. package/dist/governance/index.d.ts.map +1 -0
  34. package/dist/governance/index.js +2 -0
  35. package/dist/governance/index.js.map +1 -0
  36. package/dist/governance/types.d.ts +102 -0
  37. package/dist/governance/types.d.ts.map +1 -0
  38. package/dist/governance/types.js +3 -0
  39. package/dist/governance/types.js.map +1 -0
  40. package/dist/index.d.ts +32 -3
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +29 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/logging/logger.d.ts +37 -0
  45. package/dist/logging/logger.d.ts.map +1 -0
  46. package/dist/logging/logger.js +145 -0
  47. package/dist/logging/logger.js.map +1 -0
  48. package/dist/logging/types.d.ts +19 -0
  49. package/dist/logging/types.d.ts.map +1 -0
  50. package/dist/logging/types.js +2 -0
  51. package/dist/logging/types.js.map +1 -0
  52. package/dist/loop/loop-manager.d.ts +49 -0
  53. package/dist/loop/loop-manager.d.ts.map +1 -0
  54. package/dist/loop/loop-manager.js +105 -0
  55. package/dist/loop/loop-manager.js.map +1 -0
  56. package/dist/loop/types.d.ts +35 -0
  57. package/dist/loop/types.d.ts.map +1 -0
  58. package/dist/loop/types.js +8 -0
  59. package/dist/loop/types.js.map +1 -0
  60. package/dist/planning/gap-analysis.d.ts +29 -0
  61. package/dist/planning/gap-analysis.d.ts.map +1 -0
  62. package/dist/planning/gap-analysis.js +265 -0
  63. package/dist/planning/gap-analysis.js.map +1 -0
  64. package/dist/planning/gap-types.d.ts +29 -0
  65. package/dist/planning/gap-types.d.ts.map +1 -0
  66. package/dist/planning/gap-types.js +28 -0
  67. package/dist/planning/gap-types.js.map +1 -0
  68. package/dist/planning/planner.d.ts +150 -1
  69. package/dist/planning/planner.d.ts.map +1 -1
  70. package/dist/planning/planner.js +365 -2
  71. package/dist/planning/planner.js.map +1 -1
  72. package/dist/project/project-registry.d.ts +79 -0
  73. package/dist/project/project-registry.d.ts.map +1 -0
  74. package/dist/project/project-registry.js +276 -0
  75. package/dist/project/project-registry.js.map +1 -0
  76. package/dist/project/types.d.ts +28 -0
  77. package/dist/project/types.d.ts.map +1 -0
  78. package/dist/project/types.js +5 -0
  79. package/dist/project/types.js.map +1 -0
  80. package/dist/runtime/admin-extra-ops.d.ts +13 -0
  81. package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
  82. package/dist/runtime/admin-extra-ops.js +284 -0
  83. package/dist/runtime/admin-extra-ops.js.map +1 -0
  84. package/dist/runtime/admin-ops.d.ts +15 -0
  85. package/dist/runtime/admin-ops.d.ts.map +1 -0
  86. package/dist/runtime/admin-ops.js +322 -0
  87. package/dist/runtime/admin-ops.js.map +1 -0
  88. package/dist/runtime/capture-ops.d.ts +15 -0
  89. package/dist/runtime/capture-ops.d.ts.map +1 -0
  90. package/dist/runtime/capture-ops.js +345 -0
  91. package/dist/runtime/capture-ops.js.map +1 -0
  92. package/dist/runtime/core-ops.d.ts +7 -3
  93. package/dist/runtime/core-ops.d.ts.map +1 -1
  94. package/dist/runtime/core-ops.js +474 -8
  95. package/dist/runtime/core-ops.js.map +1 -1
  96. package/dist/runtime/curator-extra-ops.d.ts +9 -0
  97. package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
  98. package/dist/runtime/curator-extra-ops.js +59 -0
  99. package/dist/runtime/curator-extra-ops.js.map +1 -0
  100. package/dist/runtime/domain-ops.d.ts.map +1 -1
  101. package/dist/runtime/domain-ops.js +59 -13
  102. package/dist/runtime/domain-ops.js.map +1 -1
  103. package/dist/runtime/grading-ops.d.ts +14 -0
  104. package/dist/runtime/grading-ops.d.ts.map +1 -0
  105. package/dist/runtime/grading-ops.js +105 -0
  106. package/dist/runtime/grading-ops.js.map +1 -0
  107. package/dist/runtime/loop-ops.d.ts +13 -0
  108. package/dist/runtime/loop-ops.d.ts.map +1 -0
  109. package/dist/runtime/loop-ops.js +179 -0
  110. package/dist/runtime/loop-ops.js.map +1 -0
  111. package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
  112. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
  113. package/dist/runtime/memory-cross-project-ops.js +165 -0
  114. package/dist/runtime/memory-cross-project-ops.js.map +1 -0
  115. package/dist/runtime/memory-extra-ops.d.ts +13 -0
  116. package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
  117. package/dist/runtime/memory-extra-ops.js +173 -0
  118. package/dist/runtime/memory-extra-ops.js.map +1 -0
  119. package/dist/runtime/orchestrate-ops.d.ts +17 -0
  120. package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
  121. package/dist/runtime/orchestrate-ops.js +240 -0
  122. package/dist/runtime/orchestrate-ops.js.map +1 -0
  123. package/dist/runtime/planning-extra-ops.d.ts +17 -0
  124. package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
  125. package/dist/runtime/planning-extra-ops.js +300 -0
  126. package/dist/runtime/planning-extra-ops.js.map +1 -0
  127. package/dist/runtime/project-ops.d.ts +15 -0
  128. package/dist/runtime/project-ops.d.ts.map +1 -0
  129. package/dist/runtime/project-ops.js +181 -0
  130. package/dist/runtime/project-ops.js.map +1 -0
  131. package/dist/runtime/runtime.d.ts.map +1 -1
  132. package/dist/runtime/runtime.js +44 -1
  133. package/dist/runtime/runtime.js.map +1 -1
  134. package/dist/runtime/types.d.ts +21 -0
  135. package/dist/runtime/types.d.ts.map +1 -1
  136. package/dist/runtime/vault-extra-ops.d.ts +9 -0
  137. package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
  138. package/dist/runtime/vault-extra-ops.js +195 -0
  139. package/dist/runtime/vault-extra-ops.js.map +1 -0
  140. package/dist/telemetry/telemetry.d.ts +48 -0
  141. package/dist/telemetry/telemetry.d.ts.map +1 -0
  142. package/dist/telemetry/telemetry.js +87 -0
  143. package/dist/telemetry/telemetry.js.map +1 -0
  144. package/dist/vault/vault.d.ts +94 -0
  145. package/dist/vault/vault.d.ts.map +1 -1
  146. package/dist/vault/vault.js +340 -1
  147. package/dist/vault/vault.js.map +1 -1
  148. package/package.json +1 -1
  149. package/src/__tests__/admin-extra-ops.test.ts +420 -0
  150. package/src/__tests__/admin-ops.test.ts +271 -0
  151. package/src/__tests__/brain-intelligence.test.ts +205 -0
  152. package/src/__tests__/brain.test.ts +131 -0
  153. package/src/__tests__/capture-ops.test.ts +509 -0
  154. package/src/__tests__/core-ops.test.ts +266 -2
  155. package/src/__tests__/curator-extra-ops.test.ts +359 -0
  156. package/src/__tests__/domain-ops.test.ts +66 -0
  157. package/src/__tests__/governance.test.ts +522 -0
  158. package/src/__tests__/grading-ops.test.ts +340 -0
  159. package/src/__tests__/identity-manager.test.ts +243 -0
  160. package/src/__tests__/intent-router.test.ts +222 -0
  161. package/src/__tests__/logger.test.ts +200 -0
  162. package/src/__tests__/loop-ops.test.ts +398 -0
  163. package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
  164. package/src/__tests__/memory-extra-ops.test.ts +352 -0
  165. package/src/__tests__/orchestrate-ops.test.ts +284 -0
  166. package/src/__tests__/planner.test.ts +331 -0
  167. package/src/__tests__/planning-extra-ops.test.ts +548 -0
  168. package/src/__tests__/project-ops.test.ts +367 -0
  169. package/src/__tests__/vault-extra-ops.test.ts +407 -0
  170. package/src/brain/brain.ts +114 -7
  171. package/src/brain/intelligence.ts +179 -10
  172. package/src/brain/types.ts +38 -0
  173. package/src/control/identity-manager.ts +354 -0
  174. package/src/control/intent-router.ts +326 -0
  175. package/src/control/types.ts +102 -0
  176. package/src/curator/curator.ts +213 -0
  177. package/src/governance/governance.ts +698 -0
  178. package/src/governance/index.ts +18 -0
  179. package/src/governance/types.ts +111 -0
  180. package/src/index.ts +102 -2
  181. package/src/logging/logger.ts +154 -0
  182. package/src/logging/types.ts +21 -0
  183. package/src/loop/loop-manager.ts +130 -0
  184. package/src/loop/types.ts +44 -0
  185. package/src/planning/gap-analysis.ts +506 -0
  186. package/src/planning/gap-types.ts +58 -0
  187. package/src/planning/planner.ts +478 -2
  188. package/src/project/project-registry.ts +358 -0
  189. package/src/project/types.ts +31 -0
  190. package/src/runtime/admin-extra-ops.ts +307 -0
  191. package/src/runtime/admin-ops.ts +329 -0
  192. package/src/runtime/capture-ops.ts +385 -0
  193. package/src/runtime/core-ops.ts +535 -7
  194. package/src/runtime/curator-extra-ops.ts +71 -0
  195. package/src/runtime/domain-ops.ts +65 -13
  196. package/src/runtime/grading-ops.ts +121 -0
  197. package/src/runtime/loop-ops.ts +194 -0
  198. package/src/runtime/memory-cross-project-ops.ts +192 -0
  199. package/src/runtime/memory-extra-ops.ts +186 -0
  200. package/src/runtime/orchestrate-ops.ts +272 -0
  201. package/src/runtime/planning-extra-ops.ts +327 -0
  202. package/src/runtime/project-ops.ts +196 -0
  203. package/src/runtime/runtime.ts +49 -1
  204. package/src/runtime/types.ts +21 -0
  205. package/src/runtime/vault-extra-ops.ts +225 -0
  206. package/src/telemetry/telemetry.ts +118 -0
  207. package/src/vault/vault.ts +412 -1
@@ -34,8 +34,8 @@ describe('createCoreOps', () => {
34
34
  return op;
35
35
  }
36
36
 
37
- it('should return 37 ops', () => {
38
- expect(ops.length).toBe(37);
37
+ it('should return 147 ops', () => {
38
+ expect(ops.length).toBe(147);
39
39
  });
40
40
 
41
41
  it('should have all expected op names', () => {
@@ -60,6 +60,8 @@ describe('createCoreOps', () => {
60
60
  expect(names).toContain('complete_plan');
61
61
  // Brain
62
62
  expect(names).toContain('record_feedback');
63
+ expect(names).toContain('brain_feedback');
64
+ expect(names).toContain('brain_feedback_stats');
63
65
  expect(names).toContain('rebuild_vocabulary');
64
66
  expect(names).toContain('brain_stats');
65
67
  expect(names).toContain('llm_status');
@@ -75,6 +77,7 @@ describe('createCoreOps', () => {
75
77
  expect(names).toContain('brain_archive_sessions');
76
78
  expect(names).toContain('brain_promote_proposals');
77
79
  expect(names).toContain('brain_lifecycle');
80
+ expect(names).toContain('brain_reset_extracted');
78
81
  // Curator
79
82
  expect(names).toContain('curator_status');
80
83
  expect(names).toContain('curator_detect_duplicates');
@@ -84,6 +87,134 @@ describe('createCoreOps', () => {
84
87
  expect(names).toContain('curator_groom_all');
85
88
  expect(names).toContain('curator_consolidate');
86
89
  expect(names).toContain('curator_health_audit');
90
+ // Control
91
+ expect(names).toContain('get_identity');
92
+ expect(names).toContain('update_identity');
93
+ expect(names).toContain('add_guideline');
94
+ expect(names).toContain('remove_guideline');
95
+ expect(names).toContain('rollback_identity');
96
+ expect(names).toContain('route_intent');
97
+ expect(names).toContain('morph');
98
+ expect(names).toContain('get_behavior_rules');
99
+ // Cognee
100
+ expect(names).toContain('cognee_status');
101
+ expect(names).toContain('cognee_search');
102
+ expect(names).toContain('cognee_add');
103
+ expect(names).toContain('cognee_cognify');
104
+ expect(names).toContain('cognee_config');
105
+ // LLM
106
+ expect(names).toContain('llm_rotate');
107
+ expect(names).toContain('llm_call');
108
+ // Governance
109
+ expect(names).toContain('governance_policy');
110
+ expect(names).toContain('governance_proposals');
111
+ expect(names).toContain('governance_stats');
112
+ expect(names).toContain('governance_expire');
113
+ expect(names).toContain('governance_dashboard');
114
+ // Planning Extra (9)
115
+ expect(names).toContain('plan_iterate');
116
+ expect(names).toContain('plan_split');
117
+ expect(names).toContain('plan_reconcile');
118
+ expect(names).toContain('plan_complete_lifecycle');
119
+ expect(names).toContain('plan_dispatch');
120
+ expect(names).toContain('plan_review');
121
+ expect(names).toContain('plan_archive');
122
+ expect(names).toContain('plan_list_tasks');
123
+ expect(names).toContain('plan_stats');
124
+ // Memory Extra (8)
125
+ expect(names).toContain('memory_delete');
126
+ expect(names).toContain('memory_stats');
127
+ expect(names).toContain('memory_export');
128
+ expect(names).toContain('memory_import');
129
+ expect(names).toContain('memory_prune');
130
+ expect(names).toContain('memory_deduplicate');
131
+ expect(names).toContain('memory_topics');
132
+ expect(names).toContain('memory_by_project');
133
+ // Vault Extra (12)
134
+ expect(names).toContain('vault_get');
135
+ expect(names).toContain('vault_update');
136
+ expect(names).toContain('vault_remove');
137
+ expect(names).toContain('vault_bulk_add');
138
+ expect(names).toContain('vault_bulk_remove');
139
+ expect(names).toContain('vault_tags');
140
+ expect(names).toContain('vault_domains');
141
+ expect(names).toContain('vault_recent');
142
+ expect(names).toContain('vault_import');
143
+ expect(names).toContain('vault_seed');
144
+ expect(names).toContain('vault_backup');
145
+ expect(names).toContain('vault_age_report');
146
+ // Admin (8)
147
+ expect(names).toContain('admin_health');
148
+ expect(names).toContain('admin_tool_list');
149
+ expect(names).toContain('admin_config');
150
+ expect(names).toContain('admin_vault_size');
151
+ expect(names).toContain('admin_uptime');
152
+ expect(names).toContain('admin_version');
153
+ expect(names).toContain('admin_reset_cache');
154
+ expect(names).toContain('admin_diagnostic');
155
+ // Admin Extra (10)
156
+ expect(names).toContain('admin_telemetry');
157
+ expect(names).toContain('admin_telemetry_recent');
158
+ expect(names).toContain('admin_telemetry_reset');
159
+ expect(names).toContain('admin_permissions');
160
+ expect(names).toContain('admin_vault_analytics');
161
+ expect(names).toContain('admin_search_insights');
162
+ expect(names).toContain('admin_module_status');
163
+ expect(names).toContain('admin_env');
164
+ expect(names).toContain('admin_gc');
165
+ expect(names).toContain('admin_export_config');
166
+ // Loop (7)
167
+ expect(names).toContain('loop_start');
168
+ expect(names).toContain('loop_iterate');
169
+ expect(names).toContain('loop_status');
170
+ expect(names).toContain('loop_cancel');
171
+ expect(names).toContain('loop_history');
172
+ expect(names).toContain('loop_is_active');
173
+ expect(names).toContain('loop_complete');
174
+ // Orchestrate (5)
175
+ expect(names).toContain('orchestrate_plan');
176
+ expect(names).toContain('orchestrate_execute');
177
+ expect(names).toContain('orchestrate_complete');
178
+ expect(names).toContain('orchestrate_status');
179
+ expect(names).toContain('orchestrate_quick_capture');
180
+ // Grading (5)
181
+ expect(names).toContain('plan_grade');
182
+ expect(names).toContain('plan_check_history');
183
+ expect(names).toContain('plan_latest_check');
184
+ expect(names).toContain('plan_meets_grade');
185
+ expect(names).toContain('plan_auto_improve');
186
+ // Capture (4)
187
+ expect(names).toContain('capture_knowledge');
188
+ expect(names).toContain('capture_quick');
189
+ expect(names).toContain('search_intelligent');
190
+ expect(names).toContain('search_feedback');
191
+ // Curator Extra (4)
192
+ expect(names).toContain('curator_entry_history');
193
+ expect(names).toContain('curator_record_snapshot');
194
+ expect(names).toContain('curator_queue_stats');
195
+ expect(names).toContain('curator_enrich');
196
+ // Project (12)
197
+ expect(names).toContain('project_get');
198
+ expect(names).toContain('project_list');
199
+ expect(names).toContain('project_unregister');
200
+ expect(names).toContain('project_get_rules');
201
+ expect(names).toContain('project_list_rules');
202
+ expect(names).toContain('project_add_rule');
203
+ expect(names).toContain('project_remove_rule');
204
+ expect(names).toContain('project_link');
205
+ expect(names).toContain('project_unlink');
206
+ expect(names).toContain('project_get_links');
207
+ expect(names).toContain('project_linked_projects');
208
+ expect(names).toContain('project_touch');
209
+ });
210
+
211
+ it('register should include governance summary', async () => {
212
+ const result = (await findOp('register').handler({ projectPath: '/tmp/test-gov-reg' })) as {
213
+ governance: { pendingProposals: number; quotaPercent: number; isQuotaWarning: boolean };
214
+ };
215
+ expect(typeof result.governance.pendingProposals).toBe('number');
216
+ expect(typeof result.governance.quotaPercent).toBe('number');
217
+ expect(typeof result.governance.isQuotaWarning).toBe('boolean');
87
218
  });
88
219
 
89
220
  it('search should query vault via brain', async () => {
@@ -196,6 +327,47 @@ describe('createCoreOps', () => {
196
327
  expect(results.length).toBeGreaterThan(0);
197
328
  });
198
329
 
330
+ it('brain_feedback should record enhanced feedback', async () => {
331
+ runtime.vault.seed([
332
+ {
333
+ id: 'bf-1',
334
+ type: 'pattern',
335
+ domain: 'testing',
336
+ title: 'Brain feedback test',
337
+ severity: 'warning',
338
+ description: 'Test.',
339
+ tags: ['test'],
340
+ },
341
+ ]);
342
+ ops = createCoreOps(runtime);
343
+ const result = (await findOp('brain_feedback').handler({
344
+ query: 'test',
345
+ entryId: 'bf-1',
346
+ action: 'modified',
347
+ source: 'recommendation',
348
+ confidence: 0.8,
349
+ })) as { id: number; action: string; source: string };
350
+ expect(result.id).toBeGreaterThan(0);
351
+ expect(result.action).toBe('modified');
352
+ expect(result.source).toBe('recommendation');
353
+ });
354
+
355
+ it('brain_feedback_stats should return stats', async () => {
356
+ const stats = (await findOp('brain_feedback_stats').handler({})) as {
357
+ total: number;
358
+ acceptanceRate: number;
359
+ };
360
+ expect(typeof stats.total).toBe('number');
361
+ expect(typeof stats.acceptanceRate).toBe('number');
362
+ });
363
+
364
+ it('brain_reset_extracted should return reset count', async () => {
365
+ const result = (await findOp('brain_reset_extracted').handler({ all: true })) as {
366
+ reset: number;
367
+ };
368
+ expect(typeof result.reset).toBe('number');
369
+ });
370
+
199
371
  it('export should return bundles', async () => {
200
372
  runtime.vault.seed([
201
373
  {
@@ -215,4 +387,96 @@ describe('createCoreOps', () => {
215
387
  expect(result.exported).toBe(true);
216
388
  expect(result.totalEntries).toBe(1);
217
389
  });
390
+
391
+ it('cognee_status should return health check result', async () => {
392
+ // Cognee is not running in tests — should degrade gracefully
393
+ const result = (await findOp('cognee_status').handler({})) as {
394
+ available: boolean;
395
+ url: string;
396
+ };
397
+ expect(typeof result.available).toBe('boolean');
398
+ expect(typeof result.url).toBe('string');
399
+ });
400
+
401
+ it('cognee_search should return empty when unavailable', async () => {
402
+ const results = (await findOp('cognee_search').handler({
403
+ query: 'test pattern',
404
+ })) as unknown[];
405
+ expect(results).toEqual([]);
406
+ });
407
+
408
+ it('cognee_add should return 0 when unavailable', async () => {
409
+ runtime.vault.seed([
410
+ {
411
+ id: 'cog-1',
412
+ type: 'pattern',
413
+ domain: 'testing',
414
+ title: 'Cognee test',
415
+ severity: 'warning',
416
+ description: 'Test.',
417
+ tags: ['test'],
418
+ },
419
+ ]);
420
+ const result = (await findOp('cognee_add').handler({
421
+ entryIds: ['cog-1'],
422
+ })) as { added: number };
423
+ expect(result.added).toBe(0);
424
+ });
425
+
426
+ it('cognee_cognify should return unavailable when Cognee is down', async () => {
427
+ const result = (await findOp('cognee_cognify').handler({})) as { status: string };
428
+ expect(result.status).toBe('unavailable');
429
+ });
430
+
431
+ it('cognee_config should return config and null status', async () => {
432
+ const result = (await findOp('cognee_config').handler({})) as {
433
+ config: { baseUrl: string; dataset: string };
434
+ cachedStatus: null;
435
+ };
436
+ expect(result.config.baseUrl).toBe('http://localhost:8000');
437
+ expect(result.config.dataset).toBe('test-core-ops');
438
+ expect(result.cachedStatus).toBeNull();
439
+ });
440
+
441
+ it('llm_rotate should return rotation status', async () => {
442
+ const result = (await findOp('llm_rotate').handler({ provider: 'openai' })) as {
443
+ rotated?: boolean;
444
+ poolSize?: number;
445
+ error?: string;
446
+ };
447
+ // Either reports no keys or successfully rotates
448
+ expect(typeof result.rotated === 'boolean' || typeof result.error === 'string').toBe(true);
449
+ });
450
+
451
+ it('governance_policy get should return defaults', async () => {
452
+ const result = (await findOp('governance_policy').handler({
453
+ action: 'get',
454
+ projectPath: '/test',
455
+ })) as { quotas: { maxEntriesTotal: number } };
456
+ expect(result.quotas.maxEntriesTotal).toBe(500);
457
+ });
458
+
459
+ it('governance_stats should return quota and proposal stats', async () => {
460
+ const result = (await findOp('governance_stats').handler({
461
+ projectPath: '/test',
462
+ })) as {
463
+ quotaStatus: { total: number };
464
+ proposalStats: { total: number };
465
+ };
466
+ expect(typeof result.quotaStatus.total).toBe('number');
467
+ expect(typeof result.proposalStats.total).toBe('number');
468
+ });
469
+
470
+ it('governance_dashboard should return combined view', async () => {
471
+ const result = (await findOp('governance_dashboard').handler({
472
+ projectPath: '/test',
473
+ })) as {
474
+ vaultSize: number;
475
+ quotaPercent: number;
476
+ pendingProposals: number;
477
+ };
478
+ expect(typeof result.vaultSize).toBe('number');
479
+ expect(typeof result.quotaPercent).toBe('number');
480
+ expect(result.pendingProposals).toBe(0);
481
+ });
218
482
  });
@@ -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
+ });