@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,225 @@
1
+ /**
2
+ * Extra vault operations — 12 ops that extend the 4 base vault ops in core-ops.ts.
3
+ *
4
+ * Groups: single-entry CRUD (3), bulk (2), discovery (3), import/export (3), analytics (1).
5
+ */
6
+
7
+ import { z } from 'zod';
8
+ import type { OpDefinition } from '../facades/types.js';
9
+ import type { IntelligenceEntry } from '../intelligence/types.js';
10
+ import type { AgentRuntime } from './types.js';
11
+
12
+ const entrySchema = z.object({
13
+ id: z.string(),
14
+ type: z.enum(['pattern', 'anti-pattern', 'rule']),
15
+ domain: z.string(),
16
+ title: z.string(),
17
+ severity: z.enum(['critical', 'warning', 'suggestion']),
18
+ description: z.string(),
19
+ context: z.string().optional(),
20
+ example: z.string().optional(),
21
+ counterExample: z.string().optional(),
22
+ why: z.string().optional(),
23
+ tags: z.array(z.string()),
24
+ appliesTo: z.array(z.string()).optional(),
25
+ });
26
+
27
+ export function createVaultExtraOps(runtime: AgentRuntime): OpDefinition[] {
28
+ const { vault } = runtime;
29
+
30
+ return [
31
+ // ─── Single-Entry CRUD ──────────────────────────────────────────
32
+ {
33
+ name: 'vault_get',
34
+ description: 'Get a single vault entry by ID.',
35
+ auth: 'read',
36
+ schema: z.object({ id: z.string() }),
37
+ handler: async (params) => {
38
+ const entry = vault.get(params.id as string);
39
+ if (!entry) return { error: 'Entry not found: ' + params.id };
40
+ return entry;
41
+ },
42
+ },
43
+ {
44
+ name: 'vault_update',
45
+ description:
46
+ 'Update an existing vault entry. Only the fields provided are changed; the rest stay the same.',
47
+ auth: 'write',
48
+ schema: z.object({
49
+ id: z.string(),
50
+ title: z.string().optional(),
51
+ description: z.string().optional(),
52
+ context: z.string().optional(),
53
+ example: z.string().optional(),
54
+ counterExample: z.string().optional(),
55
+ why: z.string().optional(),
56
+ tags: z.array(z.string()).optional(),
57
+ appliesTo: z.array(z.string()).optional(),
58
+ severity: z.enum(['critical', 'warning', 'suggestion']).optional(),
59
+ type: z.enum(['pattern', 'anti-pattern', 'rule']).optional(),
60
+ domain: z.string().optional(),
61
+ }),
62
+ handler: async (params) => {
63
+ const id = params.id as string;
64
+ const { id: _id, ...fields } = params;
65
+ // Strip undefined values so we only pass what was actually provided
66
+ const cleaned: Record<string, unknown> = {};
67
+ for (const [k, v] of Object.entries(fields)) {
68
+ if (v !== undefined) cleaned[k] = v;
69
+ }
70
+ if (Object.keys(cleaned).length === 0) {
71
+ return { error: 'No fields to update' };
72
+ }
73
+ const updated = vault.update(
74
+ id,
75
+ cleaned as Partial<
76
+ Pick<
77
+ IntelligenceEntry,
78
+ | 'title'
79
+ | 'description'
80
+ | 'context'
81
+ | 'example'
82
+ | 'counterExample'
83
+ | 'why'
84
+ | 'tags'
85
+ | 'appliesTo'
86
+ | 'severity'
87
+ | 'type'
88
+ | 'domain'
89
+ >
90
+ >,
91
+ );
92
+ if (!updated) return { error: 'Entry not found: ' + id };
93
+ return { updated: true, entry: updated };
94
+ },
95
+ },
96
+ {
97
+ name: 'vault_remove',
98
+ description: 'Remove a single vault entry by ID.',
99
+ auth: 'admin',
100
+ schema: z.object({ id: z.string() }),
101
+ handler: async (params) => {
102
+ const removed = vault.remove(params.id as string);
103
+ return { removed, id: params.id };
104
+ },
105
+ },
106
+
107
+ // ─── Bulk Operations ────────────────────────────────────────────
108
+ {
109
+ name: 'vault_bulk_add',
110
+ description: 'Add multiple vault entries at once. Uses upsert — existing IDs are updated.',
111
+ auth: 'write',
112
+ schema: z.object({
113
+ entries: z.array(entrySchema),
114
+ }),
115
+ handler: async (params) => {
116
+ const entries = params.entries as IntelligenceEntry[];
117
+ const count = vault.seed(entries);
118
+ return { added: count, total: vault.stats().totalEntries };
119
+ },
120
+ },
121
+ {
122
+ name: 'vault_bulk_remove',
123
+ description: 'Remove multiple vault entries by IDs in a single transaction.',
124
+ auth: 'admin',
125
+ schema: z.object({
126
+ ids: z.array(z.string()),
127
+ }),
128
+ handler: async (params) => {
129
+ const ids = params.ids as string[];
130
+ const removed = vault.bulkRemove(ids);
131
+ return { removed, requested: ids.length, total: vault.stats().totalEntries };
132
+ },
133
+ },
134
+
135
+ // ─── Discovery ──────────────────────────────────────────────────
136
+ {
137
+ name: 'vault_tags',
138
+ description: 'List all unique tags used across vault entries with their occurrence counts.',
139
+ auth: 'read',
140
+ handler: async () => {
141
+ const tags = vault.getTags();
142
+ return { tags, count: tags.length };
143
+ },
144
+ },
145
+ {
146
+ name: 'vault_domains',
147
+ description: 'List all domains in the vault with their entry counts.',
148
+ auth: 'read',
149
+ handler: async () => {
150
+ const domains = vault.getDomains();
151
+ return { domains, count: domains.length };
152
+ },
153
+ },
154
+ {
155
+ name: 'vault_recent',
156
+ description: 'Get recently added or updated vault entries, ordered by most recent first.',
157
+ auth: 'read',
158
+ schema: z.object({
159
+ limit: z.number().optional().describe('Max entries to return (default 20)'),
160
+ }),
161
+ handler: async (params) => {
162
+ const limit = (params.limit as number | undefined) ?? 20;
163
+ const entries = vault.getRecent(limit);
164
+ return { entries, count: entries.length };
165
+ },
166
+ },
167
+
168
+ // ─── Import / Export / Seed ──────────────────────────────────────
169
+ {
170
+ name: 'vault_import',
171
+ description:
172
+ 'Import vault entries from a JSON bundle. Uses upsert — existing IDs are updated, new IDs are inserted.',
173
+ auth: 'write',
174
+ schema: z.object({
175
+ entries: z.array(entrySchema),
176
+ }),
177
+ handler: async (params) => {
178
+ const entries = params.entries as IntelligenceEntry[];
179
+ const before = vault.stats().totalEntries;
180
+ const count = vault.seed(entries);
181
+ const after = vault.stats().totalEntries;
182
+ return {
183
+ imported: count,
184
+ newEntries: after - before,
185
+ updatedEntries: count - (after - before),
186
+ total: after,
187
+ };
188
+ },
189
+ },
190
+ {
191
+ name: 'vault_seed',
192
+ description:
193
+ 'Seed the vault from intelligence data. Idempotent — safe to call multiple times. Uses upsert.',
194
+ auth: 'write',
195
+ schema: z.object({
196
+ entries: z.array(entrySchema),
197
+ }),
198
+ handler: async (params) => {
199
+ const entries = params.entries as IntelligenceEntry[];
200
+ const count = vault.seed(entries);
201
+ return { seeded: count, total: vault.stats().totalEntries };
202
+ },
203
+ },
204
+ {
205
+ name: 'vault_backup',
206
+ description:
207
+ 'Export the full vault as a JSON bundle suitable for backup or transfer to another agent.',
208
+ auth: 'read',
209
+ handler: async () => {
210
+ return vault.exportAll();
211
+ },
212
+ },
213
+
214
+ // ─── Analytics ──────────────────────────────────────────────────
215
+ {
216
+ name: 'vault_age_report',
217
+ description:
218
+ 'Show vault entry age distribution — how many entries are from today, this week, this month, this quarter, or older.',
219
+ auth: 'read',
220
+ handler: async () => {
221
+ return vault.getAgeReport();
222
+ },
223
+ },
224
+ ];
225
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * In-memory telemetry tracker for facade calls.
3
+ *
4
+ * No external deps — records calls, computes stats, bounds memory usage.
5
+ * Max 10 000 entries; auto-trims to 5 000 when limit is hit.
6
+ */
7
+
8
+ export interface FacadeCall {
9
+ facade: string;
10
+ op: string;
11
+ timestamp: number;
12
+ durationMs: number;
13
+ success: boolean;
14
+ error?: string;
15
+ }
16
+
17
+ export interface TelemetryStats {
18
+ totalCalls: number;
19
+ successRate: number;
20
+ avgDurationMs: number;
21
+ callsByFacade: Record<string, number>;
22
+ callsByOp: Record<string, number>;
23
+ errorsByOp: Record<string, number>;
24
+ slowestOps: Array<{ op: string; avgMs: number }>;
25
+ since: number;
26
+ }
27
+
28
+ const MAX_ENTRIES = 10_000;
29
+ const TRIM_TO = 5_000;
30
+
31
+ export class Telemetry {
32
+ private calls: FacadeCall[] = [];
33
+ private startedAt = Date.now();
34
+
35
+ /**
36
+ * Record a facade call. Timestamp is auto-set to Date.now().
37
+ */
38
+ record(call: Omit<FacadeCall, 'timestamp'>): void {
39
+ this.calls.push({ ...call, timestamp: Date.now() });
40
+ if (this.calls.length > MAX_ENTRIES) {
41
+ this.calls = this.calls.slice(-TRIM_TO);
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Compute aggregate stats from all recorded calls.
47
+ */
48
+ getStats(): TelemetryStats {
49
+ const total = this.calls.length;
50
+ if (total === 0) {
51
+ return {
52
+ totalCalls: 0,
53
+ successRate: 1,
54
+ avgDurationMs: 0,
55
+ callsByFacade: {},
56
+ callsByOp: {},
57
+ errorsByOp: {},
58
+ slowestOps: [],
59
+ since: this.startedAt,
60
+ };
61
+ }
62
+
63
+ const successes = this.calls.filter((c) => c.success).length;
64
+ const totalDuration = this.calls.reduce((sum, c) => sum + c.durationMs, 0);
65
+
66
+ const callsByFacade: Record<string, number> = {};
67
+ const callsByOp: Record<string, number> = {};
68
+ const errorsByOp: Record<string, number> = {};
69
+ const durationsByOp: Record<string, number[]> = {};
70
+
71
+ for (const call of this.calls) {
72
+ callsByFacade[call.facade] = (callsByFacade[call.facade] ?? 0) + 1;
73
+ callsByOp[call.op] = (callsByOp[call.op] ?? 0) + 1;
74
+
75
+ if (!call.success) {
76
+ errorsByOp[call.op] = (errorsByOp[call.op] ?? 0) + 1;
77
+ }
78
+
79
+ if (!durationsByOp[call.op]) durationsByOp[call.op] = [];
80
+ durationsByOp[call.op].push(call.durationMs);
81
+ }
82
+
83
+ // Compute slowest ops by average duration, top 10
84
+ const opAvgDurations = Object.entries(durationsByOp)
85
+ .map(([op, durations]) => ({
86
+ op,
87
+ avgMs: Math.round(durations.reduce((a, b) => a + b, 0) / durations.length),
88
+ }))
89
+ .sort((a, b) => b.avgMs - a.avgMs)
90
+ .slice(0, 10);
91
+
92
+ return {
93
+ totalCalls: total,
94
+ successRate: Math.round((successes / total) * 1000) / 1000,
95
+ avgDurationMs: Math.round(totalDuration / total),
96
+ callsByFacade,
97
+ callsByOp,
98
+ errorsByOp,
99
+ slowestOps: opAvgDurations,
100
+ since: this.startedAt,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Get the N most recent calls, newest first.
106
+ */
107
+ getRecent(limit = 50): FacadeCall[] {
108
+ return this.calls.slice(-limit).reverse();
109
+ }
110
+
111
+ /**
112
+ * Clear all recorded data and reset the start timestamp.
113
+ */
114
+ reset(): void {
115
+ this.calls = [];
116
+ this.startedAt = Date.now();
117
+ }
118
+ }