@soleri/forge 4.2.2 → 5.0.1

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 (78) hide show
  1. package/dist/domain-manager.d.ts +2 -2
  2. package/dist/domain-manager.js +35 -16
  3. package/dist/domain-manager.js.map +1 -1
  4. package/dist/index.js +0 -0
  5. package/dist/knowledge-installer.js +18 -12
  6. package/dist/knowledge-installer.js.map +1 -1
  7. package/dist/patching.d.ts +15 -6
  8. package/dist/patching.js +37 -12
  9. package/dist/patching.js.map +1 -1
  10. package/dist/scaffolder.js +18 -28
  11. package/dist/scaffolder.js.map +1 -1
  12. package/dist/templates/brain.d.ts +6 -0
  13. package/dist/templates/brain.js +478 -0
  14. package/dist/templates/brain.js.map +1 -0
  15. package/dist/templates/core-facade.js +95 -47
  16. package/dist/templates/core-facade.js.map +1 -1
  17. package/dist/templates/entry-point.d.ts +4 -0
  18. package/dist/templates/entry-point.js +146 -89
  19. package/dist/templates/entry-point.js.map +1 -1
  20. package/dist/templates/facade-factory.d.ts +1 -0
  21. package/dist/templates/facade-factory.js +63 -0
  22. package/dist/templates/facade-factory.js.map +1 -0
  23. package/dist/templates/facade-types.d.ts +1 -0
  24. package/dist/templates/facade-types.js +46 -0
  25. package/dist/templates/facade-types.js.map +1 -0
  26. package/dist/templates/intelligence-loader.d.ts +1 -0
  27. package/dist/templates/intelligence-loader.js +43 -0
  28. package/dist/templates/intelligence-loader.js.map +1 -0
  29. package/dist/templates/intelligence-types.d.ts +1 -0
  30. package/dist/templates/intelligence-types.js +24 -0
  31. package/dist/templates/intelligence-types.js.map +1 -0
  32. package/dist/templates/llm-key-pool.d.ts +7 -0
  33. package/dist/templates/llm-key-pool.js +211 -0
  34. package/dist/templates/llm-key-pool.js.map +1 -0
  35. package/dist/templates/llm-types.d.ts +5 -0
  36. package/dist/templates/llm-types.js +161 -0
  37. package/dist/templates/llm-types.js.map +1 -0
  38. package/dist/templates/llm-utils.d.ts +5 -0
  39. package/dist/templates/llm-utils.js +260 -0
  40. package/dist/templates/llm-utils.js.map +1 -0
  41. package/dist/templates/package-json.js +3 -1
  42. package/dist/templates/package-json.js.map +1 -1
  43. package/dist/templates/planner.d.ts +5 -0
  44. package/dist/templates/planner.js +150 -0
  45. package/dist/templates/planner.js.map +1 -0
  46. package/dist/templates/test-brain.d.ts +6 -0
  47. package/dist/templates/test-brain.js +474 -0
  48. package/dist/templates/test-brain.js.map +1 -0
  49. package/dist/templates/test-facades.d.ts +1 -1
  50. package/dist/templates/test-facades.js +182 -456
  51. package/dist/templates/test-facades.js.map +1 -1
  52. package/dist/templates/test-llm.d.ts +7 -0
  53. package/dist/templates/test-llm.js +574 -0
  54. package/dist/templates/test-llm.js.map +1 -0
  55. package/dist/templates/test-loader.d.ts +5 -0
  56. package/dist/templates/test-loader.js +146 -0
  57. package/dist/templates/test-loader.js.map +1 -0
  58. package/dist/templates/test-planner.d.ts +5 -0
  59. package/dist/templates/test-planner.js +271 -0
  60. package/dist/templates/test-planner.js.map +1 -0
  61. package/dist/templates/test-vault.d.ts +5 -0
  62. package/dist/templates/test-vault.js +380 -0
  63. package/dist/templates/test-vault.js.map +1 -0
  64. package/dist/templates/vault.d.ts +5 -0
  65. package/dist/templates/vault.js +263 -0
  66. package/dist/templates/vault.js.map +1 -0
  67. package/dist/types.d.ts +2 -2
  68. package/package.json +1 -1
  69. package/src/__tests__/scaffolder.test.ts +52 -109
  70. package/src/domain-manager.ts +34 -15
  71. package/src/knowledge-installer.ts +23 -15
  72. package/src/patching.ts +44 -12
  73. package/src/scaffolder.ts +18 -29
  74. package/src/templates/entry-point.ts +146 -91
  75. package/src/templates/package-json.ts +3 -1
  76. package/src/templates/test-facades.ts +182 -458
  77. package/src/templates/core-facade.ts +0 -517
  78. package/src/templates/llm-client.ts +0 -301
@@ -4,20 +4,20 @@
4
4
  */
5
5
  export function generateCoreFacade(config) {
6
6
  return `import { z } from 'zod';
7
- import type { FacadeConfig, Vault, IntelligenceEntry, Planner, Brain, KeyPool, CogneeClient } from '@soleri/core';
7
+ import type { FacadeConfig, Vault, IntelligenceEntry, Planner, Brain, KeyPool, Curator } from '@soleri/core';
8
8
  import { PERSONA } from '../identity/persona.js';
9
9
  import { activateAgent, deactivateAgent } from '../activation/activate.js';
10
10
  import { injectClaudeMd, injectClaudeMdGlobal, hasAgentMarker } from '../activation/inject-claude-md.js';
11
11
  import type { LLMClient } from '../llm/llm-client.js';
12
12
 
13
- export function createCoreFacade(vault: Vault, planner: Planner, brain: Brain, cognee?: CogneeClient, llmClient?: LLMClient, openaiKeyPool?: KeyPool, anthropicKeyPool?: KeyPool): FacadeConfig {
13
+ export function createCoreFacade(vault: Vault, planner: Planner, brain: Brain, llmClient?: LLMClient, openaiKeyPool?: KeyPool, anthropicKeyPool?: KeyPool, curator?: Curator): FacadeConfig {
14
14
  return {
15
15
  name: '${config.id}_core',
16
16
  description: 'Core operations — vault stats, cross-domain search, health check, identity, and activation system.',
17
17
  ops: [
18
18
  {
19
19
  name: 'search',
20
- description: 'Search across all knowledge domains. Results ranked by TF-IDF + severity + recency + tag overlap + domain match. When Cognee is available, adds vector similarity for hybrid ranking.',
20
+ description: 'Search across all knowledge domains. Results ranked by TF-IDF + severity + recency + tag overlap + domain match.',
21
21
  auth: 'read',
22
22
  schema: z.object({
23
23
  query: z.string(),
@@ -28,7 +28,7 @@ export function createCoreFacade(vault: Vault, planner: Planner, brain: Brain, c
28
28
  limit: z.number().optional(),
29
29
  }),
30
30
  handler: async (params) => {
31
- return await brain.intelligentSearch(params.query as string, {
31
+ return brain.intelligentSearch(params.query as string, {
32
32
  domain: params.domain as string | undefined,
33
33
  type: params.type as string | undefined,
34
34
  severity: params.severity as string | undefined,
@@ -448,64 +448,112 @@ export function createCoreFacade(vault: Vault, planner: Planner, brain: Brain, c
448
448
  };
449
449
  },
450
450
  },
451
+ // ─── Curator ops ───────────────────────────────────────────
451
452
  {
452
- name: 'cognee_status',
453
- description: 'Check Cognee vector search availability and connection status.',
453
+ name: 'curator_status',
454
+ description: 'Curator status table row counts, last groomed timestamp.',
454
455
  auth: 'read',
455
456
  handler: async () => {
456
- if (!cognee) return { available: false, message: 'Cognee not configured' };
457
- const status = cognee.getStatus();
458
- const cfg = cognee.getConfig();
459
- return {
460
- available: cognee.isAvailable,
461
- config: {
462
- baseUrl: cfg.baseUrl,
463
- dataset: cfg.dataset,
464
- timeoutMs: cfg.timeoutMs,
465
- searchTimeoutMs: cfg.searchTimeoutMs,
466
- healthTimeoutMs: cfg.healthTimeoutMs,
467
- healthCacheTtlMs: cfg.healthCacheTtlMs,
468
- cognifyDebounceMs: cfg.cognifyDebounceMs,
469
- hasApiToken: Boolean(cfg.apiToken),
470
- hasServiceCredentials: Boolean(cfg.serviceEmail && cfg.servicePassword),
471
- },
472
- lastHealth: status,
473
- };
457
+ if (!curator) return { error: 'Curator not initialized' };
458
+ return curator.getStatus();
474
459
  },
475
460
  },
476
461
  {
477
- name: 'cognee_sync',
478
- description: 'Force sync all vault entries to Cognee and trigger knowledge graph build. Requires Cognee to be running.',
479
- auth: 'write',
480
- handler: async () => {
481
- if (!cognee?.isAvailable) {
482
- return { synced: false, message: 'Cognee not available. Start with: docker compose -f docker/docker-compose.cognee.yml up -d' };
483
- }
484
- const result = await brain.syncToCognee();
485
- return { ok: true, entriesSynced: result.synced, cognified: result.cognified };
462
+ name: 'curator_detect_duplicates',
463
+ description: 'Detect duplicate entries using TF-IDF cosine similarity.',
464
+ auth: 'read',
465
+ schema: z.object({
466
+ entryId: z.string().optional().describe('Check a specific entry. Omit to scan all.'),
467
+ threshold: z.number().optional().describe('Similarity threshold (0-1). Default 0.45.'),
468
+ }),
469
+ handler: async (params) => {
470
+ if (!curator) return { error: 'Curator not initialized' };
471
+ return curator.detectDuplicates(
472
+ params.entryId as string | undefined,
473
+ params.threshold as number | undefined,
474
+ );
486
475
  },
487
476
  },
488
477
  {
489
- name: 'graph_search',
490
- description: 'Search using Cognee knowledge graph completion. Returns semantically related entries that keyword search might miss.',
478
+ name: 'curator_contradictions',
479
+ description: 'List or detect contradictions between patterns and anti-patterns.',
491
480
  auth: 'read',
492
481
  schema: z.object({
493
- query: z.string(),
494
- searchType: z.enum(['SUMMARIES', 'CHUNKS', 'RAG_COMPLETION', 'GRAPH_COMPLETION', 'GRAPH_SUMMARY_COMPLETION', 'NATURAL_LANGUAGE', 'FEELING_LUCKY', 'CHUNKS_LEXICAL']).optional().default('CHUNKS'),
495
- limit: z.number().optional().default(10),
482
+ status: z.enum(['open', 'resolved', 'dismissed']).optional().describe('Filter by status.'),
483
+ detect: z.boolean().optional().describe('If true, run detection before listing.'),
496
484
  }),
497
485
  handler: async (params) => {
498
- if (!cognee?.isAvailable) {
499
- return { results: [], message: 'Cognee not available — use search op for FTS5 results' };
486
+ if (!curator) return { error: 'Curator not initialized' };
487
+ if (params.detect) {
488
+ curator.detectContradictions();
500
489
  }
501
- const results = await cognee.search(
502
- params.query as string,
503
- {
504
- searchType: params.searchType as 'SUMMARIES' | 'CHUNKS' | 'GRAPH_COMPLETION',
505
- limit: params.limit as number,
506
- },
490
+ return curator.getContradictions(params.status as 'open' | 'resolved' | 'dismissed' | undefined);
491
+ },
492
+ },
493
+ {
494
+ name: 'curator_resolve_contradiction',
495
+ description: 'Resolve or dismiss a contradiction.',
496
+ auth: 'write',
497
+ schema: z.object({
498
+ id: z.number().describe('Contradiction ID.'),
499
+ resolution: z.enum(['resolved', 'dismissed']),
500
+ }),
501
+ handler: async (params) => {
502
+ if (!curator) return { error: 'Curator not initialized' };
503
+ return curator.resolveContradiction(
504
+ params.id as number,
505
+ params.resolution as 'resolved' | 'dismissed',
507
506
  );
508
- return { results, count: results.length };
507
+ },
508
+ },
509
+ {
510
+ name: 'curator_groom',
511
+ description: 'Groom a single entry — normalize tags, check staleness.',
512
+ auth: 'write',
513
+ schema: z.object({
514
+ entryId: z.string().describe('Entry ID to groom.'),
515
+ }),
516
+ handler: async (params) => {
517
+ if (!curator) return { error: 'Curator not initialized' };
518
+ return curator.groomEntry(params.entryId as string);
519
+ },
520
+ },
521
+ {
522
+ name: 'curator_groom_all',
523
+ description: 'Groom all vault entries — normalize tags, detect staleness.',
524
+ auth: 'write',
525
+ handler: async () => {
526
+ if (!curator) return { error: 'Curator not initialized' };
527
+ return curator.groomAll();
528
+ },
529
+ },
530
+ {
531
+ name: 'curator_consolidate',
532
+ description: 'Consolidate vault — find duplicates, stale entries, contradictions. Dry-run by default.',
533
+ auth: 'write',
534
+ schema: z.object({
535
+ dryRun: z.boolean().optional().describe('Default true. Set false to apply mutations.'),
536
+ staleDaysThreshold: z.number().optional().describe('Days before entry is stale. Default 90.'),
537
+ duplicateThreshold: z.number().optional().describe('Cosine similarity threshold. Default 0.45.'),
538
+ contradictionThreshold: z.number().optional().describe('Contradiction threshold. Default 0.4.'),
539
+ }),
540
+ handler: async (params) => {
541
+ if (!curator) return { error: 'Curator not initialized' };
542
+ return curator.consolidate({
543
+ dryRun: params.dryRun as boolean | undefined,
544
+ staleDaysThreshold: params.staleDaysThreshold as number | undefined,
545
+ duplicateThreshold: params.duplicateThreshold as number | undefined,
546
+ contradictionThreshold: params.contradictionThreshold as number | undefined,
547
+ });
548
+ },
549
+ },
550
+ {
551
+ name: 'curator_health_audit',
552
+ description: 'Audit vault health — score (0-100), coverage, freshness, quality, tag health, recommendations.',
553
+ auth: 'read',
554
+ handler: async () => {
555
+ if (!curator) return { error: 'Curator not initialized' };
556
+ return curator.healthAudit();
509
557
  },
510
558
  },
511
559
  ],
@@ -1 +1 @@
1
- {"version":3,"file":"core-facade.js","sourceRoot":"","sources":["../../src/templates/core-facade.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAmB;IACpD,OAAO;;;;;;;;;aASI,MAAM,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uGA2EiF,MAAM,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCA6D9E,MAAM,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gEAiCiB,MAAM,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0U1E,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"core-facade.js","sourceRoot":"","sources":["../../src/templates/core-facade.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAmB;IACpD,OAAO;;;;;;;;;aASI,MAAM,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uGA2EiF,MAAM,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCA6D9E,MAAM,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gEAiCiB,MAAM,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0X1E,CAAC;AACF,CAAC"}
@@ -1,5 +1,9 @@
1
1
  import type { AgentConfig } from '../types.js';
2
2
  /**
3
3
  * Generate the main index.ts entry point for the agent.
4
+ *
5
+ * v5.0: Thin shell — delegates to createAgentRuntime(), createCoreOps(),
6
+ * and createDomainFacades() from @soleri/core. Only agent-specific code
7
+ * (persona, activation) lives here.
4
8
  */
5
9
  export declare function generateEntryPoint(config: AgentConfig): string;
@@ -1,119 +1,182 @@
1
1
  /**
2
2
  * Generate the main index.ts entry point for the agent.
3
+ *
4
+ * v5.0: Thin shell — delegates to createAgentRuntime(), createCoreOps(),
5
+ * and createDomainFacades() from @soleri/core. Only agent-specific code
6
+ * (persona, activation) lives here.
3
7
  */
4
8
  export function generateEntryPoint(config) {
5
- const facadeImports = config.domains
6
- .map((d) => {
7
- const fn = `create${pascalCase(d)}Facade`;
8
- const file = `${d}.facade.js`;
9
- return `import { ${fn} } from './facades/${file}';`;
10
- })
11
- .join('\n');
12
- const facadeCreations = config.domains
13
- .map((d) => ` create${pascalCase(d)}Facade(vault, brain),`)
14
- .join('\n');
9
+ const domainsLiteral = JSON.stringify(config.domains);
15
10
  return `#!/usr/bin/env node
16
11
 
17
12
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
18
13
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
19
14
  import { dirname, join } from 'node:path';
20
15
  import { fileURLToPath } from 'node:url';
21
- import { homedir } from 'node:os';
22
16
 
23
- import { Vault, Brain, Planner, KeyPool, CogneeClient, loadKeyPoolConfig, loadIntelligenceData, registerAllFacades } from '@soleri/core';
24
- ${facadeImports}
25
- import { createCoreFacade } from './facades/core.facade.js';
26
- import { LLMClient } from './llm/llm-client.js';
17
+ import {
18
+ createAgentRuntime,
19
+ createCoreOps,
20
+ createDomainFacades,
21
+ registerAllFacades,
22
+ } from '@soleri/core';
23
+ import type { OpDefinition } from '@soleri/core';
24
+ import { z } from 'zod';
27
25
  import { PERSONA, getPersonaPrompt } from './identity/persona.js';
26
+ import { activateAgent, deactivateAgent } from './activation/activate.js';
27
+ import { injectClaudeMd, injectClaudeMdGlobal, hasAgentMarker } from './activation/inject-claude-md.js';
28
28
 
29
29
  const __dirname = dirname(fileURLToPath(import.meta.url));
30
30
 
31
31
  async function main(): Promise<void> {
32
- // Initialize persistent vault at ~/.${config.id}/vault.db
33
- const vaultPath = join(homedir(), '.${config.id}', 'vault.db');
34
- const vault = new Vault(vaultPath);
35
- console.error(\`[\${PERSONA.name.toLowerCase()}] Vault: \${vaultPath}\`);
36
-
37
- // Load and seed intelligence data
38
- const dataDir = join(__dirname, 'intelligence', 'data');
39
- const entries = loadIntelligenceData(dataDir);
40
- if (entries.length > 0) {
41
- const seeded = vault.seed(entries);
42
- console.error(\`[\${PERSONA.name.toLowerCase()}] Seeded vault with \${seeded} intelligence entries\`);
43
- } else {
44
- console.error(\`[\${PERSONA.name.toLowerCase()}] Vault is empty — ready for knowledge capture\`);
45
- }
46
-
47
- // Initialize planner at ~/.${config.id}/plans.json
48
- const plansPath = join(homedir(), '.${config.id}', 'plans.json');
49
- const planner = new Planner(plansPath);
50
- console.error(\`[\${PERSONA.name.toLowerCase()}] Planner: \${plansPath}\`);
51
-
52
- // Initialize Cognee client (optional — vector search + knowledge graph)
53
- // Auto-registers a service account on first use. Override with env vars.
54
- const cognee = new CogneeClient({
55
- baseUrl: process.env.COGNEE_URL ?? 'http://localhost:8000',
56
- dataset: '${config.id}-vault',
57
- ...(process.env.COGNEE_API_TOKEN ? { apiToken: process.env.COGNEE_API_TOKEN } : {}),
58
- ...(process.env.COGNEE_EMAIL ? { serviceEmail: process.env.COGNEE_EMAIL } : {}),
59
- ...(process.env.COGNEE_PASSWORD ? { servicePassword: process.env.COGNEE_PASSWORD } : {}),
32
+ // ─── Runtime vault, brain, planner, curator, LLM, key pools ───
33
+ const runtime = createAgentRuntime({
34
+ agentId: '${config.id}',
35
+ dataDir: join(__dirname, 'intelligence', 'data'),
60
36
  });
61
- await cognee.healthCheck();
62
- console.error(\`[\${PERSONA.name.toLowerCase()}] Cognee: \${cognee.isAvailable ? 'available' : 'not running (FTS5 fallback)'}\`);
63
-
64
- // Initialize brain intelligence layer for ranked search, auto-tagging, dedup
65
- const brain = new Brain(vault, cognee.isAvailable ? cognee : undefined);
66
- console.error(\`[\${PERSONA.name.toLowerCase()}] Brain: vocabulary \${brain.getVocabularySize()} terms\`);
67
-
68
- // Background sync vault to Cognee if available
69
- if (cognee.isAvailable) {
70
- brain.syncToCognee().catch((err) => {
71
- console.error(\`[\${PERSONA.name.toLowerCase()}] Cognee sync failed:\`, err);
72
- });
73
- }
74
-
75
- // Initialize LLM client (optional works without API keys)
76
- const keyPoolFiles = loadKeyPoolConfig('${config.id}');
77
- const openaiKeyPool = new KeyPool(keyPoolFiles.openai);
78
- const anthropicKeyPool = new KeyPool(keyPoolFiles.anthropic);
79
- const llmClient = new LLMClient(openaiKeyPool, anthropicKeyPool);
80
- const llmAvail = llmClient.isAvailable();
81
- console.error(\`[\${PERSONA.name.toLowerCase()}] LLM: OpenAI \${llmAvail.openai ? 'available' : 'not configured'}, Anthropic \${llmAvail.anthropic ? 'available' : 'not configured'}\`);
82
-
83
- // Create MCP server
37
+
38
+ const tag = PERSONA.name.toLowerCase();
39
+ const stats = runtime.vault.stats();
40
+ console.error(\`[\${tag}] Vault: \${stats.totalEntries} entries, Brain: \${runtime.brain.getVocabularySize()} terms\`);
41
+
42
+ const llmAvail = runtime.llmClient.isAvailable();
43
+ console.error(\`[\${tag}] LLM: OpenAI \${llmAvail.openai ? 'available' : 'not configured'}, Anthropic \${llmAvail.anthropic ? 'available' : 'not configured'}\`);
44
+
45
+ // ─── Agent-specific ops (reference persona + activation) ────────
46
+ const agentOps: OpDefinition[] = [
47
+ {
48
+ name: 'health',
49
+ description: 'Health check — vault status and agent info.',
50
+ auth: 'read',
51
+ handler: async () => {
52
+ const s = runtime.vault.stats();
53
+ return {
54
+ status: 'ok',
55
+ agent: { name: PERSONA.name, role: PERSONA.role },
56
+ vault: { entries: s.totalEntries, domains: Object.keys(s.byDomain) },
57
+ };
58
+ },
59
+ },
60
+ {
61
+ name: 'identity',
62
+ description: 'Get agent identity — name, role, principles.',
63
+ auth: 'read',
64
+ handler: async () => PERSONA,
65
+ },
66
+ {
67
+ name: 'activate',
68
+ description: 'Activate agent persona — returns full context for Claude to adopt. Say "Hello, ${config.name}!" to trigger.',
69
+ auth: 'read',
70
+ schema: z.object({
71
+ projectPath: z.string().optional().default('.'),
72
+ deactivate: z.boolean().optional(),
73
+ }),
74
+ handler: async (params) => {
75
+ if (params.deactivate) {
76
+ return deactivateAgent();
77
+ }
78
+ return activateAgent(runtime.vault, (params.projectPath as string) ?? '.', runtime.planner);
79
+ },
80
+ },
81
+ {
82
+ name: 'inject_claude_md',
83
+ description: 'Inject agent sections into CLAUDE.md — project-level or global (~/.claude/CLAUDE.md). Idempotent.',
84
+ auth: 'write',
85
+ schema: z.object({
86
+ projectPath: z.string().optional().default('.'),
87
+ global: z.boolean().optional().describe('If true, inject into ~/.claude/CLAUDE.md instead of project-level'),
88
+ }),
89
+ handler: async (params) => {
90
+ if (params.global) {
91
+ return injectClaudeMdGlobal();
92
+ }
93
+ return injectClaudeMd((params.projectPath as string) ?? '.');
94
+ },
95
+ },
96
+ {
97
+ name: 'setup',
98
+ description: 'Check setup status — CLAUDE.md configured? Vault has entries? What to do next?',
99
+ auth: 'read',
100
+ schema: z.object({
101
+ projectPath: z.string().optional().default('.'),
102
+ }),
103
+ handler: async (params) => {
104
+ const { existsSync } = await import('node:fs');
105
+ const { join: joinPath } = await import('node:path');
106
+ const { homedir } = await import('node:os');
107
+ const projectPath = (params.projectPath as string) ?? '.';
108
+
109
+ const projectClaudeMd = joinPath(projectPath, 'CLAUDE.md');
110
+ const globalClaudeMd = joinPath(homedir(), '.claude', 'CLAUDE.md');
111
+
112
+ const projectExists = existsSync(projectClaudeMd);
113
+ const projectHasAgent = hasAgentMarker(projectClaudeMd);
114
+ const globalExists = existsSync(globalClaudeMd);
115
+ const globalHasAgent = hasAgentMarker(globalClaudeMd);
116
+
117
+ const s = runtime.vault.stats();
118
+
119
+ const recommendations: string[] = [];
120
+ if (!globalHasAgent && !projectHasAgent) {
121
+ recommendations.push('No CLAUDE.md configured — run inject_claude_md with global: true for all projects, or without for this project');
122
+ } else if (!globalHasAgent) {
123
+ recommendations.push('Global ~/.claude/CLAUDE.md not configured — run inject_claude_md with global: true to enable in all projects');
124
+ }
125
+ if (s.totalEntries === 0) {
126
+ recommendations.push('Vault is empty — add intelligence data or capture knowledge via domain facades');
127
+ }
128
+ if (recommendations.length === 0) {
129
+ recommendations.push('${config.name} is fully set up and ready!');
130
+ }
131
+
132
+ return {
133
+ agent: { name: PERSONA.name, role: PERSONA.role },
134
+ claude_md: {
135
+ project: { exists: projectExists, has_agent_section: projectHasAgent },
136
+ global: { exists: globalExists, has_agent_section: globalHasAgent },
137
+ },
138
+ vault: { entries: s.totalEntries, domains: Object.keys(s.byDomain) },
139
+ recommendations,
140
+ };
141
+ },
142
+ },
143
+ ];
144
+
145
+ // ─── Assemble facades ──────────────────────────────────────────
146
+ const coreOps = createCoreOps(runtime);
147
+ const coreFacade = {
148
+ name: '${config.id}_core',
149
+ description: 'Core operations — vault stats, cross-domain search, health check, identity, and activation system.',
150
+ ops: [...coreOps, ...agentOps],
151
+ };
152
+
153
+ const domainFacades = createDomainFacades(runtime, '${config.id}', ${domainsLiteral});
154
+
155
+ // ─── MCP server ────────────────────────────────────────────────
84
156
  const server = new McpServer({
85
157
  name: '${config.id}-mcp',
86
158
  version: '1.0.0',
87
159
  });
88
160
 
89
- // Register persona prompt
90
161
  server.prompt('persona', 'Get agent persona and principles', async () => ({
91
162
  messages: [{ role: 'assistant' as const, content: { type: 'text' as const, text: getPersonaPrompt() } }],
92
163
  }));
93
164
 
94
- // Create and register facades
95
- const facades = [
96
- ${facadeCreations}
97
- createCoreFacade(vault, planner, brain, cognee.isAvailable ? cognee : undefined, llmClient, openaiKeyPool, anthropicKeyPool),
98
- ];
99
-
165
+ const facades = [coreFacade, ...domainFacades];
100
166
  registerAllFacades(server, facades);
101
167
 
102
- const stats = vault.stats();
103
- console.error(\`[\${PERSONA.name.toLowerCase()}] \${PERSONA.name} \${PERSONA.role}\`);
104
- console.error(\`[\${PERSONA.name.toLowerCase()}] Vault: \${stats.totalEntries} entries across \${Object.keys(stats.byDomain).length} domains\`);
105
- console.error(\`[\${PERSONA.name.toLowerCase()}] Registered \${facades.length} facades with \${facades.reduce((sum, f) => sum + f.ops.length, 0)} operations\`);
168
+ console.error(\`[\${tag}] \${PERSONA.name} \${PERSONA.role}\`);
169
+ console.error(\`[\${tag}] Registered \${facades.length} facades with \${facades.reduce((sum, f) => sum + f.ops.length, 0)} operations\`);
106
170
 
107
- // Stdio transport
171
+ // ─── Transport + shutdown ──────────────────────────────────────
108
172
  const transport = new StdioServerTransport();
109
173
  await server.connect(transport);
110
- console.error(\`[\${PERSONA.name.toLowerCase()}] Connected via stdio transport\`);
111
- console.error(\`[\${PERSONA.name.toLowerCase()}] Say "Hello, \${PERSONA.name}!" to activate\`);
174
+ console.error(\`[\${tag}] Connected via stdio transport\`);
175
+ console.error(\`[\${tag}] Say "Hello, \${PERSONA.name}!" to activate\`);
112
176
 
113
- // Graceful shutdown
114
177
  const shutdown = async (): Promise<void> => {
115
- console.error(\`[\${PERSONA.name.toLowerCase()}] Shutting down...\`);
116
- vault.close();
178
+ console.error(\`[\${tag}] Shutting down...\`);
179
+ runtime.close();
117
180
  process.exit(0);
118
181
  };
119
182
  process.on('SIGTERM', shutdown);
@@ -126,10 +189,4 @@ main().catch((err) => {
126
189
  });
127
190
  `;
128
191
  }
129
- function pascalCase(s) {
130
- return s
131
- .split(/[-_\s]+/)
132
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
133
- .join('');
134
- }
135
192
  //# sourceMappingURL=entry-point.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"entry-point.js","sourceRoot":"","sources":["../../src/templates/entry-point.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAmB;IACpD,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO;SACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,EAAE,GAAG,SAAS,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC1C,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC;QAC9B,OAAO,YAAY,EAAE,sBAAsB,IAAI,IAAI,CAAC;IACtD,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO;SACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,UAAU,CAAC,CAAC,CAAC,uBAAuB,CAAC;SAC7D,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;;;;;;;;;EASP,aAAa;;;;;;;;yCAQ0B,MAAM,CAAC,EAAE;wCACV,MAAM,CAAC,EAAE;;;;;;;;;;;;;;gCAcjB,MAAM,CAAC,EAAE;wCACD,MAAM,CAAC,EAAE;;;;;;;;gBAQjC,MAAM,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;4CAoBmB,MAAM,CAAC,EAAE;;;;;;;;;aASxC,MAAM,CAAC,EAAE;;;;;;;;;;;EAWpB,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA4BG,MAAM,CAAC,EAAE;;;CAG5B,CAAC;AACF,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC;SACL,KAAK,CAAC,SAAS,CAAC;SAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAClD,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"entry-point.js","sourceRoot":"","sources":["../../src/templates/entry-point.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAmB;IACpD,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEtD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;gBAwBO,MAAM,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qGAkC4E,MAAM,CAAC,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCA6D9E,MAAM,CAAC,IAAI;;;;;;;;;;;;;;;;;;;aAmBhC,MAAM,CAAC,EAAE;;;;;wDAKkC,MAAM,CAAC,EAAE,MAAM,cAAc;;;;aAIxE,MAAM,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA8BF,MAAM,CAAC,EAAE;;;CAG5B,CAAC;AACF,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function generateFacadeFactory(): string;
@@ -0,0 +1,63 @@
1
+ export function generateFacadeFactory() {
2
+ return `import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { z } from 'zod';
4
+ import type { FacadeConfig, FacadeResponse } from './types.js';
5
+
6
+ export function registerFacade(server: McpServer, facade: FacadeConfig): void {
7
+ const opNames = facade.ops.map((o) => o.name);
8
+
9
+ server.tool(
10
+ facade.name,
11
+ \`\${facade.description}\\n\\nOperations: \${opNames.join(', ')}\`,
12
+ {
13
+ op: z.string().describe(\`Operation: \${opNames.join(' | ')}\`),
14
+ params: z.record(z.unknown()).optional().default({}).describe('Operation parameters'),
15
+ },
16
+ async ({ op, params }): Promise<{ content: Array<{ type: 'text'; text: string }> }> => {
17
+ const response = await dispatchOp(facade, op, params);
18
+ return { content: [{ type: 'text' as const, text: JSON.stringify(response, null, 2) }] };
19
+ },
20
+ );
21
+ }
22
+
23
+ async function dispatchOp(
24
+ facade: FacadeConfig,
25
+ opName: string,
26
+ params: Record<string, unknown>,
27
+ ): Promise<FacadeResponse> {
28
+ const op = facade.ops.find((o) => o.name === opName);
29
+ if (!op) {
30
+ return {
31
+ success: false,
32
+ error: \`Unknown operation "\${opName}" on \${facade.name}. Available: \${facade.ops.map((o) => o.name).join(', ')}\`,
33
+ op: opName,
34
+ facade: facade.name,
35
+ };
36
+ }
37
+
38
+ try {
39
+ let validatedParams = params;
40
+ if (op.schema) {
41
+ const result = op.schema.safeParse(params);
42
+ if (!result.success) {
43
+ return { success: false, error: \`Invalid params for \${opName}: \${result.error.message}\`, op: opName, facade: facade.name };
44
+ }
45
+ validatedParams = result.data as Record<string, unknown>;
46
+ }
47
+
48
+ const data = await op.handler(validatedParams);
49
+ return { success: true, data, op: opName, facade: facade.name };
50
+ } catch (err) {
51
+ const message = err instanceof Error ? err.message : String(err);
52
+ return { success: false, error: message, op: opName, facade: facade.name };
53
+ }
54
+ }
55
+
56
+ export function registerAllFacades(server: McpServer, facades: FacadeConfig[]): void {
57
+ for (const facade of facades) {
58
+ registerFacade(server, facade);
59
+ }
60
+ }
61
+ `;
62
+ }
63
+ //# sourceMappingURL=facade-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facade-factory.js","sourceRoot":"","sources":["../../src/templates/facade-factory.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,qBAAqB;IACnC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2DR,CAAC;AACF,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function generateFacadeTypes(): string;
@@ -0,0 +1,46 @@
1
+ export function generateFacadeTypes() {
2
+ return `import { z } from 'zod';
3
+
4
+ /** Handler function for a single facade operation */
5
+ export type OpHandler = (params: Record<string, unknown>) => Promise<unknown>;
6
+
7
+ /** Auth level required for an operation */
8
+ export type AuthLevel = 'read' | 'write' | 'admin';
9
+
10
+ /** Operation definition within a facade */
11
+ export interface OpDefinition {
12
+ name: string;
13
+ description: string;
14
+ auth: AuthLevel;
15
+ handler: OpHandler;
16
+ schema?: z.ZodType;
17
+ }
18
+
19
+ /** Facade configuration — one MCP tool */
20
+ export interface FacadeConfig {
21
+ /** MCP tool name */
22
+ name: string;
23
+ /** Human-readable description */
24
+ description: string;
25
+ /** Domain operations */
26
+ ops: OpDefinition[];
27
+ }
28
+
29
+ /** Standard facade response envelope */
30
+ export interface FacadeResponse {
31
+ success: boolean;
32
+ data?: unknown;
33
+ error?: string;
34
+ op?: string;
35
+ facade?: string;
36
+ }
37
+
38
+ export const facadeInputSchema = z.object({
39
+ op: z.string().describe('Operation name'),
40
+ params: z.record(z.unknown()).optional().default({}),
41
+ });
42
+
43
+ export type FacadeInput = z.infer<typeof facadeInputSchema>;
44
+ `;
45
+ }
46
+ //# sourceMappingURL=facade-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facade-types.js","sourceRoot":"","sources":["../../src/templates/facade-types.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,mBAAmB;IACjC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CR,CAAC;AACF,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function generateIntelligenceLoader(): string;