@soleri/core 7.0.0 → 8.0.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 (210) hide show
  1. package/dist/agency/agency-manager.d.ts +27 -1
  2. package/dist/agency/agency-manager.d.ts.map +1 -1
  3. package/dist/agency/agency-manager.js +180 -9
  4. package/dist/agency/agency-manager.js.map +1 -1
  5. package/dist/agency/default-rules.d.ts +7 -0
  6. package/dist/agency/default-rules.d.ts.map +1 -0
  7. package/dist/agency/default-rules.js +79 -0
  8. package/dist/agency/default-rules.js.map +1 -0
  9. package/dist/agency/types.d.ts +48 -0
  10. package/dist/agency/types.d.ts.map +1 -1
  11. package/dist/brain/brain.d.ts +17 -2
  12. package/dist/brain/brain.d.ts.map +1 -1
  13. package/dist/brain/brain.js +118 -8
  14. package/dist/brain/brain.js.map +1 -1
  15. package/dist/brain/knowledge-synthesizer.d.ts +37 -0
  16. package/dist/brain/knowledge-synthesizer.d.ts.map +1 -0
  17. package/dist/brain/knowledge-synthesizer.js +161 -0
  18. package/dist/brain/knowledge-synthesizer.js.map +1 -0
  19. package/dist/brain/learning-radar.d.ts +96 -0
  20. package/dist/brain/learning-radar.d.ts.map +1 -0
  21. package/dist/brain/learning-radar.js +202 -0
  22. package/dist/brain/learning-radar.js.map +1 -0
  23. package/dist/brain/types.d.ts +15 -0
  24. package/dist/brain/types.d.ts.map +1 -1
  25. package/dist/context/context-engine.d.ts.map +1 -1
  26. package/dist/context/context-engine.js +82 -17
  27. package/dist/context/context-engine.js.map +1 -1
  28. package/dist/context/types.d.ts +5 -0
  29. package/dist/context/types.d.ts.map +1 -1
  30. package/dist/control/intent-router.d.ts +12 -1
  31. package/dist/control/intent-router.d.ts.map +1 -1
  32. package/dist/control/intent-router.js +68 -0
  33. package/dist/control/intent-router.js.map +1 -1
  34. package/dist/control/types.d.ts +17 -0
  35. package/dist/control/types.d.ts.map +1 -1
  36. package/dist/curator/classifier.d.ts +18 -0
  37. package/dist/curator/classifier.d.ts.map +1 -0
  38. package/dist/curator/classifier.js +61 -0
  39. package/dist/curator/classifier.js.map +1 -0
  40. package/dist/curator/quality-gate.d.ts +29 -0
  41. package/dist/curator/quality-gate.d.ts.map +1 -0
  42. package/dist/curator/quality-gate.js +88 -0
  43. package/dist/curator/quality-gate.js.map +1 -0
  44. package/dist/engine/bin/soleri-engine.js +1 -0
  45. package/dist/engine/bin/soleri-engine.js.map +1 -1
  46. package/dist/events/event-bus.d.ts +30 -0
  47. package/dist/events/event-bus.d.ts.map +1 -0
  48. package/dist/events/event-bus.js +51 -0
  49. package/dist/events/event-bus.js.map +1 -0
  50. package/dist/flows/chain-runner.d.ts +46 -0
  51. package/dist/flows/chain-runner.d.ts.map +1 -0
  52. package/dist/flows/chain-runner.js +271 -0
  53. package/dist/flows/chain-runner.js.map +1 -0
  54. package/dist/flows/chain-types.d.ts +103 -0
  55. package/dist/flows/chain-types.d.ts.map +1 -0
  56. package/dist/flows/chain-types.js +23 -0
  57. package/dist/flows/chain-types.js.map +1 -0
  58. package/dist/health/doctor-checks.d.ts +15 -0
  59. package/dist/health/doctor-checks.d.ts.map +1 -0
  60. package/dist/health/doctor-checks.js +98 -0
  61. package/dist/health/doctor-checks.js.map +1 -0
  62. package/dist/intake/text-ingester.d.ts +52 -0
  63. package/dist/intake/text-ingester.d.ts.map +1 -0
  64. package/dist/intake/text-ingester.js +181 -0
  65. package/dist/intake/text-ingester.js.map +1 -0
  66. package/dist/llm/llm-client.d.ts.map +1 -1
  67. package/dist/llm/llm-client.js +37 -1
  68. package/dist/llm/llm-client.js.map +1 -1
  69. package/dist/llm/oauth-discovery.d.ts +26 -0
  70. package/dist/llm/oauth-discovery.d.ts.map +1 -0
  71. package/dist/llm/oauth-discovery.js +149 -0
  72. package/dist/llm/oauth-discovery.js.map +1 -0
  73. package/dist/planning/evidence-collector.d.ts +41 -0
  74. package/dist/planning/evidence-collector.d.ts.map +1 -0
  75. package/dist/planning/evidence-collector.js +194 -0
  76. package/dist/planning/evidence-collector.js.map +1 -0
  77. package/dist/planning/planner.d.ts +4 -0
  78. package/dist/planning/planner.d.ts.map +1 -1
  79. package/dist/planning/planner.js +11 -0
  80. package/dist/planning/planner.js.map +1 -1
  81. package/dist/queue/job-queue.d.ts +92 -0
  82. package/dist/queue/job-queue.d.ts.map +1 -0
  83. package/dist/queue/job-queue.js +180 -0
  84. package/dist/queue/job-queue.js.map +1 -0
  85. package/dist/queue/pipeline-runner.d.ts +62 -0
  86. package/dist/queue/pipeline-runner.d.ts.map +1 -0
  87. package/dist/queue/pipeline-runner.js +126 -0
  88. package/dist/queue/pipeline-runner.js.map +1 -0
  89. package/dist/runtime/admin-setup-ops.d.ts +20 -0
  90. package/dist/runtime/admin-setup-ops.d.ts.map +1 -0
  91. package/dist/runtime/admin-setup-ops.js +583 -0
  92. package/dist/runtime/admin-setup-ops.js.map +1 -0
  93. package/dist/runtime/chain-ops.d.ts +9 -0
  94. package/dist/runtime/chain-ops.d.ts.map +1 -0
  95. package/dist/runtime/chain-ops.js +107 -0
  96. package/dist/runtime/chain-ops.js.map +1 -0
  97. package/dist/runtime/claude-md-helpers.d.ts +65 -0
  98. package/dist/runtime/claude-md-helpers.d.ts.map +1 -0
  99. package/dist/runtime/claude-md-helpers.js +173 -0
  100. package/dist/runtime/claude-md-helpers.js.map +1 -0
  101. package/dist/runtime/curator-extra-ops.d.ts +3 -2
  102. package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
  103. package/dist/runtime/curator-extra-ops.js +81 -3
  104. package/dist/runtime/curator-extra-ops.js.map +1 -1
  105. package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
  106. package/dist/runtime/facades/admin-facade.js +4 -0
  107. package/dist/runtime/facades/admin-facade.js.map +1 -1
  108. package/dist/runtime/facades/agency-facade.d.ts.map +1 -1
  109. package/dist/runtime/facades/agency-facade.js +64 -0
  110. package/dist/runtime/facades/agency-facade.js.map +1 -1
  111. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  112. package/dist/runtime/facades/brain-facade.js +122 -1
  113. package/dist/runtime/facades/brain-facade.js.map +1 -1
  114. package/dist/runtime/facades/control-facade.d.ts.map +1 -1
  115. package/dist/runtime/facades/control-facade.js +42 -0
  116. package/dist/runtime/facades/control-facade.js.map +1 -1
  117. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  118. package/dist/runtime/facades/memory-facade.js +20 -2
  119. package/dist/runtime/facades/memory-facade.js.map +1 -1
  120. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  121. package/dist/runtime/facades/plan-facade.js +2 -0
  122. package/dist/runtime/facades/plan-facade.js.map +1 -1
  123. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  124. package/dist/runtime/facades/vault-facade.js +25 -5
  125. package/dist/runtime/facades/vault-facade.js.map +1 -1
  126. package/dist/runtime/intake-ops.d.ts +7 -5
  127. package/dist/runtime/intake-ops.d.ts.map +1 -1
  128. package/dist/runtime/intake-ops.js +98 -5
  129. package/dist/runtime/intake-ops.js.map +1 -1
  130. package/dist/runtime/memory-extra-ops.d.ts +6 -3
  131. package/dist/runtime/memory-extra-ops.d.ts.map +1 -1
  132. package/dist/runtime/memory-extra-ops.js +292 -4
  133. package/dist/runtime/memory-extra-ops.js.map +1 -1
  134. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  135. package/dist/runtime/planning-extra-ops.js +85 -0
  136. package/dist/runtime/planning-extra-ops.js.map +1 -1
  137. package/dist/runtime/playbook-ops.js +1 -1
  138. package/dist/runtime/playbook-ops.js.map +1 -1
  139. package/dist/runtime/runtime.d.ts.map +1 -1
  140. package/dist/runtime/runtime.js +143 -2
  141. package/dist/runtime/runtime.js.map +1 -1
  142. package/dist/runtime/session-briefing.d.ts +23 -0
  143. package/dist/runtime/session-briefing.d.ts.map +1 -0
  144. package/dist/runtime/session-briefing.js +140 -0
  145. package/dist/runtime/session-briefing.js.map +1 -0
  146. package/dist/runtime/types.d.ts +23 -0
  147. package/dist/runtime/types.d.ts.map +1 -1
  148. package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
  149. package/dist/runtime/vault-linking-ops.js +1 -3
  150. package/dist/runtime/vault-linking-ops.js.map +1 -1
  151. package/dist/vault/vault.d.ts +25 -0
  152. package/dist/vault/vault.d.ts.map +1 -1
  153. package/dist/vault/vault.js +67 -3
  154. package/dist/vault/vault.js.map +1 -1
  155. package/package.json +1 -1
  156. package/src/__tests__/admin-setup-ops.test.ts +355 -0
  157. package/src/__tests__/async-infrastructure.test.ts +307 -0
  158. package/src/__tests__/cognee-client-gaps.test.ts +6 -2
  159. package/src/__tests__/cognee-hybrid-search.test.ts +49 -35
  160. package/src/__tests__/cognee-sync-manager-deep.test.ts +89 -65
  161. package/src/__tests__/curator-extra-ops.test.ts +6 -2
  162. package/src/__tests__/curator-pipeline-e2e.test.ts +358 -0
  163. package/src/__tests__/memory-extra-ops.test.ts +2 -2
  164. package/src/__tests__/planning-extra-ops.test.ts +2 -2
  165. package/src/__tests__/second-brain-features.test.ts +583 -0
  166. package/src/agency/agency-manager.ts +217 -9
  167. package/src/agency/default-rules.ts +83 -0
  168. package/src/agency/types.ts +61 -0
  169. package/src/brain/brain.ts +110 -8
  170. package/src/brain/knowledge-synthesizer.ts +218 -0
  171. package/src/brain/learning-radar.ts +340 -0
  172. package/src/brain/types.ts +16 -0
  173. package/src/context/context-engine.ts +114 -15
  174. package/src/context/types.ts +5 -0
  175. package/src/control/intent-router.ts +107 -0
  176. package/src/control/types.ts +10 -0
  177. package/src/curator/classifier.ts +88 -0
  178. package/src/curator/quality-gate.ts +129 -0
  179. package/src/engine/bin/soleri-engine.ts +1 -0
  180. package/src/events/event-bus.ts +58 -0
  181. package/src/flows/chain-runner.ts +369 -0
  182. package/src/flows/chain-types.ts +57 -0
  183. package/src/health/doctor-checks.ts +115 -0
  184. package/src/intake/text-ingester.ts +234 -0
  185. package/src/llm/llm-client.ts +38 -1
  186. package/src/llm/oauth-discovery.ts +169 -0
  187. package/src/planning/evidence-collector.ts +247 -0
  188. package/src/planning/planner.ts +11 -0
  189. package/src/queue/job-queue.ts +281 -0
  190. package/src/queue/pipeline-runner.ts +149 -0
  191. package/src/runtime/admin-setup-ops.ts +664 -0
  192. package/src/runtime/chain-ops.ts +121 -0
  193. package/src/runtime/claude-md-helpers.ts +236 -0
  194. package/src/runtime/curator-extra-ops.ts +86 -3
  195. package/src/runtime/facades/admin-facade.ts +4 -0
  196. package/src/runtime/facades/agency-facade.ts +68 -0
  197. package/src/runtime/facades/brain-facade.ts +142 -1
  198. package/src/runtime/facades/control-facade.ts +45 -0
  199. package/src/runtime/facades/memory-facade.ts +20 -2
  200. package/src/runtime/facades/plan-facade.ts +2 -0
  201. package/src/runtime/facades/vault-facade.ts +28 -5
  202. package/src/runtime/intake-ops.ts +107 -5
  203. package/src/runtime/memory-extra-ops.ts +312 -4
  204. package/src/runtime/planning-extra-ops.ts +94 -0
  205. package/src/runtime/playbook-ops.ts +1 -1
  206. package/src/runtime/runtime.ts +138 -2
  207. package/src/runtime/session-briefing.ts +161 -0
  208. package/src/runtime/types.ts +23 -0
  209. package/src/runtime/vault-linking-ops.ts +1 -3
  210. package/src/vault/vault.ts +79 -4
@@ -1,11 +1,14 @@
1
1
  /**
2
- * Extended memory operations — 8 ops for advanced memory management.
2
+ * Extended memory operations — 18 ops for advanced memory management.
3
3
  *
4
4
  * These complement the 4 base memory ops in core-ops.ts:
5
5
  * memory_search, memory_capture, memory_list, session_capture
6
6
  *
7
- * New ops: memory_delete, memory_stats, memory_export, memory_import,
8
- * memory_prune, memory_deduplicate, memory_topics, memory_by_project
7
+ * CRUD: memory_delete, memory_stats, memory_export, memory_import,
8
+ * memory_prune, memory_deduplicate, memory_topics, memory_by_project
9
+ * Governance (#213): memory_get, session_search, knowledge_audit, smart_capture,
10
+ * knowledge_health, merge_patterns, knowledge_reorganize,
11
+ * list_project_knowledge, list_projects, knowledge_debug
9
12
  */
10
13
 
11
14
  import { z } from 'zod';
@@ -13,7 +16,7 @@ import type { OpDefinition } from '../facades/types.js';
13
16
  import type { AgentRuntime } from './types.js';
14
17
 
15
18
  export function createMemoryExtraOps(runtime: AgentRuntime): OpDefinition[] {
16
- const { vault } = runtime;
19
+ const { vault, brain, curator, linkManager } = runtime;
17
20
 
18
21
  return [
19
22
  {
@@ -116,6 +119,11 @@ export function createMemoryExtraOps(runtime: AgentRuntime): OpDefinition[] {
116
119
  topics: (m.topics as string[]) ?? [],
117
120
  filesModified: (m.filesModified as string[]) ?? [],
118
121
  toolsUsed: (m.toolsUsed as string[]) ?? [],
122
+ intent: (m.intent as string) ?? null,
123
+ decisions: (m.decisions as string[]) ?? [],
124
+ currentState: (m.currentState as string) ?? null,
125
+ nextSteps: (m.nextSteps as string[]) ?? [],
126
+ vaultEntriesReferenced: (m.vaultEntriesReferenced as string[]) ?? [],
119
127
  createdAt: m.createdAt as number,
120
128
  archivedAt: (m.archivedAt as number | null) ?? null,
121
129
  }));
@@ -182,5 +190,305 @@ export function createMemoryExtraOps(runtime: AgentRuntime): OpDefinition[] {
182
190
  return { count: groups.length, projects: groups };
183
191
  },
184
192
  },
193
+
194
+ // ─── Knowledge Governance (#213) ─────────────────────────────────
195
+
196
+ {
197
+ name: 'memory_get',
198
+ description: 'Get a single memory entry by ID.',
199
+ auth: 'read',
200
+ schema: z.object({
201
+ id: z.string().describe('Memory ID'),
202
+ }),
203
+ handler: async (params) => {
204
+ const memory = vault.getMemory(params.id as string);
205
+ if (!memory) return { found: false, id: params.id };
206
+ return memory;
207
+ },
208
+ },
209
+ {
210
+ name: 'session_search',
211
+ description: 'Search session memories with optional includeArchived flag.',
212
+ auth: 'read',
213
+ schema: z.object({
214
+ query: z.string().describe('Search query'),
215
+ includeArchived: z.boolean().optional().default(false),
216
+ intent: z.string().optional().describe('Filter by session intent'),
217
+ limit: z.number().optional().default(10),
218
+ }),
219
+ handler: async (params) => {
220
+ // Use searchMemories with type=session
221
+ const results = vault.searchMemories(params.query as string, {
222
+ type: 'session',
223
+ intent: params.intent as string | undefined,
224
+ limit: params.limit as number,
225
+ });
226
+ // If includeArchived, also search archived
227
+ if (params.includeArchived) {
228
+ try {
229
+ const archived = vault
230
+ .getProvider()
231
+ .all<Record<string, unknown>>(
232
+ "SELECT * FROM memories WHERE type = 'session' AND archived_at IS NOT NULL AND summary LIKE @q ORDER BY created_at DESC LIMIT @limit",
233
+ { q: `%${params.query}%`, limit: params.limit as number },
234
+ );
235
+ // Minimal parsing for archived results
236
+ const archivedMemories = archived.map((r) => ({
237
+ id: r.id,
238
+ summary: r.summary,
239
+ intent: r.intent ?? null,
240
+ createdAt: r.created_at,
241
+ archived: true,
242
+ }));
243
+ return { active: results, archived: archivedMemories };
244
+ } catch {
245
+ return { active: results, archived: [] };
246
+ }
247
+ }
248
+ return { results };
249
+ },
250
+ },
251
+ {
252
+ name: 'knowledge_audit',
253
+ description:
254
+ 'Audit vault knowledge quality — coverage, freshness, tag health, recommendations.',
255
+ auth: 'read',
256
+ handler: async () => {
257
+ const healthAudit = curator.healthAudit();
258
+ const vaultStats = vault.stats();
259
+ const brainStats = brain.getStats();
260
+ return {
261
+ vault: {
262
+ totalEntries: vaultStats.totalEntries,
263
+ byType: vaultStats.byType,
264
+ byDomain: vaultStats.byDomain,
265
+ },
266
+ health: healthAudit,
267
+ brain: brainStats,
268
+ };
269
+ },
270
+ },
271
+ {
272
+ name: 'smart_capture',
273
+ description:
274
+ 'Capture knowledge with auto-classification — infers type, tags, and severity from content.',
275
+ auth: 'write',
276
+ schema: z.object({
277
+ title: z.string(),
278
+ description: z.string(),
279
+ domain: z.string().optional().default('general'),
280
+ context: z.string().optional(),
281
+ why: z.string().optional(),
282
+ }),
283
+ handler: async (params) => {
284
+ const id = `smart-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
285
+ // Auto-infer type from keywords
286
+ const desc = (params.description as string).toLowerCase();
287
+ const inferredType: 'pattern' | 'anti-pattern' =
288
+ desc.includes('never') ||
289
+ desc.includes("don't") ||
290
+ desc.includes('avoid') ||
291
+ desc.includes('anti')
292
+ ? 'anti-pattern'
293
+ : 'pattern';
294
+ const inferredSeverity: 'critical' | 'warning' | 'suggestion' =
295
+ desc.includes('must') || desc.includes('critical') || desc.includes('always')
296
+ ? 'critical'
297
+ : desc.includes('should') || desc.includes('important')
298
+ ? 'warning'
299
+ : 'suggestion';
300
+
301
+ const result = brain.enrichAndCapture({
302
+ id,
303
+ type: inferredType,
304
+ domain: params.domain as string,
305
+ title: params.title as string,
306
+ description: params.description as string,
307
+ severity: inferredSeverity,
308
+ context: params.context as string | undefined,
309
+ why: params.why as string | undefined,
310
+ });
311
+
312
+ return {
313
+ ...result,
314
+ inferred: { type: inferredType, severity: inferredSeverity },
315
+ };
316
+ },
317
+ },
318
+ {
319
+ name: 'knowledge_health',
320
+ description: 'Knowledge base health — freshness, staleness, contradictions, coverage gaps.',
321
+ auth: 'read',
322
+ handler: async () => {
323
+ const audit = curator.healthAudit();
324
+ const ageReport = vault.getAgeReport();
325
+ const contradictions = curator.detectContradictions();
326
+ return {
327
+ score: audit.score,
328
+ metrics: audit.metrics,
329
+ ageDistribution: ageReport,
330
+ openContradictions: contradictions.filter((c) => c.status === 'open').length,
331
+ recommendations: audit.recommendations,
332
+ };
333
+ },
334
+ },
335
+ {
336
+ name: 'merge_patterns',
337
+ description: 'Merge two vault entries into one — combines tags, preserves links from both.',
338
+ auth: 'write',
339
+ schema: z.object({
340
+ keepId: z.string().describe('Entry ID to keep (survives the merge)'),
341
+ removeId: z.string().describe('Entry ID to remove (merged into keepId)'),
342
+ }),
343
+ handler: async (params) => {
344
+ const keepEntry = vault.get(params.keepId as string);
345
+ const removeEntry = vault.get(params.removeId as string);
346
+ if (!keepEntry) return { error: `Entry not found: ${params.keepId}` };
347
+ if (!removeEntry) return { error: `Entry not found: ${params.removeId}` };
348
+
349
+ // Merge tags
350
+ const mergedTags = [...new Set([...keepEntry.tags, ...removeEntry.tags])];
351
+
352
+ // Update the kept entry with merged tags and enriched description
353
+ vault.update(params.keepId as string, {
354
+ tags: mergedTags,
355
+ description: keepEntry.description.includes(removeEntry.title)
356
+ ? keepEntry.description
357
+ : `${keepEntry.description}\n\n[Merged from: ${removeEntry.title}] ${removeEntry.description}`,
358
+ });
359
+
360
+ // Transfer links from removed entry to kept entry
361
+ if (linkManager) {
362
+ try {
363
+ const links = linkManager.getLinks(params.removeId as string);
364
+ for (const link of links) {
365
+ const otherId = link.sourceId === params.removeId ? link.targetId : link.sourceId;
366
+ if (otherId !== params.keepId) {
367
+ try {
368
+ linkManager.addLink(
369
+ params.keepId as string,
370
+ otherId,
371
+ link.linkType,
372
+ `merged from ${params.removeId}`,
373
+ );
374
+ } catch {
375
+ /* duplicate link — skip */
376
+ }
377
+ }
378
+ }
379
+ } catch {
380
+ /* link manager ops failed — non-critical */
381
+ }
382
+ }
383
+
384
+ // Remove the merged entry
385
+ vault.remove(params.removeId as string);
386
+
387
+ return {
388
+ merged: true,
389
+ keptId: params.keepId,
390
+ removedId: params.removeId,
391
+ mergedTags,
392
+ };
393
+ },
394
+ },
395
+ {
396
+ name: 'knowledge_reorganize',
397
+ description: 'Re-categorize vault entries — change domain, retag, with dry-run preview.',
398
+ auth: 'write',
399
+ schema: z.object({
400
+ fromDomain: z.string().describe('Current domain to reorganize'),
401
+ toDomain: z.string().describe('Target domain'),
402
+ addTags: z.array(z.string()).optional().describe('Tags to add to all affected entries'),
403
+ removeTags: z
404
+ .array(z.string())
405
+ .optional()
406
+ .describe('Tags to remove from all affected entries'),
407
+ dryRun: z.boolean().optional().default(true).describe('Preview without applying'),
408
+ }),
409
+ handler: async (params) => {
410
+ const entries = vault.list({ limit: 10000 }).filter((e) => e.domain === params.fromDomain);
411
+ const addTags = (params.addTags as string[]) ?? [];
412
+ const removeTags = new Set((params.removeTags as string[]) ?? []);
413
+ const toDomain = params.toDomain as string;
414
+
415
+ if (params.dryRun) {
416
+ return {
417
+ dryRun: true,
418
+ affected: entries.length,
419
+ fromDomain: params.fromDomain,
420
+ toDomain,
421
+ entries: entries.slice(0, 20).map((e) => ({ id: e.id, title: e.title })),
422
+ };
423
+ }
424
+
425
+ let updated = 0;
426
+ for (const entry of entries) {
427
+ const newTags = [...entry.tags.filter((t) => !removeTags.has(t)), ...addTags];
428
+ vault.update(entry.id, { domain: toDomain, tags: [...new Set(newTags)] });
429
+ updated++;
430
+ }
431
+
432
+ return { applied: true, updated, fromDomain: params.fromDomain, toDomain };
433
+ },
434
+ },
435
+ {
436
+ name: 'list_project_knowledge',
437
+ description: 'List vault entries scoped to a project (by tier tag or domain).',
438
+ auth: 'read',
439
+ schema: z.object({
440
+ project: z.string().describe('Project name or path to filter by'),
441
+ limit: z.number().optional().default(50),
442
+ }),
443
+ handler: async () => {
444
+ // Entries with tier='project' or tagged with project name
445
+ const all = vault.list({ limit: 10000 });
446
+ const projectEntries = all.filter((e) => e.tier === 'project' || e.origin === 'user');
447
+ return {
448
+ count: projectEntries.length,
449
+ entries: projectEntries.slice(0, 50).map((e) => ({
450
+ id: e.id,
451
+ title: e.title,
452
+ type: e.type,
453
+ domain: e.domain,
454
+ tier: e.tier,
455
+ })),
456
+ };
457
+ },
458
+ },
459
+ {
460
+ name: 'list_projects',
461
+ description:
462
+ 'List all distinct domains and tiers in the vault — shows knowledge distribution.',
463
+ auth: 'read',
464
+ handler: async () => {
465
+ const stats = vault.stats();
466
+ return {
467
+ domains: Object.entries(stats.byDomain ?? {}).map(([domain, count]) => ({
468
+ domain,
469
+ count,
470
+ })),
471
+ types: Object.entries(stats.byType ?? {}).map(([type, count]) => ({ type, count })),
472
+ total: stats.totalEntries,
473
+ };
474
+ },
475
+ },
476
+ {
477
+ name: 'knowledge_debug',
478
+ description:
479
+ 'Debug knowledge system internals — vault DB stats, brain state, curator state, memory counts.',
480
+ auth: 'admin',
481
+ handler: async () => {
482
+ return {
483
+ vault: {
484
+ stats: vault.stats(),
485
+ recentCount: vault.getRecent(1).length > 0 ? 'has entries' : 'empty',
486
+ },
487
+ brain: brain.getStats(),
488
+ curator: curator.getStatus(),
489
+ memory: vault.memoryStats(),
490
+ };
491
+ },
492
+ },
185
493
  ];
186
494
  }
@@ -13,6 +13,7 @@ import { z } from 'zod';
13
13
  import type { OpDefinition } from '../facades/types.js';
14
14
  import type { AgentRuntime } from './types.js';
15
15
  import type { DriftItem, TaskEvidence } from '../planning/planner.js';
16
+ import { collectGitEvidence } from '../planning/evidence-collector.js';
16
17
  import { matchPlaybooks, type PlaybookMatchResult } from '../playbooks/index.js';
17
18
  import { entryToPlaybookDefinition } from '../playbooks/index.js';
18
19
 
@@ -714,5 +715,98 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
714
715
  }
715
716
  },
716
717
  },
718
+
719
+ // ─── Evidence-Based Reconciliation (#206) ─────────────────────
720
+ {
721
+ name: 'plan_reconcile_with_evidence',
722
+ description:
723
+ 'Cross-reference plan tasks against git diff to produce an evidence-based drift report. ' +
724
+ 'Shows which tasks have matching file changes, which are missing, and what unplanned work was done.',
725
+ auth: 'read',
726
+ schema: z.object({
727
+ planId: z.string().describe('Plan ID to verify against git'),
728
+ projectPath: z.string().describe('Project root (must be a git repo)'),
729
+ baseBranch: z
730
+ .string()
731
+ .optional()
732
+ .default('main')
733
+ .describe('Branch to diff against (default: main)'),
734
+ }),
735
+ handler: async (params) => {
736
+ try {
737
+ const plan = planner.get(params.planId as string);
738
+ if (!plan) return { error: `Plan not found: ${params.planId}` };
739
+
740
+ return collectGitEvidence(
741
+ plan,
742
+ params.projectPath as string,
743
+ params.baseBranch as string,
744
+ );
745
+ } catch (err) {
746
+ return { error: (err as Error).message };
747
+ }
748
+ },
749
+ },
750
+
751
+ // ─── Purge Plans (#215) ──────────────────────────────────────────
752
+ {
753
+ name: 'plan_purge',
754
+ description:
755
+ 'Permanently delete plans by mode: "archived" (only archived), "completed" (completed + archived), ' +
756
+ '"stale" (draft/approved older than 24h), or "specific" (by IDs). Use dryRun to preview.',
757
+ auth: 'admin',
758
+ schema: z.object({
759
+ mode: z.enum(['archived', 'completed', 'stale', 'specific']).describe('Purge mode'),
760
+ planIds: z.array(z.string()).optional().describe('Plan IDs for specific mode'),
761
+ dryRun: z.boolean().optional().default(false).describe('Preview without deleting'),
762
+ }),
763
+ handler: async (params) => {
764
+ const mode = params.mode as string;
765
+ const dryRun = params.dryRun as boolean;
766
+ const plans = planner.list();
767
+ const now = Date.now();
768
+ const staleThresholdMs = 24 * 60 * 60 * 1000;
769
+
770
+ let toPurge: typeof plans;
771
+ if (mode === 'archived') {
772
+ toPurge = plans.filter((p) => p.status === 'archived');
773
+ } else if (mode === 'completed') {
774
+ toPurge = plans.filter((p) => p.status === 'completed' || p.status === 'archived');
775
+ } else if (mode === 'stale') {
776
+ toPurge = plans.filter(
777
+ (p) =>
778
+ (p.status === 'draft' || p.status === 'approved' || p.status === 'brainstorming') &&
779
+ p.createdAt &&
780
+ now - p.createdAt > staleThresholdMs,
781
+ );
782
+ } else if (mode === 'specific') {
783
+ const ids = new Set((params.planIds as string[]) ?? []);
784
+ toPurge = plans.filter((p) => ids.has(p.id));
785
+ } else {
786
+ return { error: `Unknown purge mode: ${mode}` };
787
+ }
788
+
789
+ if (dryRun) {
790
+ return {
791
+ dryRun: true,
792
+ mode,
793
+ wouldPurge: toPurge.length,
794
+ plans: toPurge.map((p) => ({ id: p.id, status: p.status, objective: p.objective })),
795
+ };
796
+ }
797
+
798
+ let purged = 0;
799
+ for (const p of toPurge) {
800
+ try {
801
+ planner.remove(p.id);
802
+ purged++;
803
+ } catch {
804
+ // Skip plans that can't be removed
805
+ }
806
+ }
807
+
808
+ return { purged, mode };
809
+ },
810
+ },
717
811
  ];
718
812
  }
@@ -88,7 +88,7 @@ export function createPlaybookOps(runtime: AgentRuntime): OpDefinition[] {
88
88
  validation?: string;
89
89
  }>;
90
90
 
91
- const steps = rawSteps.map((s, i) => ({ ...s, order: i + 1 }));
91
+ const steps = rawSteps.map((s, i) => Object.assign({}, s, { order: i + 1 }));
92
92
  const id =
93
93
  (params.id as string | undefined) ??
94
94
  `playbook-${domain}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
@@ -19,10 +19,12 @@ import { LoopManager } from '../loop/loop-manager.js';
19
19
  import { IdentityManager } from '../control/identity-manager.js';
20
20
  import { IntentRouter } from '../control/intent-router.js';
21
21
  import { KeyPool, loadKeyPoolConfig } from '../llm/key-pool.js';
22
+ import { discoverAnthropicToken } from '../llm/oauth-discovery.js';
22
23
  import { loadIntelligenceData } from '../intelligence/loader.js';
23
24
  import { LLMClient } from '../llm/llm-client.js';
24
25
  import { CogneeSyncManager } from '../cognee/sync-manager.js';
25
26
  import { IntakePipeline } from '../intake/intake-pipeline.js';
27
+ import { TextIngester } from '../intake/text-ingester.js';
26
28
  import { Telemetry } from '../telemetry/telemetry.js';
27
29
  import { ProjectRegistry } from '../project/project-registry.js';
28
30
  import { TemplateManager } from '../prompts/template-manager.js';
@@ -39,6 +41,14 @@ import { VaultBranching } from '../vault/vault-branching.js';
39
41
  import { ContextEngine } from '../context/context-engine.js';
40
42
  import { AgencyManager } from '../agency/agency-manager.js';
41
43
  import { KnowledgeReview } from '../vault/knowledge-review.js';
44
+ import { LinkManager } from '../vault/linking.js';
45
+ import { LearningRadar } from '../brain/learning-radar.js';
46
+ import { KnowledgeSynthesizer } from '../brain/knowledge-synthesizer.js';
47
+ import { ChainRunner } from '../flows/chain-runner.js';
48
+ import { JobQueue } from '../queue/job-queue.js';
49
+ import { PipelineRunner } from '../queue/pipeline-runner.js';
50
+ import { evaluateQuality } from '../curator/quality-gate.js';
51
+ import { classifyEntry } from '../curator/classifier.js';
42
52
  import type { AgentRuntimeConfig, AgentRuntime } from './types.js';
43
53
 
44
54
  /**
@@ -96,7 +106,8 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
96
106
  }
97
107
 
98
108
  // Brain — intelligence layer (TF-IDF scoring, auto-tagging, dedup)
99
- const brain = new Brain(vault, cognee ?? undefined);
109
+ // Pass vaultManager so intelligentSearch queries all connected sources (not just agent tier)
110
+ const brain = new Brain(vault, cognee ?? undefined, vaultManager);
100
111
 
101
112
  // Brain Intelligence — pattern strengths, session knowledge, intelligence pipeline
102
113
  const brainIntelligence = new BrainIntelligence(vault, brain);
@@ -130,9 +141,15 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
130
141
  }
131
142
 
132
143
  // LLM key pools and client
144
+ // Try OAuth token discovery for Anthropic (Claude Code subscription → free API access)
133
145
  const keyPoolFiles = loadKeyPoolConfig(agentId);
146
+ const oauthToken = discoverAnthropicToken();
147
+ const anthropicConfig = keyPoolFiles.anthropic;
148
+ if (oauthToken && anthropicConfig.keys.length === 0) {
149
+ anthropicConfig.keys.push(oauthToken);
150
+ }
134
151
  const openaiKeyPool = new KeyPool(keyPoolFiles.openai);
135
- const anthropicKeyPool = new KeyPool(keyPoolFiles.anthropic);
152
+ const anthropicKeyPool = new KeyPool(anthropicConfig);
136
153
  const llmClient = new LLMClient(openaiKeyPool, anthropicKeyPool, agentId);
137
154
 
138
155
  // Cognee Sync Manager — queue-based dirty tracking with offline resilience (only when Cognee enabled)
@@ -146,8 +163,13 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
146
163
  vault.setSyncManager(syncManager);
147
164
  }
148
165
 
166
+ // Link Manager — Zettelkasten auto-linking on vault ingestion
167
+ const linkManager = new LinkManager(vault.getProvider());
168
+ vault.setLinkManager(linkManager, { enabled: true, maxLinks: 3 });
169
+
149
170
  // Intake Pipeline — PDF/book ingestion with LLM classification
150
171
  const intakePipeline = new IntakePipeline(vault.getProvider(), vault, llmClient);
172
+ const textIngester = new TextIngester(vault, llmClient);
151
173
 
152
174
  // Playbook Executor — in-memory step-by-step workflow sessions
153
175
  const playbookExecutor = new PlaybookExecutor();
@@ -217,6 +239,7 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
217
239
  templateManager,
218
240
  syncManager,
219
241
  intakePipeline,
242
+ textIngester,
220
243
  authPolicy: { mode: 'permissive', callerLevel: 'admin' },
221
244
  flags: new FeatureFlags(join(agentHome, 'flags.json')),
222
245
  health,
@@ -228,6 +251,119 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
228
251
  contextEngine,
229
252
  agencyManager,
230
253
  knowledgeReview,
254
+ linkManager,
255
+ learningRadar: new LearningRadar(vault, brain),
256
+ knowledgeSynthesizer: new KnowledgeSynthesizer(brain, llmClient),
257
+ chainRunner: new ChainRunner(vault.getProvider()),
258
+ jobQueue: new JobQueue(vault.getProvider()),
259
+ pipelineRunner: (() => {
260
+ const jq = new JobQueue(vault.getProvider());
261
+ const pr = new PipelineRunner(jq);
262
+ // Register default job handlers for curator pipeline
263
+ pr.registerHandler('tag-normalize', async (job) => {
264
+ const entry = vault.get(job.entryId ?? '');
265
+ if (!entry) return { skipped: true, reason: 'entry not found' };
266
+ const result = curator.normalizeTag(entry.tags[0] ?? '');
267
+ return result;
268
+ });
269
+ pr.registerHandler('dedup-check', async (job) => {
270
+ const entry = vault.get(job.entryId ?? '');
271
+ if (!entry) return { skipped: true, reason: 'entry not found' };
272
+ return curator.detectDuplicates(entry.id);
273
+ });
274
+ pr.registerHandler('auto-link', async (job) => {
275
+ if (linkManager) {
276
+ const suggestions = linkManager.suggestLinks(job.entryId ?? '', 3);
277
+ for (const s of suggestions) {
278
+ linkManager.addLink(
279
+ job.entryId ?? '',
280
+ s.entryId,
281
+ s.suggestedType,
282
+ `pipeline: ${s.reason}`,
283
+ );
284
+ }
285
+ return { linked: suggestions.length };
286
+ }
287
+ return { skipped: true, reason: 'link manager not available' };
288
+ });
289
+ pr.registerHandler('quality-gate', async (job) => {
290
+ const entry = vault.get(job.entryId ?? '');
291
+ if (!entry) return { skipped: true, reason: 'entry not found' };
292
+ return evaluateQuality(entry, llmClient);
293
+ });
294
+ pr.registerHandler('classify', async (job) => {
295
+ const entry = vault.get(job.entryId ?? '');
296
+ if (!entry) return { skipped: true, reason: 'entry not found' };
297
+ return classifyEntry(entry, llmClient);
298
+ });
299
+
300
+ // ─── 9 additional handlers for full Salvador parity (#216) ────
301
+ pr.registerHandler('enrich-frontmatter', async (job) => {
302
+ const entry = vault.get(job.entryId ?? '');
303
+ if (!entry) return { skipped: true, reason: 'entry not found' };
304
+ return curator.enrichMetadata(entry.id);
305
+ });
306
+ pr.registerHandler('detect-staleness', async (job) => {
307
+ const entry = vault.get(job.entryId ?? '');
308
+ if (!entry) return { skipped: true, reason: 'entry not found' };
309
+ // Check if entry is older than 90 days (using validFrom or fallback to 0)
310
+ const entryTimestamp = (entry.validFrom ?? 0) * 1000 || Date.now();
311
+ const ageMs = Date.now() - entryTimestamp;
312
+ const staleDays = 90;
313
+ const isStale = ageMs > staleDays * 86400000;
314
+ return { stale: isStale, ageDays: Math.floor(ageMs / 86400000), entryId: entry.id };
315
+ });
316
+ pr.registerHandler('detect-duplicate', async (job) => {
317
+ const entry = vault.get(job.entryId ?? '');
318
+ if (!entry) return { skipped: true, reason: 'entry not found' };
319
+ return curator.detectDuplicates(entry.id);
320
+ });
321
+ pr.registerHandler('detect-contradiction', async (job) => {
322
+ const entry = vault.get(job.entryId ?? '');
323
+ if (!entry) return { skipped: true, reason: 'entry not found' };
324
+ const contradictions = curator.detectContradictions(0.4);
325
+ const relevant = contradictions.filter(
326
+ (c) => c.patternId === job.entryId || c.antipatternId === job.entryId,
327
+ );
328
+ return { found: relevant.length, contradictions: relevant };
329
+ });
330
+ pr.registerHandler('consolidate-duplicates', async (_job) => {
331
+ return curator.consolidate({ dryRun: false, staleDaysThreshold: 90 });
332
+ });
333
+ pr.registerHandler('archive-stale', async (_job) => {
334
+ // Run consolidation with stale detection
335
+ const result = curator.consolidate({ dryRun: false, staleDaysThreshold: 90 });
336
+ return { archived: result.staleEntries.length, result };
337
+ });
338
+ pr.registerHandler('cognee-ingest', async (job) => {
339
+ if (!cognee) return { skipped: true, reason: 'cognee not available' };
340
+ const entry = vault.get(job.entryId ?? '');
341
+ if (!entry) return { skipped: true, reason: 'entry not found' };
342
+ try {
343
+ const result = await cognee.addEntries([entry]);
344
+ return { ingested: result.added };
345
+ } catch {
346
+ return { skipped: true, reason: 'cognee ingestion failed' };
347
+ }
348
+ });
349
+ pr.registerHandler('cognee-cognify', async (_job) => {
350
+ if (!cognee) return { skipped: true, reason: 'cognee not available' };
351
+ try {
352
+ const result = await cognee.cognify();
353
+ return result;
354
+ } catch {
355
+ return { skipped: true, reason: 'cognee cognify failed' };
356
+ }
357
+ });
358
+ pr.registerHandler('verify-searchable', async (job) => {
359
+ const entry = vault.get(job.entryId ?? '');
360
+ if (!entry) return { skipped: true, reason: 'entry not found' };
361
+ const searchResults = vault.search(entry.title, { limit: 1 });
362
+ const found = searchResults.some((r) => r.entry.id === entry.id);
363
+ return { searchable: found, entryId: entry.id };
364
+ });
365
+ return pr;
366
+ })(),
231
367
  createdAt: Date.now(),
232
368
  close: () => {
233
369
  syncManager?.close();