@soleri/core 2.0.1 → 2.1.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 (68) hide show
  1. package/dist/brain/brain.d.ts +2 -49
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +1 -158
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +51 -0
  6. package/dist/brain/intelligence.d.ts.map +1 -0
  7. package/dist/brain/intelligence.js +666 -0
  8. package/dist/brain/intelligence.js.map +1 -0
  9. package/dist/brain/types.d.ts +165 -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/curator/curator.d.ts +28 -0
  14. package/dist/curator/curator.d.ts.map +1 -0
  15. package/dist/curator/curator.js +525 -0
  16. package/dist/curator/curator.js.map +1 -0
  17. package/dist/curator/types.d.ts +87 -0
  18. package/dist/curator/types.d.ts.map +1 -0
  19. package/dist/curator/types.js +3 -0
  20. package/dist/curator/types.js.map +1 -0
  21. package/dist/facades/types.d.ts +1 -1
  22. package/dist/index.d.ts +11 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +11 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/llm/llm-client.d.ts +28 -0
  27. package/dist/llm/llm-client.d.ts.map +1 -0
  28. package/dist/llm/llm-client.js +226 -0
  29. package/dist/llm/llm-client.js.map +1 -0
  30. package/dist/runtime/core-ops.d.ts +17 -0
  31. package/dist/runtime/core-ops.d.ts.map +1 -0
  32. package/dist/runtime/core-ops.js +613 -0
  33. package/dist/runtime/core-ops.js.map +1 -0
  34. package/dist/runtime/domain-ops.d.ts +25 -0
  35. package/dist/runtime/domain-ops.d.ts.map +1 -0
  36. package/dist/runtime/domain-ops.js +130 -0
  37. package/dist/runtime/domain-ops.js.map +1 -0
  38. package/dist/runtime/runtime.d.ts +19 -0
  39. package/dist/runtime/runtime.d.ts.map +1 -0
  40. package/dist/runtime/runtime.js +66 -0
  41. package/dist/runtime/runtime.js.map +1 -0
  42. package/dist/runtime/types.d.ts +41 -0
  43. package/dist/runtime/types.d.ts.map +1 -0
  44. package/dist/runtime/types.js +2 -0
  45. package/dist/runtime/types.js.map +1 -0
  46. package/dist/text/similarity.d.ts +8 -0
  47. package/dist/text/similarity.d.ts.map +1 -0
  48. package/dist/text/similarity.js +161 -0
  49. package/dist/text/similarity.js.map +1 -0
  50. package/package.json +6 -2
  51. package/src/__tests__/brain-intelligence.test.ts +623 -0
  52. package/src/__tests__/core-ops.test.ts +218 -0
  53. package/src/__tests__/curator.test.ts +574 -0
  54. package/src/__tests__/domain-ops.test.ts +160 -0
  55. package/src/__tests__/llm-client.test.ts +69 -0
  56. package/src/__tests__/runtime.test.ts +95 -0
  57. package/src/brain/brain.ts +27 -221
  58. package/src/brain/intelligence.ts +1061 -0
  59. package/src/brain/types.ts +176 -0
  60. package/src/curator/curator.ts +699 -0
  61. package/src/curator/types.ts +114 -0
  62. package/src/index.ts +55 -1
  63. package/src/llm/llm-client.ts +310 -0
  64. package/src/runtime/core-ops.ts +665 -0
  65. package/src/runtime/domain-ops.ts +144 -0
  66. package/src/runtime/runtime.ts +76 -0
  67. package/src/runtime/types.ts +39 -0
  68. package/src/text/similarity.ts +168 -0
@@ -0,0 +1,665 @@
1
+ /**
2
+ * Generic core operations factory — 37 ops that every agent gets.
3
+ *
4
+ * These ops are agent-agnostic (no persona, no activation).
5
+ * The 5 agent-specific ops (health, identity, activate, inject_claude_md, setup)
6
+ * stay in generated code because they reference agent-specific modules.
7
+ */
8
+
9
+ import { z } from 'zod';
10
+ import type { OpDefinition } from '../facades/types.js';
11
+ import type { IntelligenceEntry } from '../intelligence/types.js';
12
+ import type { AgentRuntime } from './types.js';
13
+
14
+ /**
15
+ * Create the 37 generic core operations for an agent runtime.
16
+ *
17
+ * Groups: search/vault (4), memory (4), export (1), planning (5),
18
+ * brain (4), brain intelligence (11), curator (8).
19
+ */
20
+ export function createCoreOps(runtime: AgentRuntime): OpDefinition[] {
21
+ const { vault, brain, brainIntelligence, planner, curator, llmClient, keyPool } = runtime;
22
+
23
+ return [
24
+ // ─── Search / Vault ──────────────────────────────────────────
25
+ {
26
+ name: 'search',
27
+ description:
28
+ 'Search across all knowledge domains. Results ranked by TF-IDF + severity + recency + tag overlap + domain match.',
29
+ auth: 'read',
30
+ schema: z.object({
31
+ query: z.string(),
32
+ domain: z.string().optional(),
33
+ type: z.enum(['pattern', 'anti-pattern', 'rule']).optional(),
34
+ severity: z.enum(['critical', 'warning', 'suggestion']).optional(),
35
+ tags: z.array(z.string()).optional(),
36
+ limit: z.number().optional(),
37
+ }),
38
+ handler: async (params) => {
39
+ return brain.intelligentSearch(params.query as string, {
40
+ domain: params.domain as string | undefined,
41
+ type: params.type as string | undefined,
42
+ severity: params.severity as string | undefined,
43
+ tags: params.tags as string[] | undefined,
44
+ limit: (params.limit as number) ?? 10,
45
+ });
46
+ },
47
+ },
48
+ {
49
+ name: 'vault_stats',
50
+ description: 'Get vault statistics — entry counts by type, domain, severity.',
51
+ auth: 'read',
52
+ handler: async () => vault.stats(),
53
+ },
54
+ {
55
+ name: 'list_all',
56
+ description: 'List all knowledge entries with optional filters.',
57
+ auth: 'read',
58
+ schema: z.object({
59
+ domain: z.string().optional(),
60
+ type: z.enum(['pattern', 'anti-pattern', 'rule']).optional(),
61
+ severity: z.enum(['critical', 'warning', 'suggestion']).optional(),
62
+ tags: z.array(z.string()).optional(),
63
+ limit: z.number().optional(),
64
+ offset: z.number().optional(),
65
+ }),
66
+ handler: async (params) => {
67
+ return vault.list({
68
+ domain: params.domain as string | undefined,
69
+ type: params.type as string | undefined,
70
+ severity: params.severity as string | undefined,
71
+ tags: params.tags as string[] | undefined,
72
+ limit: (params.limit as number) ?? 50,
73
+ offset: (params.offset as number) ?? 0,
74
+ });
75
+ },
76
+ },
77
+ {
78
+ name: 'register',
79
+ description:
80
+ 'Register a project for this session. Call on every new session to track usage and get context.',
81
+ auth: 'write',
82
+ schema: z.object({
83
+ projectPath: z.string().optional().default('.'),
84
+ name: z.string().optional().describe('Project display name (derived from path if omitted)'),
85
+ }),
86
+ handler: async (params) => {
87
+ const { resolve } = await import('node:path');
88
+ const projectPath = resolve((params.projectPath as string) ?? '.');
89
+ const project = vault.registerProject(projectPath, params.name as string | undefined);
90
+ const stats = vault.stats();
91
+ const isNew = project.sessionCount === 1;
92
+
93
+ return {
94
+ project,
95
+ is_new: isNew,
96
+ message: isNew
97
+ ? 'Welcome! New project registered.'
98
+ : 'Welcome back! Session #' + project.sessionCount + ' for ' + project.name + '.',
99
+ vault: { entries: stats.totalEntries, domains: Object.keys(stats.byDomain) },
100
+ };
101
+ },
102
+ },
103
+
104
+ // ─── Memory ──────────────────────────────────────────────────
105
+ {
106
+ name: 'memory_search',
107
+ description: 'Search memories using full-text search.',
108
+ auth: 'read',
109
+ schema: z.object({
110
+ query: z.string(),
111
+ type: z.enum(['session', 'lesson', 'preference']).optional(),
112
+ projectPath: z.string().optional(),
113
+ limit: z.number().optional(),
114
+ }),
115
+ handler: async (params) => {
116
+ return vault.searchMemories(params.query as string, {
117
+ type: params.type as string | undefined,
118
+ projectPath: params.projectPath as string | undefined,
119
+ limit: (params.limit as number) ?? 10,
120
+ });
121
+ },
122
+ },
123
+ {
124
+ name: 'memory_capture',
125
+ description: 'Capture a memory — session summary, lesson learned, or preference.',
126
+ auth: 'write',
127
+ schema: z.object({
128
+ projectPath: z.string(),
129
+ type: z.enum(['session', 'lesson', 'preference']),
130
+ context: z.string(),
131
+ summary: z.string(),
132
+ topics: z.array(z.string()).optional().default([]),
133
+ filesModified: z.array(z.string()).optional().default([]),
134
+ toolsUsed: z.array(z.string()).optional().default([]),
135
+ }),
136
+ handler: async (params) => {
137
+ const memory = vault.captureMemory({
138
+ projectPath: params.projectPath as string,
139
+ type: params.type as 'session' | 'lesson' | 'preference',
140
+ context: params.context as string,
141
+ summary: params.summary as string,
142
+ topics: (params.topics as string[]) ?? [],
143
+ filesModified: (params.filesModified as string[]) ?? [],
144
+ toolsUsed: (params.toolsUsed as string[]) ?? [],
145
+ });
146
+ return { captured: true, memory };
147
+ },
148
+ },
149
+ {
150
+ name: 'memory_list',
151
+ description: 'List memories with optional filters.',
152
+ auth: 'read',
153
+ schema: z.object({
154
+ type: z.enum(['session', 'lesson', 'preference']).optional(),
155
+ projectPath: z.string().optional(),
156
+ limit: z.number().optional(),
157
+ offset: z.number().optional(),
158
+ }),
159
+ handler: async (params) => {
160
+ const memories = vault.listMemories({
161
+ type: params.type as string | undefined,
162
+ projectPath: params.projectPath as string | undefined,
163
+ limit: (params.limit as number) ?? 50,
164
+ offset: (params.offset as number) ?? 0,
165
+ });
166
+ const stats = vault.memoryStats();
167
+ return { memories, stats };
168
+ },
169
+ },
170
+ {
171
+ name: 'session_capture',
172
+ description:
173
+ 'Capture a session summary before context compaction. Called automatically by PreCompact hook.',
174
+ auth: 'write',
175
+ schema: z.object({
176
+ projectPath: z.string().optional().default('.'),
177
+ summary: z.string().describe('Brief summary of what was accomplished in this session'),
178
+ topics: z.array(z.string()).optional().default([]),
179
+ filesModified: z.array(z.string()).optional().default([]),
180
+ toolsUsed: z.array(z.string()).optional().default([]),
181
+ }),
182
+ handler: async (params) => {
183
+ const { resolve } = await import('node:path');
184
+ const projectPath = resolve((params.projectPath as string) ?? '.');
185
+ const memory = vault.captureMemory({
186
+ projectPath,
187
+ type: 'session',
188
+ context: 'Auto-captured before context compaction',
189
+ summary: params.summary as string,
190
+ topics: (params.topics as string[]) ?? [],
191
+ filesModified: (params.filesModified as string[]) ?? [],
192
+ toolsUsed: (params.toolsUsed as string[]) ?? [],
193
+ });
194
+ return { captured: true, memory, message: 'Session summary saved to memory.' };
195
+ },
196
+ },
197
+
198
+ // ─── Export ───────────────────────────────────────────────────
199
+ {
200
+ name: 'export',
201
+ description:
202
+ 'Export vault entries as JSON intelligence bundles — one per domain. Enables version control and sharing.',
203
+ auth: 'read',
204
+ schema: z.object({
205
+ domain: z.string().optional().describe('Export only this domain. Omit to export all.'),
206
+ }),
207
+ handler: async (params) => {
208
+ const stats = vault.stats();
209
+ const domains = params.domain ? [params.domain as string] : Object.keys(stats.byDomain);
210
+ const bundles: Array<{ domain: string; version: string; entries: IntelligenceEntry[] }> =
211
+ [];
212
+ for (const d of domains) {
213
+ const entries = vault.list({ domain: d, limit: 10000 });
214
+ bundles.push({ domain: d, version: '1.0.0', entries });
215
+ }
216
+ return {
217
+ exported: true,
218
+ bundles,
219
+ totalEntries: bundles.reduce((sum, b) => sum + b.entries.length, 0),
220
+ domains: bundles.map((b) => b.domain),
221
+ };
222
+ },
223
+ },
224
+
225
+ // ─── Planning ────────────────────────────────────────────────
226
+ {
227
+ name: 'create_plan',
228
+ description:
229
+ 'Create a new plan in draft status. Plans track multi-step tasks with decisions and sub-tasks.',
230
+ auth: 'write',
231
+ schema: z.object({
232
+ objective: z.string().describe('What the plan aims to achieve'),
233
+ scope: z.string().describe('Which parts of the codebase are affected'),
234
+ decisions: z.array(z.string()).optional().default([]),
235
+ tasks: z
236
+ .array(z.object({ title: z.string(), description: z.string() }))
237
+ .optional()
238
+ .default([]),
239
+ }),
240
+ handler: async (params) => {
241
+ const plan = planner.create({
242
+ objective: params.objective as string,
243
+ scope: params.scope as string,
244
+ decisions: (params.decisions as string[]) ?? [],
245
+ tasks: (params.tasks as Array<{ title: string; description: string }>) ?? [],
246
+ });
247
+ return { created: true, plan };
248
+ },
249
+ },
250
+ {
251
+ name: 'get_plan',
252
+ description: 'Get a plan by ID, or list all active plans if no ID provided.',
253
+ auth: 'read',
254
+ schema: z.object({
255
+ planId: z.string().optional().describe('Plan ID. Omit to list all active plans.'),
256
+ }),
257
+ handler: async (params) => {
258
+ if (params.planId) {
259
+ const plan = planner.get(params.planId as string);
260
+ if (!plan) return { error: 'Plan not found: ' + params.planId };
261
+ return plan;
262
+ }
263
+ return { active: planner.getActive(), executing: planner.getExecuting() };
264
+ },
265
+ },
266
+ {
267
+ name: 'approve_plan',
268
+ description: 'Approve a draft plan and optionally start execution.',
269
+ auth: 'write',
270
+ schema: z.object({
271
+ planId: z.string(),
272
+ startExecution: z
273
+ .boolean()
274
+ .optional()
275
+ .default(false)
276
+ .describe('If true, immediately start execution after approval'),
277
+ }),
278
+ handler: async (params) => {
279
+ let plan = planner.approve(params.planId as string);
280
+ if (params.startExecution) {
281
+ plan = planner.startExecution(plan.id);
282
+ }
283
+ return { approved: true, executing: plan.status === 'executing', plan };
284
+ },
285
+ },
286
+ {
287
+ name: 'update_task',
288
+ description: 'Update a task status within an executing plan.',
289
+ auth: 'write',
290
+ schema: z.object({
291
+ planId: z.string(),
292
+ taskId: z.string(),
293
+ status: z.enum(['pending', 'in_progress', 'completed', 'skipped', 'failed']),
294
+ }),
295
+ handler: async (params) => {
296
+ const plan = planner.updateTask(
297
+ params.planId as string,
298
+ params.taskId as string,
299
+ params.status as 'pending' | 'in_progress' | 'completed' | 'skipped' | 'failed',
300
+ );
301
+ const task = plan.tasks.find((t) => t.id === params.taskId);
302
+ return { updated: true, task, plan: { id: plan.id, status: plan.status } };
303
+ },
304
+ },
305
+ {
306
+ name: 'complete_plan',
307
+ description: 'Mark an executing plan as completed.',
308
+ auth: 'write',
309
+ schema: z.object({
310
+ planId: z.string(),
311
+ }),
312
+ handler: async (params) => {
313
+ const plan = planner.complete(params.planId as string);
314
+ const taskSummary = {
315
+ completed: plan.tasks.filter((t) => t.status === 'completed').length,
316
+ skipped: plan.tasks.filter((t) => t.status === 'skipped').length,
317
+ failed: plan.tasks.filter((t) => t.status === 'failed').length,
318
+ total: plan.tasks.length,
319
+ };
320
+ return { completed: true, plan, taskSummary };
321
+ },
322
+ },
323
+
324
+ // ─── Brain ───────────────────────────────────────────────────
325
+ {
326
+ name: 'record_feedback',
327
+ description:
328
+ 'Record feedback on a search result — accepted or dismissed. Used for adaptive weight tuning.',
329
+ auth: 'write',
330
+ schema: z.object({
331
+ query: z.string().describe('The original search query'),
332
+ entryId: z.string().describe('The entry ID that was accepted or dismissed'),
333
+ action: z.enum(['accepted', 'dismissed']),
334
+ }),
335
+ handler: async (params) => {
336
+ brain.recordFeedback(
337
+ params.query as string,
338
+ params.entryId as string,
339
+ params.action as 'accepted' | 'dismissed',
340
+ );
341
+ return {
342
+ recorded: true,
343
+ query: params.query,
344
+ entryId: params.entryId,
345
+ action: params.action,
346
+ };
347
+ },
348
+ },
349
+ {
350
+ name: 'rebuild_vocabulary',
351
+ description: 'Force rebuild the TF-IDF vocabulary from all vault entries.',
352
+ auth: 'write',
353
+ handler: async () => {
354
+ brain.rebuildVocabulary();
355
+ return { rebuilt: true, vocabularySize: brain.getVocabularySize() };
356
+ },
357
+ },
358
+ {
359
+ name: 'brain_stats',
360
+ description:
361
+ 'Get brain intelligence stats — vocabulary size, feedback count, current scoring weights, intelligence pipeline stats.',
362
+ auth: 'read',
363
+ handler: async () => {
364
+ const base = brain.getStats();
365
+ const intelligence = brainIntelligence.getStats();
366
+ return { ...base, intelligence };
367
+ },
368
+ },
369
+ {
370
+ name: 'llm_status',
371
+ description:
372
+ 'LLM client status — provider availability, key pool status, model routing config.',
373
+ auth: 'read',
374
+ handler: async () => {
375
+ const available = llmClient.isAvailable();
376
+ return {
377
+ providers: {
378
+ openai: {
379
+ available: available.openai,
380
+ keyPool: keyPool.openai.hasKeys ? keyPool.openai.getStatus() : null,
381
+ },
382
+ anthropic: {
383
+ available: available.anthropic,
384
+ keyPool: keyPool.anthropic.hasKeys ? keyPool.anthropic.getStatus() : null,
385
+ },
386
+ },
387
+ routes: llmClient.getRoutes(),
388
+ };
389
+ },
390
+ },
391
+
392
+ // ─── Brain Intelligence ───────────────────────────────────────
393
+ {
394
+ name: 'brain_session_context',
395
+ description:
396
+ 'Get recent session context — sessions, tool usage frequency, file change frequency.',
397
+ auth: 'read',
398
+ schema: z.object({
399
+ limit: z.number().optional().describe('Number of recent sessions. Default 10.'),
400
+ }),
401
+ handler: async (params) => {
402
+ return brainIntelligence.getSessionContext((params.limit as number) ?? 10);
403
+ },
404
+ },
405
+ {
406
+ name: 'brain_strengths',
407
+ description:
408
+ 'Get pattern strength scores. 4-signal scoring: usage (0-25) + spread (0-25) + success (0-25) + recency (0-25).',
409
+ auth: 'read',
410
+ schema: z.object({
411
+ domain: z.string().optional(),
412
+ minStrength: z.number().optional().describe('Minimum strength score (0-100).'),
413
+ limit: z.number().optional(),
414
+ }),
415
+ handler: async (params) => {
416
+ return brainIntelligence.getStrengths({
417
+ domain: params.domain as string | undefined,
418
+ minStrength: params.minStrength as number | undefined,
419
+ limit: (params.limit as number) ?? 50,
420
+ });
421
+ },
422
+ },
423
+ {
424
+ name: 'brain_global_patterns',
425
+ description:
426
+ 'Get cross-domain pattern registry — patterns that appear across multiple domains.',
427
+ auth: 'read',
428
+ schema: z.object({
429
+ limit: z.number().optional(),
430
+ }),
431
+ handler: async (params) => {
432
+ return brainIntelligence.getGlobalPatterns((params.limit as number) ?? 20);
433
+ },
434
+ },
435
+ {
436
+ name: 'brain_recommend',
437
+ description:
438
+ 'Get pattern recommendations for a task context. Matches domain and task terms against known strengths.',
439
+ auth: 'read',
440
+ schema: z.object({
441
+ domain: z.string().optional(),
442
+ task: z.string().optional().describe('Task description for contextual matching.'),
443
+ limit: z.number().optional(),
444
+ }),
445
+ handler: async (params) => {
446
+ return brainIntelligence.recommend({
447
+ domain: params.domain as string | undefined,
448
+ task: params.task as string | undefined,
449
+ limit: (params.limit as number) ?? 5,
450
+ });
451
+ },
452
+ },
453
+ {
454
+ name: 'brain_build_intelligence',
455
+ description:
456
+ 'Run the full intelligence pipeline: compute strengths → build global registry → build domain profiles.',
457
+ auth: 'write',
458
+ handler: async () => {
459
+ return brainIntelligence.buildIntelligence();
460
+ },
461
+ },
462
+ {
463
+ name: 'brain_export',
464
+ description:
465
+ 'Export all brain intelligence data — strengths, sessions, proposals, global patterns, domain profiles.',
466
+ auth: 'read',
467
+ handler: async () => {
468
+ return brainIntelligence.exportData();
469
+ },
470
+ },
471
+ {
472
+ name: 'brain_import',
473
+ description: 'Import brain intelligence data from a previous export.',
474
+ auth: 'write',
475
+ schema: z.object({
476
+ data: z.any().describe('BrainExportData object from brain_export.'),
477
+ }),
478
+ handler: async (params) => {
479
+ return brainIntelligence.importData(
480
+ params.data as import('../brain/types.js').BrainExportData,
481
+ );
482
+ },
483
+ },
484
+ {
485
+ name: 'brain_extract_knowledge',
486
+ description:
487
+ 'Extract knowledge proposals from a session using 6 heuristic rules (repeated tools, multi-file edits, long sessions, plan outcomes, feedback ratios).',
488
+ auth: 'write',
489
+ schema: z.object({
490
+ sessionId: z.string().describe('Session ID to extract knowledge from.'),
491
+ }),
492
+ handler: async (params) => {
493
+ return brainIntelligence.extractKnowledge(params.sessionId as string);
494
+ },
495
+ },
496
+ {
497
+ name: 'brain_archive_sessions',
498
+ description: 'Archive (delete) completed sessions older than N days.',
499
+ auth: 'write',
500
+ schema: z.object({
501
+ olderThanDays: z.number().optional().describe('Days threshold. Default 30.'),
502
+ }),
503
+ handler: async (params) => {
504
+ return brainIntelligence.archiveSessions((params.olderThanDays as number) ?? 30);
505
+ },
506
+ },
507
+ {
508
+ name: 'brain_promote_proposals',
509
+ description:
510
+ 'Promote knowledge proposals to vault entries. Creates intelligence entries from auto-extracted patterns.',
511
+ auth: 'write',
512
+ schema: z.object({
513
+ proposalIds: z.array(z.string()).describe('IDs of proposals to promote.'),
514
+ }),
515
+ handler: async (params) => {
516
+ return brainIntelligence.promoteProposals(params.proposalIds as string[]);
517
+ },
518
+ },
519
+ {
520
+ name: 'brain_lifecycle',
521
+ description:
522
+ 'Start or end a brain session. Sessions track tool usage, file changes, and plan context.',
523
+ auth: 'write',
524
+ schema: z.object({
525
+ action: z.enum(['start', 'end']),
526
+ sessionId: z
527
+ .string()
528
+ .optional()
529
+ .describe('Required for end. Auto-generated for start if omitted.'),
530
+ domain: z.string().optional(),
531
+ context: z.string().optional(),
532
+ toolsUsed: z.array(z.string()).optional(),
533
+ filesModified: z.array(z.string()).optional(),
534
+ planId: z.string().optional(),
535
+ planOutcome: z.string().optional(),
536
+ }),
537
+ handler: async (params) => {
538
+ return brainIntelligence.lifecycle({
539
+ action: params.action as 'start' | 'end',
540
+ sessionId: params.sessionId as string | undefined,
541
+ domain: params.domain as string | undefined,
542
+ context: params.context as string | undefined,
543
+ toolsUsed: params.toolsUsed as string[] | undefined,
544
+ filesModified: params.filesModified as string[] | undefined,
545
+ planId: params.planId as string | undefined,
546
+ planOutcome: params.planOutcome as string | undefined,
547
+ });
548
+ },
549
+ },
550
+
551
+ // ─── Curator ─────────────────────────────────────────────────
552
+ {
553
+ name: 'curator_status',
554
+ description: 'Curator status — table row counts, last groomed timestamp.',
555
+ auth: 'read',
556
+ handler: async () => {
557
+ return curator.getStatus();
558
+ },
559
+ },
560
+ {
561
+ name: 'curator_detect_duplicates',
562
+ description: 'Detect duplicate entries using TF-IDF cosine similarity.',
563
+ auth: 'read',
564
+ schema: z.object({
565
+ entryId: z.string().optional().describe('Check a specific entry. Omit to scan all.'),
566
+ threshold: z.number().optional().describe('Similarity threshold (0-1). Default 0.45.'),
567
+ }),
568
+ handler: async (params) => {
569
+ return curator.detectDuplicates(
570
+ params.entryId as string | undefined,
571
+ params.threshold as number | undefined,
572
+ );
573
+ },
574
+ },
575
+ {
576
+ name: 'curator_contradictions',
577
+ description: 'List or detect contradictions between patterns and anti-patterns.',
578
+ auth: 'read',
579
+ schema: z.object({
580
+ status: z.enum(['open', 'resolved', 'dismissed']).optional().describe('Filter by status.'),
581
+ detect: z.boolean().optional().describe('If true, run detection before listing.'),
582
+ }),
583
+ handler: async (params) => {
584
+ if (params.detect) {
585
+ curator.detectContradictions();
586
+ }
587
+ return curator.getContradictions(
588
+ params.status as 'open' | 'resolved' | 'dismissed' | undefined,
589
+ );
590
+ },
591
+ },
592
+ {
593
+ name: 'curator_resolve_contradiction',
594
+ description: 'Resolve or dismiss a contradiction.',
595
+ auth: 'write',
596
+ schema: z.object({
597
+ id: z.number().describe('Contradiction ID.'),
598
+ resolution: z.enum(['resolved', 'dismissed']),
599
+ }),
600
+ handler: async (params) => {
601
+ return curator.resolveContradiction(
602
+ params.id as number,
603
+ params.resolution as 'resolved' | 'dismissed',
604
+ );
605
+ },
606
+ },
607
+ {
608
+ name: 'curator_groom',
609
+ description: 'Groom a single entry — normalize tags, check staleness.',
610
+ auth: 'write',
611
+ schema: z.object({
612
+ entryId: z.string().describe('Entry ID to groom.'),
613
+ }),
614
+ handler: async (params) => {
615
+ return curator.groomEntry(params.entryId as string);
616
+ },
617
+ },
618
+ {
619
+ name: 'curator_groom_all',
620
+ description: 'Groom all vault entries — normalize tags, detect staleness.',
621
+ auth: 'write',
622
+ handler: async () => {
623
+ return curator.groomAll();
624
+ },
625
+ },
626
+ {
627
+ name: 'curator_consolidate',
628
+ description:
629
+ 'Consolidate vault — find duplicates, stale entries, contradictions. Dry-run by default.',
630
+ auth: 'write',
631
+ schema: z.object({
632
+ dryRun: z.boolean().optional().describe('Default true. Set false to apply mutations.'),
633
+ staleDaysThreshold: z
634
+ .number()
635
+ .optional()
636
+ .describe('Days before entry is stale. Default 90.'),
637
+ duplicateThreshold: z
638
+ .number()
639
+ .optional()
640
+ .describe('Cosine similarity threshold. Default 0.45.'),
641
+ contradictionThreshold: z
642
+ .number()
643
+ .optional()
644
+ .describe('Contradiction threshold. Default 0.4.'),
645
+ }),
646
+ handler: async (params) => {
647
+ return curator.consolidate({
648
+ dryRun: params.dryRun as boolean | undefined,
649
+ staleDaysThreshold: params.staleDaysThreshold as number | undefined,
650
+ duplicateThreshold: params.duplicateThreshold as number | undefined,
651
+ contradictionThreshold: params.contradictionThreshold as number | undefined,
652
+ });
653
+ },
654
+ },
655
+ {
656
+ name: 'curator_health_audit',
657
+ description:
658
+ 'Audit vault health — score (0-100), coverage, freshness, quality, tag health, recommendations.',
659
+ auth: 'read',
660
+ handler: async () => {
661
+ return curator.healthAudit();
662
+ },
663
+ },
664
+ ];
665
+ }