@soleri/forge 5.14.10 → 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 (65) hide show
  1. package/dist/agent-schema.d.ts +323 -0
  2. package/dist/agent-schema.js +151 -0
  3. package/dist/agent-schema.js.map +1 -0
  4. package/dist/compose-claude-md.d.ts +24 -0
  5. package/dist/compose-claude-md.js +197 -0
  6. package/dist/compose-claude-md.js.map +1 -0
  7. package/dist/index.js +0 -0
  8. package/dist/lib.d.ts +12 -1
  9. package/dist/lib.js +10 -1
  10. package/dist/lib.js.map +1 -1
  11. package/dist/scaffold-filetree.d.ts +22 -0
  12. package/dist/scaffold-filetree.js +361 -0
  13. package/dist/scaffold-filetree.js.map +1 -0
  14. package/dist/scaffolder.js +261 -11
  15. package/dist/scaffolder.js.map +1 -1
  16. package/dist/templates/activate.js +39 -1
  17. package/dist/templates/activate.js.map +1 -1
  18. package/dist/templates/agents-md.d.ts +10 -1
  19. package/dist/templates/agents-md.js +76 -16
  20. package/dist/templates/agents-md.js.map +1 -1
  21. package/dist/templates/claude-md-template.js +9 -1
  22. package/dist/templates/claude-md-template.js.map +1 -1
  23. package/dist/templates/entry-point.js +83 -6
  24. package/dist/templates/entry-point.js.map +1 -1
  25. package/dist/templates/inject-claude-md.js +53 -0
  26. package/dist/templates/inject-claude-md.js.map +1 -1
  27. package/dist/templates/package-json.js +4 -1
  28. package/dist/templates/package-json.js.map +1 -1
  29. package/dist/templates/readme.js +4 -3
  30. package/dist/templates/readme.js.map +1 -1
  31. package/dist/templates/setup-script.js +109 -3
  32. package/dist/templates/setup-script.js.map +1 -1
  33. package/dist/templates/shared-rules.js +54 -17
  34. package/dist/templates/shared-rules.js.map +1 -1
  35. package/dist/templates/test-facades.js +151 -6
  36. package/dist/templates/test-facades.js.map +1 -1
  37. package/dist/types.d.ts +71 -6
  38. package/dist/types.js +39 -1
  39. package/dist/types.js.map +1 -1
  40. package/dist/utils/detect-domain-packs.d.ts +25 -0
  41. package/dist/utils/detect-domain-packs.js +104 -0
  42. package/dist/utils/detect-domain-packs.js.map +1 -0
  43. package/package.json +2 -1
  44. package/src/__tests__/detect-domain-packs.test.ts +178 -0
  45. package/src/__tests__/scaffold-filetree.test.ts +257 -0
  46. package/src/__tests__/scaffolder.test.ts +5 -3
  47. package/src/agent-schema.ts +184 -0
  48. package/src/compose-claude-md.ts +252 -0
  49. package/src/lib.ts +14 -1
  50. package/src/scaffold-filetree.ts +421 -0
  51. package/src/scaffolder.ts +299 -15
  52. package/src/templates/activate.ts +39 -0
  53. package/src/templates/agents-md.ts +78 -16
  54. package/src/templates/claude-md-template.ts +12 -1
  55. package/src/templates/entry-point.ts +90 -6
  56. package/src/templates/inject-claude-md.ts +53 -0
  57. package/src/templates/package-json.ts +4 -1
  58. package/src/templates/readme.ts +4 -3
  59. package/src/templates/setup-script.ts +110 -4
  60. package/src/templates/shared-rules.ts +55 -17
  61. package/src/templates/test-facades.ts +156 -6
  62. package/src/types.ts +44 -1
  63. package/src/utils/detect-domain-packs.ts +129 -0
  64. package/tsconfig.json +0 -1
  65. package/vitest.config.ts +1 -2
package/src/scaffolder.ts CHANGED
@@ -9,7 +9,13 @@ import {
9
9
  import { join, dirname } from 'node:path';
10
10
  import { homedir } from 'node:os';
11
11
  import { execFileSync } from 'node:child_process';
12
- import type { AgentConfig, ScaffoldResult, ScaffoldPreview, AgentInfo } from './types.js';
12
+ import type {
13
+ AgentConfig,
14
+ SetupTarget,
15
+ ScaffoldResult,
16
+ ScaffoldPreview,
17
+ AgentInfo,
18
+ } from './types.js';
13
19
 
14
20
  import { generatePackageJson } from './templates/package-json.js';
15
21
  import { generateTsconfig } from './templates/tsconfig.js';
@@ -29,30 +35,49 @@ import { generateTelegramBot } from './templates/telegram-bot.js';
29
35
  import { generateTelegramConfig } from './templates/telegram-config.js';
30
36
  import { generateTelegramAgent } from './templates/telegram-agent.js';
31
37
  import { generateTelegramSupervisor } from './templates/telegram-supervisor.js';
38
+ import { detectInstalledDomainPacks } from './utils/detect-domain-packs.js';
32
39
 
33
- function getSetupTarget(config: AgentConfig): 'claude' | 'codex' | 'both' {
40
+ function getSetupTarget(config: AgentConfig): SetupTarget {
34
41
  return config.setupTarget ?? 'claude';
35
42
  }
36
43
 
37
44
  function includesClaudeSetup(config: AgentConfig): boolean {
38
45
  const target = getSetupTarget(config);
39
- return target === 'claude' || target === 'both';
46
+ return target === 'claude' || target === 'both' || target === 'all';
40
47
  }
41
48
 
42
49
  function includesCodexSetup(config: AgentConfig): boolean {
43
50
  const target = getSetupTarget(config);
44
- return target === 'codex' || target === 'both';
51
+ return target === 'codex' || target === 'both' || target === 'all';
52
+ }
53
+
54
+ function includesOpencodeSetup(config: AgentConfig): boolean {
55
+ const target = getSetupTarget(config);
56
+ return target === 'opencode' || target === 'all';
45
57
  }
46
58
 
47
59
  /**
48
60
  * Preview what scaffold will create without writing anything.
49
61
  */
50
62
  export function previewScaffold(config: AgentConfig): ScaffoldPreview {
63
+ // Auto-detect domain packs if not explicitly configured
64
+ if (!config.domainPacks || config.domainPacks.length === 0) {
65
+ const detected = detectInstalledDomainPacks(config.outputDir);
66
+ if (detected.length > 0) {
67
+ config = { ...config, domainPacks: detected };
68
+ }
69
+ }
70
+
51
71
  const agentDir = join(config.outputDir, config.id);
52
72
  const claudeSetup = includesClaudeSetup(config);
53
73
  const codexSetup = includesCodexSetup(config);
54
- const setupLabel =
55
- claudeSetup && codexSetup ? 'Claude Code + Codex' : claudeSetup ? 'Claude Code' : 'Codex';
74
+ const opencodeSetup = includesOpencodeSetup(config);
75
+ const setupParts = [
76
+ ...(claudeSetup ? ['Claude Code'] : []),
77
+ ...(codexSetup ? ['Codex'] : []),
78
+ ...(opencodeSetup ? ['OpenCode'] : []),
79
+ ];
80
+ const setupLabel = setupParts.join(' + ');
56
81
 
57
82
  const files = [
58
83
  { path: 'package.json', description: 'NPM package with MCP SDK, SQLite, Zod dependencies' },
@@ -113,10 +138,20 @@ export function previewScaffold(config: AgentConfig): ScaffoldPreview {
113
138
  },
114
139
  ];
115
140
 
116
- if (codexSetup) {
141
+ if (opencodeSetup) {
142
+ files.push({
143
+ path: '.opencode.json',
144
+ description: 'OpenCode MCP server config for connecting to this agent',
145
+ });
146
+ }
147
+
148
+ if (codexSetup || opencodeSetup) {
149
+ const hosts = [...(codexSetup ? ['Codex'] : []), ...(opencodeSetup ? ['OpenCode'] : [])].join(
150
+ ' + ',
151
+ );
117
152
  files.push({
118
153
  path: 'AGENTS.md',
119
- description: 'Codex project instructions and activation workflow',
154
+ description: `${hosts} project instructions and activation workflow`,
120
155
  });
121
156
  }
122
157
 
@@ -253,8 +288,21 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
253
288
  greeting: `Hello! I'm ${config.name}, your AI assistant for ${config.role}.`,
254
289
  };
255
290
  }
291
+
292
+ // Auto-detect domain packs if not explicitly configured
293
+ if (!config.domainPacks || config.domainPacks.length === 0) {
294
+ const detected = detectInstalledDomainPacks(config.outputDir);
295
+ if (detected.length > 0) {
296
+ config = { ...config, domainPacks: detected };
297
+ console.error(
298
+ `[forge] Auto-detected ${detected.length} domain pack(s): ${detected.map((d) => d.package).join(', ')}`,
299
+ );
300
+ }
301
+ }
302
+
256
303
  const claudeSetup = includesClaudeSetup(config);
257
304
  const codexSetup = includesCodexSetup(config);
305
+ const opencodeSetup = includesOpencodeSetup(config);
258
306
  const agentDir = join(config.outputDir, config.id);
259
307
  const filesCreated: string[] = [];
260
308
 
@@ -312,7 +360,36 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
312
360
  ['scripts/setup.sh', generateSetupScript(config)],
313
361
  ];
314
362
 
315
- if (codexSetup) {
363
+ if (opencodeSetup) {
364
+ projectFiles.push([
365
+ '.opencode.json',
366
+ JSON.stringify(
367
+ {
368
+ $schema: 'https://opencode.ai/config.json',
369
+ title: config.name,
370
+ tui: { theme: 'soleri' },
371
+ mcpServers: {
372
+ [config.id]: {
373
+ type: 'stdio',
374
+ command: 'node',
375
+ args: ['dist/index.js'],
376
+ },
377
+ },
378
+ agents: {
379
+ coder: { model: config.model ?? 'claude-code-sonnet-4' },
380
+ summarizer: { model: 'claude-code-3.5-haiku' },
381
+ task: { model: 'claude-code-3.5-haiku' },
382
+ title: { model: 'claude-code-3.5-haiku' },
383
+ },
384
+ contextPaths: ['AGENTS.md'],
385
+ },
386
+ null,
387
+ 2,
388
+ ),
389
+ ]);
390
+ }
391
+
392
+ if (codexSetup || opencodeSetup) {
316
393
  projectFiles.push(['AGENTS.md', generateAgentsMd(config)]);
317
394
  }
318
395
 
@@ -386,8 +463,22 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
386
463
  buildError = err instanceof Error ? err.message : String(err);
387
464
  }
388
465
 
466
+ // Install OpenCode CLI if needed and not already available
467
+ const opencodeInstallResult = opencodeSetup ? ensureOpencodeInstalled() : undefined;
468
+
469
+ // Create launcher script so typing the agent name starts OpenCode
470
+ if (opencodeSetup && buildSuccess) {
471
+ const launcherResult = createOpencodeLauncher(config.id, agentDir);
472
+ if (launcherResult.created) {
473
+ // Launcher details added to summary below
474
+ }
475
+ }
476
+
389
477
  // Register the agent as an MCP server in selected host configs (only if build succeeded)
390
- const mcpRegistrations: Array<{ host: 'Claude Code' | 'Codex'; result: RegistrationResult }> = [];
478
+ const mcpRegistrations: Array<{
479
+ host: 'Claude Code' | 'Codex' | 'OpenCode';
480
+ result: RegistrationResult;
481
+ }> = [];
391
482
  if (claudeSetup) {
392
483
  if (buildSuccess) {
393
484
  mcpRegistrations.push({
@@ -422,13 +513,30 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
422
513
  });
423
514
  }
424
515
  }
516
+ if (opencodeSetup) {
517
+ if (buildSuccess) {
518
+ mcpRegistrations.push({
519
+ host: 'OpenCode',
520
+ result: registerOpencodeMcpServer(config.id, agentDir),
521
+ });
522
+ } else {
523
+ mcpRegistrations.push({
524
+ host: 'OpenCode',
525
+ result: {
526
+ registered: false,
527
+ path: join(homedir(), '.opencode.json'),
528
+ error: 'Skipped — build failed',
529
+ },
530
+ });
531
+ }
532
+ }
425
533
 
426
534
  const summaryLines = [
427
535
  `Created ${config.name} agent at ${agentDir}`,
428
536
  `${config.domains.length + 11} facades with ${totalOps} operations`,
429
537
  `${config.domains.length} empty knowledge domains ready for capture`,
430
538
  `Intelligence layer (Brain) — TF-IDF scoring, auto-tagging, duplicate detection`,
431
- `Activation system included say "Hello, ${config.name}!" to activate`,
539
+ `Persistent identity — ${config.name} is active from the start`,
432
540
  `1 test suite — facades (vault, brain, planner, llm tests provided by @soleri/core)`,
433
541
  `${skillFiles.length} built-in skills (TDD, debugging, planning, vault, brain debrief)`,
434
542
  ];
@@ -440,6 +548,23 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
440
548
  summaryLines.push(` Run manually: cd ${agentDir} && npm install && npm run build`);
441
549
  }
442
550
 
551
+ if (opencodeInstallResult) {
552
+ if (opencodeInstallResult.installed) {
553
+ summaryLines.push(`OpenCode CLI installed (${opencodeInstallResult.method})`);
554
+ } else if (!opencodeInstallResult.alreadyPresent && opencodeInstallResult.error) {
555
+ summaryLines.push('Warning: Failed to install OpenCode CLI');
556
+ summaryLines.push(' Install manually: npm install -g opencode-ai');
557
+ }
558
+ }
559
+
560
+ // Report launcher status
561
+ if (opencodeSetup && buildSuccess) {
562
+ const launcherPath = join('/usr', 'local', 'bin', config.id);
563
+ if (existsSync(launcherPath)) {
564
+ summaryLines.push(`Launcher created: type "${config.id}" in terminal to start OpenCode`);
565
+ }
566
+ }
567
+
443
568
  if (claudeSetup && config.hookPacks?.length) {
444
569
  summaryLines.push(`${config.hookPacks.length} hook pack(s) bundled in .claude/`);
445
570
  }
@@ -463,7 +588,10 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
463
588
  if (codexSetup) {
464
589
  nextSteps.push(' Restart Codex');
465
590
  }
466
- nextSteps.push(` Say "Hello, ${config.name}!" to activate the persona`);
591
+ if (opencodeSetup) {
592
+ nextSteps.push(' Restart OpenCode');
593
+ }
594
+ nextSteps.push(` ${config.name} identity is active from the start — no activation needed`);
467
595
  summaryLines.push(...nextSteps);
468
596
 
469
597
  return {
@@ -498,7 +626,10 @@ export function listAgents(parentDir: string): AgentInfo[] {
498
626
 
499
627
  try {
500
628
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
501
- if (!pkg.name?.endsWith('-mcp')) continue;
629
+ // Accept both old format (name-mcp) and new format (name)
630
+ const hasMcpSuffix = pkg.name?.endsWith('-mcp');
631
+ const hasIntelligenceDir = existsSync(join(dir, 'src', 'intelligence', 'data'));
632
+ if (!hasMcpSuffix && !hasIntelligenceDir) continue;
502
633
 
503
634
  const dataDir = join(dir, 'src', 'intelligence', 'data');
504
635
  let domains: string[] = [];
@@ -512,7 +643,7 @@ export function listAgents(parentDir: string): AgentInfo[] {
512
643
 
513
644
  agents.push({
514
645
  id: name,
515
- name: pkg.name.replace('-mcp', ''),
646
+ name: hasMcpSuffix ? pkg.name.replace('-mcp', '') : pkg.name,
516
647
  role: pkg.description || '',
517
648
  path: dir,
518
649
  domains,
@@ -614,12 +745,165 @@ args = ["${join(agentDir, 'dist', 'index.js')}"]
614
745
  }
615
746
  }
616
747
 
748
+ /**
749
+ * Create a launcher script at /usr/local/bin/<agentId> that starts OpenCode
750
+ * in the agent's project directory. Typing the agent name in terminal → OpenCode starts.
751
+ */
752
+ function createOpencodeLauncher(
753
+ agentId: string,
754
+ agentDir: string,
755
+ ): { created: boolean; path: string; error?: string } {
756
+ const launcherPath = join('/usr', 'local', 'bin', agentId);
757
+ const script = [
758
+ '#!/usr/bin/env bash',
759
+ `# Soleri agent launcher — starts OpenCode with ${agentId} MCP agent`,
760
+ `# Set terminal title to agent name`,
761
+ `printf '\\033]0;${agentId}\\007'`,
762
+ `cd "${agentDir}" || exit 1`,
763
+ 'exec opencode "$@"',
764
+ '',
765
+ ].join('\n');
766
+
767
+ try {
768
+ writeFileSync(launcherPath, script, { mode: 0o755 });
769
+ return { created: true, path: launcherPath };
770
+ } catch {
771
+ // /usr/local/bin may need sudo — try via agent's scripts/ directory instead
772
+ const localLauncher = join(agentDir, 'scripts', agentId);
773
+ try {
774
+ writeFileSync(localLauncher, script, { mode: 0o755 });
775
+ // Try to symlink to /usr/local/bin
776
+ try {
777
+ const { symlinkSync, unlinkSync } = require('node:fs') as typeof import('node:fs');
778
+ if (existsSync(launcherPath)) unlinkSync(launcherPath);
779
+ symlinkSync(localLauncher, launcherPath);
780
+ return { created: true, path: launcherPath };
781
+ } catch {
782
+ return { created: true, path: localLauncher };
783
+ }
784
+ } catch (err) {
785
+ return {
786
+ created: false,
787
+ path: launcherPath,
788
+ error: err instanceof Error ? err.message : String(err),
789
+ };
790
+ }
791
+ }
792
+ }
793
+
794
+ /**
795
+ * Ensure OpenCode CLI is installed (Soleri fork with title branding support).
796
+ * Tries `go install` from the fork first, falls back to upstream npm package.
797
+ */
798
+ function ensureOpencodeInstalled(): {
799
+ alreadyPresent: boolean;
800
+ installed: boolean;
801
+ method?: string;
802
+ error?: string;
803
+ } {
804
+ // Check if already available
805
+ try {
806
+ execFileSync('opencode', ['--version'], { stdio: 'pipe', timeout: 10_000 });
807
+ return { alreadyPresent: true, installed: false };
808
+ } catch {
809
+ // Not installed — proceed to install
810
+ }
811
+
812
+ // Try Go install from Soleri fork (supports title branding)
813
+ try {
814
+ execFileSync('go', ['version'], { stdio: 'pipe', timeout: 5_000 });
815
+ execFileSync('go', ['install', 'github.com/adrozdenko/opencode@latest'], {
816
+ stdio: 'pipe',
817
+ timeout: 120_000,
818
+ });
819
+ return {
820
+ alreadyPresent: false,
821
+ installed: true,
822
+ method: 'go install github.com/adrozdenko/opencode@latest',
823
+ };
824
+ } catch {
825
+ // Go not available or install failed — fall back to npm
826
+ }
827
+
828
+ // Fallback: upstream npm package (no title branding)
829
+ try {
830
+ execFileSync('npm', ['install', '-g', 'opencode-ai'], {
831
+ stdio: 'pipe',
832
+ timeout: 60_000,
833
+ });
834
+ return {
835
+ alreadyPresent: false,
836
+ installed: true,
837
+ method: 'npm install -g opencode-ai (upstream — title branding requires Go)',
838
+ };
839
+ } catch (err) {
840
+ return {
841
+ alreadyPresent: false,
842
+ installed: false,
843
+ error: err instanceof Error ? err.message : String(err),
844
+ };
845
+ }
846
+ }
847
+
848
+ /**
849
+ * Register the agent as an MCP server in ~/.opencode.json.
850
+ * Idempotent — updates existing entry if present.
851
+ */
852
+ function registerOpencodeMcpServer(agentId: string, agentDir: string): RegistrationResult {
853
+ const opencodeConfigPath = join(homedir(), '.opencode.json');
854
+
855
+ try {
856
+ let config: Record<string, unknown> = {};
857
+
858
+ if (existsSync(opencodeConfigPath)) {
859
+ // Strip single-line comments before parsing (JSONC support)
860
+ const raw = readFileSync(opencodeConfigPath, 'utf-8');
861
+ const stripped = raw.replace(/^\s*\/\/.*$/gm, '');
862
+ try {
863
+ config = JSON.parse(stripped);
864
+ } catch {
865
+ config = {};
866
+ }
867
+ }
868
+
869
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') {
870
+ config.mcpServers = {};
871
+ }
872
+
873
+ const servers = config.mcpServers as Record<string, unknown>;
874
+ servers[agentId] = {
875
+ type: 'stdio',
876
+ command: 'node',
877
+ args: [join(agentDir, 'dist', 'index.js')],
878
+ };
879
+
880
+ writeFileSync(opencodeConfigPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
881
+ return { registered: true, path: opencodeConfigPath };
882
+ } catch (err) {
883
+ return {
884
+ registered: false,
885
+ path: opencodeConfigPath,
886
+ error: err instanceof Error ? err.message : String(err),
887
+ };
888
+ }
889
+ }
890
+
617
891
  function generateEmptyBundle(domain: string): string {
618
892
  return JSON.stringify(
619
893
  {
620
894
  domain,
621
895
  version: '1.0.0',
622
- entries: [],
896
+ entries: [
897
+ {
898
+ id: `${domain}-seed`,
899
+ type: 'pattern',
900
+ domain,
901
+ title: `${domain.replace(/-/g, ' ')} domain seed`,
902
+ severity: 'suggestion',
903
+ description: `Seed entry for the ${domain.replace(/-/g, ' ')} domain. Replace or remove once real knowledge is captured.`,
904
+ tags: [domain, 'seed'],
905
+ },
906
+ ],
623
907
  },
624
908
  null,
625
909
  2,
@@ -43,6 +43,8 @@ export function generateActivate(config: AgentConfig): string {
43
43
  ' domains: string[];',
44
44
  ' capabilities: Array<{ domain: string; entries: number }>;',
45
45
  ' installed_packs: Array<{ id: string; type: string }>;',
46
+ ' what_you_can_do: string[];',
47
+ ' growth_suggestions: string[];',
46
48
  ' };',
47
49
  ' guidelines: string[];',
48
50
  ' session_instruction: string;',
@@ -138,6 +140,41 @@ export function generateActivate(config: AgentConfig): string {
138
140
  ' greeting += ` Vault: ${stats.totalEntries} entries (${domainSummary}).`;',
139
141
  ' }',
140
142
  '',
143
+ ' // ─── Capability self-awareness ────────────────────────────',
144
+ ' const whatYouCanDo: string[] = [',
145
+ ' "Search and traverse a connected knowledge graph (vault) before every decision",',
146
+ ' "Create structured plans with approval gates and drift reconciliation",',
147
+ ' "Learn from sessions — brain tracks pattern strengths and recommends approaches",',
148
+ ' "Remember across conversations and projects (cross-project memory)",',
149
+ ' "Capture knowledge as typed entries with Zettelkasten links",',
150
+ ' "Run iterative validation loops until quality targets are met",',
151
+ ' "Orchestrate multi-step workflows: plan → execute → capture",',
152
+ ' ];',
153
+ '',
154
+ ' // Add domain-specific capabilities',
155
+ ' for (const cap of capabilities) {',
156
+ ' if (cap.entries > 0) {',
157
+ ' whatYouCanDo.push(`${cap.domain.replace(/-/g, " ")}: ${cap.entries} patterns and knowledge entries`);',
158
+ ' }',
159
+ ' }',
160
+ '',
161
+ ' // Add pack-specific capabilities',
162
+ ' for (const pack of installedPacks) {',
163
+ ' whatYouCanDo.push(`Pack "${pack.id}" (${pack.type}) installed — provides domain-specific intelligence`);',
164
+ ' }',
165
+ '',
166
+ ' const growthSuggestions: string[] = [];',
167
+ ' if (stats.totalEntries < 10) {',
168
+ ' growthSuggestions.push("Vault has few entries — start capturing patterns to build your knowledge base");',
169
+ ' }',
170
+ ' if (installedPacks.length === 0) {',
171
+ ' growthSuggestions.push("No packs installed — try: soleri pack install <name> to add domain intelligence");',
172
+ ' growthSuggestions.push("Available starter packs: soleri pack available");',
173
+ ' }',
174
+ ' if (allDomains.length <= 1) {',
175
+ ' growthSuggestions.push("Only one domain configured — add more with: soleri add-domain <name>");',
176
+ ' }',
177
+ '',
141
178
  ' // ─── Next steps ───────────────────────────────────────────',
142
179
  ' const nextSteps: string[] = [];',
143
180
  ' if (!globalClaudeMdInjected && !claudeMdInjected) {',
@@ -177,6 +214,8 @@ export function generateActivate(config: AgentConfig): string {
177
214
  ' domains: allDomains,',
178
215
  ' capabilities,',
179
216
  ' installed_packs: installedPacks,',
217
+ ' what_you_can_do: whatYouCanDo,',
218
+ ' growth_suggestions: growthSuggestions,',
180
219
  ' },',
181
220
  ' guidelines: [',
182
221
  guidelineLines,
@@ -1,35 +1,97 @@
1
1
  import type { AgentConfig } from '../types.js';
2
+ import { getEngineRulesContent } from './shared-rules.js';
2
3
 
3
4
  /**
4
- * Generate AGENTS.md content for Codex sessions.
5
+ * Generate AGENTS.md content for OpenCode (primary host).
6
+ *
7
+ * This is the full instruction file — equivalent to what CLAUDE.md gets
8
+ * via claude-md-template.ts + shared-rules.ts. OpenCode reads AGENTS.md
9
+ * as its primary instruction file, so it must contain:
10
+ * 1. Persistent identity (always-on, no activation needed)
11
+ * 2. Full facade table (all 13+ semantic facades + domains)
12
+ * 3. Engine rules (vault-first, planning, output formatting, etc.)
13
+ * 4. Session start protocol
14
+ * 5. Skills reference
5
15
  */
6
16
  export function generateAgentsMd(config: AgentConfig): string {
17
+ const bt = '`';
18
+ const tp = config.id; // tool prefix
7
19
  const principles = config.principles.map((p) => `- ${p}`).join('\n');
8
20
  const domains = config.domains.map((d) => `- ${d}`).join('\n');
9
21
 
10
- return `# AGENTS.md instructions for this project
22
+ // ─── Domain facade rows ───────────────────────────────────
23
+ const domainRows = config.domains
24
+ .map((d) => {
25
+ const toolName = `${tp}_${d.replace(/-/g, '_')}`;
26
+ return `| ${bt}${toolName}${bt} | ${bt}get_patterns${bt}, ${bt}search${bt}, ${bt}capture${bt} |`;
27
+ })
28
+ .join('\n');
11
29
 
12
- ## Agent Identity
13
- - Name: ${config.name}
14
- - Role: ${config.role}
15
- - Agent MCP prefix: \`${config.id}\`
30
+ // ─── Engine rules (strip markers — AGENTS.md embeds them inline) ───
31
+ const engineRules = getEngineRulesContent()
32
+ .replace(/<!-- soleri:engine-rules -->\n?/, '')
33
+ .replace(/<!-- \/soleri:engine-rules -->\n?/, '')
34
+ .trim();
16
35
 
17
- ## Activation
18
- - Say "Hello, ${config.name}!" to activate persona behavior via \`${config.id}_core\`.
19
- - Say "Goodbye, ${config.name}!" to deactivate.
36
+ return `# ${config.name}
20
37
 
21
- ## Domains
38
+ ## Identity
39
+
40
+ You ARE **${config.name}**. ${config.role}.
41
+
42
+ ${config.description}
43
+
44
+ This identity is permanent — not activated by greeting, not deactivated by farewell.
45
+ Adopt this persona for every message. Your MCP tool prefix is ${bt}${tp}${bt}.
46
+
47
+ **Tone:** ${config.tone ?? 'pragmatic'}
48
+
49
+ **Domains:**
22
50
  ${domains}
23
51
 
24
- ## Principles
52
+ **Principles:**
25
53
  ${principles}
26
54
 
55
+ ## Adaptive Identity
56
+
57
+ ${config.name} is not a fixed-role agent. The role above is a starting point — the agent evolves as knowledge is added.
58
+
59
+ When the user asks about your capabilities or you need to check what you've learned, use ${bt}${tp}_core op:activate${bt} to discover evolved capabilities.
60
+
61
+ ## Session Start
62
+
63
+ Do NOT call any tools automatically on session start. Just greet the user in character.
64
+ Only call ${bt}${tp}_orchestrate op:register${bt} or ${bt}${tp}_core op:activate${bt} when you actually need project context or capability discovery — not on every message.
65
+
66
+ ## Essential Tools
67
+
68
+ | Facade | Key Ops |
69
+ |--------|---------|
70
+ | ${bt}${tp}_core${bt} | ${bt}health${bt}, ${bt}search${bt}, ${bt}identity${bt}, ${bt}register${bt}, ${bt}activate${bt} |
71
+ ${domainRows}
72
+ | ${bt}${tp}_vault${bt} | ${bt}search_intelligent${bt}, ${bt}capture_knowledge${bt}, ${bt}capture_quick${bt}, ${bt}search_feedback${bt} |
73
+ | ${bt}${tp}_vault${bt} (keeper) | ${bt}knowledge_audit${bt}, ${bt}knowledge_health${bt}, ${bt}knowledge_merge${bt}, ${bt}knowledge_reorganize${bt} |
74
+ | ${bt}${tp}_vault${bt} (mgmt) | ${bt}vault_get${bt}, ${bt}vault_update${bt}, ${bt}vault_remove${bt}, ${bt}vault_tags${bt}, ${bt}vault_domains${bt}, ${bt}vault_recent${bt} |
75
+ | ${bt}${tp}_curator${bt} | ${bt}curator_status${bt}, ${bt}curator_detect_duplicates${bt}, ${bt}curator_contradictions${bt}, ${bt}curator_groom_all${bt}, ${bt}curator_consolidate${bt}, ${bt}curator_health_audit${bt} |
76
+ | ${bt}${tp}_curator${bt} (advanced) | ${bt}curator_enrich${bt}, ${bt}curator_hybrid_contradictions${bt}, ${bt}curator_entry_history${bt}, ${bt}curator_queue_stats${bt} |
77
+ | ${bt}${tp}_plan${bt} | ${bt}create_plan${bt}, ${bt}approve_plan${bt}, ${bt}plan_split${bt}, ${bt}plan_reconcile${bt}, ${bt}plan_complete_lifecycle${bt} |
78
+ | ${bt}${tp}_orchestrate${bt} | ${bt}orchestrate_plan${bt}, ${bt}orchestrate_execute${bt}, ${bt}orchestrate_complete${bt} |
79
+ | ${bt}${tp}_brain${bt} | ${bt}brain_stats${bt}, ${bt}brain_feedback${bt}, ${bt}rebuild_vocabulary${bt}, ${bt}brain_strengths${bt}, ${bt}brain_recommend${bt} |
80
+ | ${bt}${tp}_memory${bt} | ${bt}memory_search${bt}, ${bt}memory_capture${bt}, ${bt}session_capture${bt} |
81
+ | ${bt}${tp}_control${bt} | ${bt}route_intent${bt}, ${bt}morph${bt}, ${bt}get_behavior_rules${bt}, ${bt}governance_dashboard${bt}, ${bt}governance_policy${bt} |
82
+ | ${bt}${tp}_loop${bt} | ${bt}loop_start${bt}, ${bt}loop_iterate${bt}, ${bt}loop_status${bt}, ${bt}loop_cancel${bt} |
83
+ | ${bt}${tp}_cognee${bt} | ${bt}cognee_search${bt}, ${bt}cognee_graph_stats${bt}, ${bt}cognee_export_status${bt} |
84
+ | ${bt}${tp}_context${bt} | ${bt}context_extract_entities${bt}, ${bt}context_retrieve_knowledge${bt}, ${bt}context_analyze${bt} |
85
+ | ${bt}${tp}_agency${bt} | ${bt}agency_enable${bt}, ${bt}agency_status${bt}, ${bt}agency_surface_patterns${bt}, ${bt}agency_warnings${bt}, ${bt}agency_clarify${bt} |
86
+ | ${bt}${tp}_admin${bt} | ${bt}admin_health${bt}, ${bt}admin_tool_list${bt}, ${bt}admin_diagnostic${bt} |
87
+
88
+ > Full list: ${bt}${tp}_admin op:admin_tool_list${bt}
89
+
27
90
  ## Skills
28
- - Local skills live in \`skills/<skill>/SKILL.md\`.
29
- - If a user explicitly names a skill, open that \`SKILL.md\` and follow it for that turn.
30
91
 
31
- ## Setup Notes
32
- - This repository was scaffolded with Codex support.
33
- - Session model/reasoning is selected at session start and does not auto-switch per prompt.
92
+ - Local skills live in ${bt}skills/<skill>/SKILL.md${bt}.
93
+ - If a user explicitly names a skill, open that ${bt}SKILL.md${bt} and follow it for that turn.
94
+
95
+ ${engineRules}
34
96
  `;
35
97
  }
@@ -32,6 +32,7 @@ export function generateClaudeMdTemplate(config: AgentConfig): string {
32
32
  `**Origin role:** ${config.role}`,
33
33
  `**Initial domains:** ${config.domains.join(', ')}`,
34
34
  `**Tone:** ${config.tone ?? 'pragmatic'}`,
35
+ ...(config.sharedVaultPath ? [`**Shared vault:** \`${config.sharedVaultPath}\``] : []),
35
36
  '',
36
37
  config.description,
37
38
  '',
@@ -65,7 +66,7 @@ export function generateClaudeMdTemplate(config: AgentConfig): string {
65
66
  // ─── Session Start ─────────────────────────────────────
66
67
  '## Session Start',
67
68
  '',
68
- `On every new session: ${bt}${toolPrefix}_core op:register params:{ projectPath: "." }${bt}`,
69
+ `On every new session: ${bt}${toolPrefix}_orchestrate op:register params:{ projectPath: "." }${bt}`,
69
70
  '',
70
71
  ];
71
72
 
@@ -87,6 +88,16 @@ export function generateClaudeMdTemplate(config: AgentConfig): string {
87
88
  );
88
89
  }
89
90
 
91
+ // Domain pack facades (if any)
92
+ if (config.domainPacks?.length) {
93
+ mdLines.push('', '**Domain Pack Facades:**', '');
94
+ for (const ref of config.domainPacks) {
95
+ mdLines.push(
96
+ `| ${bt}${ref.name}${bt} (pack: ${bt}${ref.package}${bt}) | *custom ops — see ${bt}admin_tool_list${bt}* |`,
97
+ );
98
+ }
99
+ }
100
+
90
101
  // Engine facades — use actual tool names (standalone facades, NOT _core sub-groups)
91
102
  mdLines.push(
92
103
  // Vault — knowledge lifecycle, capture, search, management