@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.
- package/dist/agent-schema.d.ts +323 -0
- package/dist/agent-schema.js +151 -0
- package/dist/agent-schema.js.map +1 -0
- package/dist/compose-claude-md.d.ts +24 -0
- package/dist/compose-claude-md.js +197 -0
- package/dist/compose-claude-md.js.map +1 -0
- package/dist/index.js +0 -0
- package/dist/lib.d.ts +12 -1
- package/dist/lib.js +10 -1
- package/dist/lib.js.map +1 -1
- package/dist/scaffold-filetree.d.ts +22 -0
- package/dist/scaffold-filetree.js +361 -0
- package/dist/scaffold-filetree.js.map +1 -0
- package/dist/scaffolder.js +261 -11
- package/dist/scaffolder.js.map +1 -1
- package/dist/templates/activate.js +39 -1
- package/dist/templates/activate.js.map +1 -1
- package/dist/templates/agents-md.d.ts +10 -1
- package/dist/templates/agents-md.js +76 -16
- package/dist/templates/agents-md.js.map +1 -1
- package/dist/templates/claude-md-template.js +9 -1
- package/dist/templates/claude-md-template.js.map +1 -1
- package/dist/templates/entry-point.js +83 -6
- package/dist/templates/entry-point.js.map +1 -1
- package/dist/templates/inject-claude-md.js +53 -0
- package/dist/templates/inject-claude-md.js.map +1 -1
- package/dist/templates/package-json.js +4 -1
- package/dist/templates/package-json.js.map +1 -1
- package/dist/templates/readme.js +4 -3
- package/dist/templates/readme.js.map +1 -1
- package/dist/templates/setup-script.js +109 -3
- package/dist/templates/setup-script.js.map +1 -1
- package/dist/templates/shared-rules.js +54 -17
- package/dist/templates/shared-rules.js.map +1 -1
- package/dist/templates/test-facades.js +151 -6
- package/dist/templates/test-facades.js.map +1 -1
- package/dist/types.d.ts +71 -6
- package/dist/types.js +39 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/detect-domain-packs.d.ts +25 -0
- package/dist/utils/detect-domain-packs.js +104 -0
- package/dist/utils/detect-domain-packs.js.map +1 -0
- package/package.json +2 -1
- package/src/__tests__/detect-domain-packs.test.ts +178 -0
- package/src/__tests__/scaffold-filetree.test.ts +257 -0
- package/src/__tests__/scaffolder.test.ts +5 -3
- package/src/agent-schema.ts +184 -0
- package/src/compose-claude-md.ts +252 -0
- package/src/lib.ts +14 -1
- package/src/scaffold-filetree.ts +421 -0
- package/src/scaffolder.ts +299 -15
- package/src/templates/activate.ts +39 -0
- package/src/templates/agents-md.ts +78 -16
- package/src/templates/claude-md-template.ts +12 -1
- package/src/templates/entry-point.ts +90 -6
- package/src/templates/inject-claude-md.ts +53 -0
- package/src/templates/package-json.ts +4 -1
- package/src/templates/readme.ts +4 -3
- package/src/templates/setup-script.ts +110 -4
- package/src/templates/shared-rules.ts +55 -17
- package/src/templates/test-facades.ts +156 -6
- package/src/types.ts +44 -1
- package/src/utils/detect-domain-packs.ts +129 -0
- package/tsconfig.json +0 -1
- 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 {
|
|
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):
|
|
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
|
|
55
|
-
|
|
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 (
|
|
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:
|
|
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 (
|
|
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<{
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
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}
|
|
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
|