@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,407 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdirSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { createAgentRuntime } from '../runtime/runtime.js';
6
+ import { createVaultExtraOps } from '../runtime/vault-extra-ops.js';
7
+ import type { AgentRuntime } from '../runtime/types.js';
8
+ import type { OpDefinition } from '../facades/types.js';
9
+ import type { IntelligenceEntry } from '../intelligence/types.js';
10
+
11
+ function makeEntry(overrides: Partial<IntelligenceEntry> & { id: string }): IntelligenceEntry {
12
+ return {
13
+ type: 'pattern',
14
+ domain: 'testing',
15
+ title: 'Test entry',
16
+ severity: 'warning',
17
+ description: 'A test entry.',
18
+ tags: ['test'],
19
+ ...overrides,
20
+ };
21
+ }
22
+
23
+ describe('createVaultExtraOps', () => {
24
+ let runtime: AgentRuntime;
25
+ let ops: OpDefinition[];
26
+ let plannerDir: string;
27
+
28
+ beforeEach(() => {
29
+ plannerDir = join(tmpdir(), 'vault-extra-ops-test-' + Date.now());
30
+ mkdirSync(plannerDir, { recursive: true });
31
+ runtime = createAgentRuntime({
32
+ agentId: 'test-vault-extra',
33
+ vaultPath: ':memory:',
34
+ plansPath: join(plannerDir, 'plans.json'),
35
+ });
36
+ ops = createVaultExtraOps(runtime);
37
+ });
38
+
39
+ afterEach(() => {
40
+ runtime.close();
41
+ rmSync(plannerDir, { recursive: true, force: true });
42
+ });
43
+
44
+ function findOp(name: string): OpDefinition {
45
+ const op = ops.find((o) => o.name === name);
46
+ if (!op) throw new Error(`Op "${name}" not found`);
47
+ return op;
48
+ }
49
+
50
+ it('should return 12 ops', () => {
51
+ expect(ops.length).toBe(12);
52
+ });
53
+
54
+ it('should have all expected op names', () => {
55
+ const names = ops.map((o) => o.name);
56
+ expect(names).toContain('vault_get');
57
+ expect(names).toContain('vault_update');
58
+ expect(names).toContain('vault_remove');
59
+ expect(names).toContain('vault_bulk_add');
60
+ expect(names).toContain('vault_bulk_remove');
61
+ expect(names).toContain('vault_tags');
62
+ expect(names).toContain('vault_domains');
63
+ expect(names).toContain('vault_recent');
64
+ expect(names).toContain('vault_import');
65
+ expect(names).toContain('vault_seed');
66
+ expect(names).toContain('vault_backup');
67
+ expect(names).toContain('vault_age_report');
68
+ });
69
+
70
+ // ─── vault_get ────────────────────────────────────────────────────
71
+
72
+ it('vault_get should return entry when found', async () => {
73
+ runtime.vault.seed([makeEntry({ id: 'vg-1', title: 'Get test' })]);
74
+ const result = (await findOp('vault_get').handler({ id: 'vg-1' })) as IntelligenceEntry;
75
+ expect(result.id).toBe('vg-1');
76
+ expect(result.title).toBe('Get test');
77
+ });
78
+
79
+ it('vault_get should return error when not found', async () => {
80
+ const result = (await findOp('vault_get').handler({ id: 'nonexistent' })) as {
81
+ error: string;
82
+ };
83
+ expect(result.error).toContain('nonexistent');
84
+ });
85
+
86
+ // ─── vault_update ─────────────────────────────────────────────────
87
+
88
+ it('vault_update should update title', async () => {
89
+ runtime.vault.seed([makeEntry({ id: 'vu-1', title: 'Original' })]);
90
+ const result = (await findOp('vault_update').handler({
91
+ id: 'vu-1',
92
+ title: 'Updated',
93
+ })) as { updated: boolean; entry: IntelligenceEntry };
94
+ expect(result.updated).toBe(true);
95
+ expect(result.entry.title).toBe('Updated');
96
+ // Verify in vault directly
97
+ expect(runtime.vault.get('vu-1')!.title).toBe('Updated');
98
+ });
99
+
100
+ it('vault_update should update tags', async () => {
101
+ runtime.vault.seed([makeEntry({ id: 'vu-2', tags: ['old'] })]);
102
+ const result = (await findOp('vault_update').handler({
103
+ id: 'vu-2',
104
+ tags: ['new', 'shiny'],
105
+ })) as { updated: boolean; entry: IntelligenceEntry };
106
+ expect(result.entry.tags).toEqual(['new', 'shiny']);
107
+ });
108
+
109
+ it('vault_update should return error when entry not found', async () => {
110
+ const result = (await findOp('vault_update').handler({
111
+ id: 'missing',
112
+ title: 'x',
113
+ })) as { error: string };
114
+ expect(result.error).toContain('missing');
115
+ });
116
+
117
+ it('vault_update should return error when no fields provided', async () => {
118
+ runtime.vault.seed([makeEntry({ id: 'vu-3' })]);
119
+ const result = (await findOp('vault_update').handler({ id: 'vu-3' })) as { error: string };
120
+ expect(result.error).toContain('No fields');
121
+ });
122
+
123
+ // ─── vault_remove ─────────────────────────────────────────────────
124
+
125
+ it('vault_remove should delete entry', async () => {
126
+ runtime.vault.seed([makeEntry({ id: 'vr-1' })]);
127
+ const result = (await findOp('vault_remove').handler({ id: 'vr-1' })) as {
128
+ removed: boolean;
129
+ id: string;
130
+ };
131
+ expect(result.removed).toBe(true);
132
+ expect(result.id).toBe('vr-1');
133
+ expect(runtime.vault.get('vr-1')).toBeNull();
134
+ });
135
+
136
+ it('vault_remove should return removed=false for missing entry', async () => {
137
+ const result = (await findOp('vault_remove').handler({ id: 'nope' })) as { removed: boolean };
138
+ expect(result.removed).toBe(false);
139
+ });
140
+
141
+ // ─── vault_bulk_add ───────────────────────────────────────────────
142
+
143
+ it('vault_bulk_add should add multiple entries', async () => {
144
+ const entries = [
145
+ makeEntry({ id: 'ba-1', title: 'Bulk 1' }),
146
+ makeEntry({ id: 'ba-2', title: 'Bulk 2' }),
147
+ makeEntry({ id: 'ba-3', title: 'Bulk 3' }),
148
+ ];
149
+ const result = (await findOp('vault_bulk_add').handler({ entries })) as {
150
+ added: number;
151
+ total: number;
152
+ };
153
+ expect(result.added).toBe(3);
154
+ expect(result.total).toBe(3);
155
+ expect(runtime.vault.get('ba-2')!.title).toBe('Bulk 2');
156
+ });
157
+
158
+ it('vault_bulk_add should upsert existing entries', async () => {
159
+ runtime.vault.seed([makeEntry({ id: 'ba-u1', title: 'Old' })]);
160
+ const result = (await findOp('vault_bulk_add').handler({
161
+ entries: [makeEntry({ id: 'ba-u1', title: 'New' })],
162
+ })) as { added: number; total: number };
163
+ expect(result.added).toBe(1);
164
+ expect(result.total).toBe(1); // no new entry, just update
165
+ expect(runtime.vault.get('ba-u1')!.title).toBe('New');
166
+ });
167
+
168
+ // ─── vault_bulk_remove ────────────────────────────────────────────
169
+
170
+ it('vault_bulk_remove should remove multiple entries', async () => {
171
+ runtime.vault.seed([
172
+ makeEntry({ id: 'br-1' }),
173
+ makeEntry({ id: 'br-2' }),
174
+ makeEntry({ id: 'br-3' }),
175
+ ]);
176
+ const result = (await findOp('vault_bulk_remove').handler({
177
+ ids: ['br-1', 'br-3'],
178
+ })) as { removed: number; requested: number; total: number };
179
+ expect(result.removed).toBe(2);
180
+ expect(result.requested).toBe(2);
181
+ expect(result.total).toBe(1); // br-2 remains
182
+ expect(runtime.vault.get('br-2')).not.toBeNull();
183
+ expect(runtime.vault.get('br-1')).toBeNull();
184
+ });
185
+
186
+ it('vault_bulk_remove should handle missing IDs gracefully', async () => {
187
+ runtime.vault.seed([makeEntry({ id: 'br-4' })]);
188
+ const result = (await findOp('vault_bulk_remove').handler({
189
+ ids: ['br-4', 'br-missing'],
190
+ })) as { removed: number; requested: number };
191
+ expect(result.removed).toBe(1);
192
+ expect(result.requested).toBe(2);
193
+ });
194
+
195
+ // ─── vault_tags ───────────────────────────────────────────────────
196
+
197
+ it('vault_tags should return unique tags with counts', async () => {
198
+ runtime.vault.seed([
199
+ makeEntry({ id: 'vt-1', tags: ['alpha', 'beta'] }),
200
+ makeEntry({ id: 'vt-2', tags: ['beta', 'gamma'] }),
201
+ makeEntry({ id: 'vt-3', tags: ['beta'] }),
202
+ ]);
203
+ const result = (await findOp('vault_tags').handler({})) as {
204
+ tags: Array<{ tag: string; count: number }>;
205
+ count: number;
206
+ };
207
+ expect(result.count).toBe(3); // alpha, beta, gamma
208
+ const beta = result.tags.find((t) => t.tag === 'beta');
209
+ expect(beta!.count).toBe(3);
210
+ const alpha = result.tags.find((t) => t.tag === 'alpha');
211
+ expect(alpha!.count).toBe(1);
212
+ // beta should be first (highest count)
213
+ expect(result.tags[0].tag).toBe('beta');
214
+ });
215
+
216
+ it('vault_tags should return empty when vault is empty', async () => {
217
+ const result = (await findOp('vault_tags').handler({})) as {
218
+ tags: Array<{ tag: string; count: number }>;
219
+ count: number;
220
+ };
221
+ expect(result.tags).toEqual([]);
222
+ expect(result.count).toBe(0);
223
+ });
224
+
225
+ // ─── vault_domains ────────────────────────────────────────────────
226
+
227
+ it('vault_domains should return domains with counts', async () => {
228
+ runtime.vault.seed([
229
+ makeEntry({ id: 'vd-1', domain: 'security' }),
230
+ makeEntry({ id: 'vd-2', domain: 'security' }),
231
+ makeEntry({ id: 'vd-3', domain: 'a11y' }),
232
+ ]);
233
+ const result = (await findOp('vault_domains').handler({})) as {
234
+ domains: Array<{ domain: string; count: number }>;
235
+ count: number;
236
+ };
237
+ expect(result.count).toBe(2);
238
+ const security = result.domains.find((d) => d.domain === 'security');
239
+ expect(security!.count).toBe(2);
240
+ const a11y = result.domains.find((d) => d.domain === 'a11y');
241
+ expect(a11y!.count).toBe(1);
242
+ });
243
+
244
+ // ─── vault_recent ─────────────────────────────────────────────────
245
+
246
+ it('vault_recent should return entries ordered by most recent', async () => {
247
+ runtime.vault.seed([
248
+ makeEntry({ id: 'rec-1', title: 'First' }),
249
+ makeEntry({ id: 'rec-2', title: 'Second' }),
250
+ ]);
251
+ const result = (await findOp('vault_recent').handler({})) as {
252
+ entries: IntelligenceEntry[];
253
+ count: number;
254
+ };
255
+ expect(result.count).toBe(2);
256
+ // Both entries should be present
257
+ expect(result.entries.map((e) => e.id)).toContain('rec-1');
258
+ expect(result.entries.map((e) => e.id)).toContain('rec-2');
259
+ });
260
+
261
+ it('vault_recent should respect limit', async () => {
262
+ runtime.vault.seed([
263
+ makeEntry({ id: 'rl-1' }),
264
+ makeEntry({ id: 'rl-2' }),
265
+ makeEntry({ id: 'rl-3' }),
266
+ ]);
267
+ const result = (await findOp('vault_recent').handler({ limit: 2 })) as {
268
+ entries: IntelligenceEntry[];
269
+ count: number;
270
+ };
271
+ expect(result.count).toBe(2);
272
+ });
273
+
274
+ // ─── vault_import ─────────────────────────────────────────────────
275
+
276
+ it('vault_import should import new entries', async () => {
277
+ const entries = [
278
+ makeEntry({ id: 'vi-1', title: 'Import 1' }),
279
+ makeEntry({ id: 'vi-2', title: 'Import 2' }),
280
+ ];
281
+ const result = (await findOp('vault_import').handler({ entries })) as {
282
+ imported: number;
283
+ newEntries: number;
284
+ updatedEntries: number;
285
+ total: number;
286
+ };
287
+ expect(result.imported).toBe(2);
288
+ expect(result.newEntries).toBe(2);
289
+ expect(result.updatedEntries).toBe(0);
290
+ expect(result.total).toBe(2);
291
+ });
292
+
293
+ it('vault_import should track updated vs new entries', async () => {
294
+ runtime.vault.seed([makeEntry({ id: 'vi-3', title: 'Existing' })]);
295
+ const entries = [
296
+ makeEntry({ id: 'vi-3', title: 'Updated' }),
297
+ makeEntry({ id: 'vi-4', title: 'Brand New' }),
298
+ ];
299
+ const result = (await findOp('vault_import').handler({ entries })) as {
300
+ imported: number;
301
+ newEntries: number;
302
+ updatedEntries: number;
303
+ total: number;
304
+ };
305
+ expect(result.imported).toBe(2);
306
+ expect(result.newEntries).toBe(1);
307
+ expect(result.updatedEntries).toBe(1);
308
+ expect(result.total).toBe(2);
309
+ });
310
+
311
+ // ─── vault_seed ───────────────────────────────────────────────────
312
+
313
+ it('vault_seed should be idempotent', async () => {
314
+ const entries = [makeEntry({ id: 'vs-1', title: 'Seed' })];
315
+ const r1 = (await findOp('vault_seed').handler({ entries })) as {
316
+ seeded: number;
317
+ total: number;
318
+ };
319
+ expect(r1.seeded).toBe(1);
320
+ expect(r1.total).toBe(1);
321
+ // Seed again
322
+ const r2 = (await findOp('vault_seed').handler({ entries })) as {
323
+ seeded: number;
324
+ total: number;
325
+ };
326
+ expect(r2.seeded).toBe(1);
327
+ expect(r2.total).toBe(1); // still 1, not 2
328
+ });
329
+
330
+ // ─── vault_backup ─────────────────────────────────────────────────
331
+
332
+ it('vault_backup should export all entries', async () => {
333
+ runtime.vault.seed([
334
+ makeEntry({ id: 'vb-1', title: 'Backup 1' }),
335
+ makeEntry({ id: 'vb-2', title: 'Backup 2' }),
336
+ ]);
337
+ const result = (await findOp('vault_backup').handler({})) as {
338
+ entries: IntelligenceEntry[];
339
+ exportedAt: number;
340
+ count: number;
341
+ };
342
+ expect(result.count).toBe(2);
343
+ expect(result.entries.length).toBe(2);
344
+ expect(result.exportedAt).toBeGreaterThan(0);
345
+ expect(result.entries.map((e) => e.id).sort()).toEqual(['vb-1', 'vb-2']);
346
+ });
347
+
348
+ it('vault_backup should return empty bundle when vault is empty', async () => {
349
+ const result = (await findOp('vault_backup').handler({})) as {
350
+ entries: IntelligenceEntry[];
351
+ count: number;
352
+ };
353
+ expect(result.entries).toEqual([]);
354
+ expect(result.count).toBe(0);
355
+ });
356
+
357
+ // ─── vault_age_report ─────────────────────────────────────────────
358
+
359
+ it('vault_age_report should return age distribution', async () => {
360
+ runtime.vault.seed([
361
+ makeEntry({ id: 'va-1' }),
362
+ makeEntry({ id: 'va-2' }),
363
+ ]);
364
+ const result = (await findOp('vault_age_report').handler({})) as {
365
+ total: number;
366
+ buckets: Array<{ label: string; count: number; minDays: number; maxDays: number }>;
367
+ oldestTimestamp: number | null;
368
+ newestTimestamp: number | null;
369
+ };
370
+ expect(result.total).toBe(2);
371
+ expect(result.buckets.length).toBe(5);
372
+ // Entries just created should be in the 'today' bucket
373
+ const today = result.buckets.find((b) => b.label === 'today');
374
+ expect(today!.count).toBe(2);
375
+ expect(result.oldestTimestamp).toBeGreaterThan(0);
376
+ expect(result.newestTimestamp).toBeGreaterThan(0);
377
+ });
378
+
379
+ it('vault_age_report should handle empty vault', async () => {
380
+ const result = (await findOp('vault_age_report').handler({})) as {
381
+ total: number;
382
+ oldestTimestamp: number | null;
383
+ newestTimestamp: number | null;
384
+ };
385
+ expect(result.total).toBe(0);
386
+ expect(result.oldestTimestamp).toBeNull();
387
+ expect(result.newestTimestamp).toBeNull();
388
+ });
389
+
390
+ // ─── Auth levels ──────────────────────────────────────────────────
391
+
392
+ it('should assign correct auth levels', () => {
393
+ const readOps = ['vault_get', 'vault_tags', 'vault_domains', 'vault_recent', 'vault_backup', 'vault_age_report'];
394
+ const writeOps = ['vault_update', 'vault_bulk_add', 'vault_import', 'vault_seed'];
395
+ const adminOps = ['vault_remove', 'vault_bulk_remove'];
396
+
397
+ for (const name of readOps) {
398
+ expect(findOp(name).auth).toBe('read');
399
+ }
400
+ for (const name of writeOps) {
401
+ expect(findOp(name).auth).toBe('write');
402
+ }
403
+ for (const name of adminOps) {
404
+ expect(findOp(name).auth).toBe('admin');
405
+ }
406
+ });
407
+ });