@soleri/forge 5.14.9 → 7.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 +349 -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.d.ts +5 -2
- package/dist/templates/activate.js +136 -35
- 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 +25 -4
- package/dist/templates/claude-md-template.js.map +1 -1
- package/dist/templates/entry-point.js +84 -7
- 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 +75 -10
- package/dist/types.js +40 -2
- 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 +243 -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 +409 -0
- package/src/scaffolder.ts +299 -15
- package/src/templates/activate.ts +137 -39
- package/src/templates/agents-md.ts +78 -16
- package/src/templates/claude-md-template.ts +29 -4
- package/src/templates/entry-point.ts +91 -7
- 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 +45 -2
- package/src/utils/detect-domain-packs.ts +129 -0
- package/tsconfig.json +0 -1
- package/vitest.config.ts +1 -2
|
@@ -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
|
}
|
|
@@ -29,9 +29,10 @@ export function generateClaudeMdTemplate(config: AgentConfig): string {
|
|
|
29
29
|
// ─── Identity ──────────────────────────────────────────
|
|
30
30
|
`## ${config.name}`,
|
|
31
31
|
'',
|
|
32
|
-
`**
|
|
33
|
-
`**
|
|
32
|
+
`**Origin role:** ${config.role}`,
|
|
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
|
'',
|
|
@@ -39,19 +40,33 @@ export function generateClaudeMdTemplate(config: AgentConfig): string {
|
|
|
39
40
|
...config.principles.map((p) => `- ${p}`),
|
|
40
41
|
'',
|
|
41
42
|
|
|
43
|
+
// ─── Adaptive Identity ──────────────────────────────────
|
|
44
|
+
'## Adaptive Identity',
|
|
45
|
+
'',
|
|
46
|
+
`${config.name} is not a fixed-role agent. The origin role above is a starting point — the agent evolves as knowledge is added.`,
|
|
47
|
+
'',
|
|
48
|
+
'**On activation**, the agent discovers its current capabilities by checking:',
|
|
49
|
+
'- Vault domains (what knowledge actually exists)',
|
|
50
|
+
'- Installed packs (what was added after creation)',
|
|
51
|
+
'- Identity updates (any role changes via `op:update_identity`)',
|
|
52
|
+
'',
|
|
53
|
+
'**Use the `current` field** from the activation response — not `origin` — to determine how to present yourself.',
|
|
54
|
+
'If you have grown beyond your origin role, say so. Your greeting and expertise should reflect what you actually know.',
|
|
55
|
+
'',
|
|
56
|
+
|
|
42
57
|
// ─── Activation ────────────────────────────────────────
|
|
43
58
|
'## Activation',
|
|
44
59
|
'',
|
|
45
60
|
`**Activate:** "Hello, ${config.name}!" → ${bt}${toolPrefix}_core op:activate params:{ projectPath: "." }${bt}`,
|
|
46
61
|
`**Deactivate:** "Goodbye, ${config.name}!" → ${bt}${toolPrefix}_core op:activate params:{ deactivate: true }${bt}`,
|
|
47
62
|
'',
|
|
48
|
-
'On activation,
|
|
63
|
+
'On activation, read the `current` field to discover your evolved role, then adopt that persona for the session.',
|
|
49
64
|
'',
|
|
50
65
|
|
|
51
66
|
// ─── Session Start ─────────────────────────────────────
|
|
52
67
|
'## Session Start',
|
|
53
68
|
'',
|
|
54
|
-
`On every new session: ${bt}${toolPrefix}
|
|
69
|
+
`On every new session: ${bt}${toolPrefix}_orchestrate op:register params:{ projectPath: "." }${bt}`,
|
|
55
70
|
'',
|
|
56
71
|
];
|
|
57
72
|
|
|
@@ -73,6 +88,16 @@ export function generateClaudeMdTemplate(config: AgentConfig): string {
|
|
|
73
88
|
);
|
|
74
89
|
}
|
|
75
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
|
+
|
|
76
101
|
// Engine facades — use actual tool names (standalone facades, NOT _core sub-groups)
|
|
77
102
|
mdLines.push(
|
|
78
103
|
// Vault — knowledge lifecycle, capture, search, management
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import type { AgentConfig } from '../types.js';
|
|
2
2
|
|
|
3
|
+
/** Generate vault connection code for linked vaults. */
|
|
4
|
+
function generateVaultConnections(config: AgentConfig): string {
|
|
5
|
+
if (!config.vaults?.length) return '';
|
|
6
|
+
const lines = ['', ' // ─── Linked vaults ──────────────────────────────────────────────'];
|
|
7
|
+
for (const v of config.vaults) {
|
|
8
|
+
lines.push(
|
|
9
|
+
` try { runtime.vaultManager.connect('${v.name}', '${v.path}', ${v.priority ?? 0.5}); } catch { /* already connected or unavailable */ }`,
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
lines.push(
|
|
13
|
+
` console.error(\`[\${tag}] Connected ${config.vaults.length} linked vault(s): ${config.vaults.map((v) => v.name).join(', ')}\`);`,
|
|
14
|
+
'',
|
|
15
|
+
);
|
|
16
|
+
return lines.join('\n');
|
|
17
|
+
}
|
|
18
|
+
|
|
3
19
|
/**
|
|
4
20
|
* Generate the main index.ts entry point for the agent.
|
|
5
21
|
*
|
|
@@ -9,6 +25,7 @@ import type { AgentConfig } from '../types.js';
|
|
|
9
25
|
*/
|
|
10
26
|
export function generateEntryPoint(config: AgentConfig): string {
|
|
11
27
|
const domainsLiteral = JSON.stringify(config.domains);
|
|
28
|
+
const hasDomainPacks = config.domainPacks && config.domainPacks.length > 0;
|
|
12
29
|
|
|
13
30
|
return `#!/usr/bin/env node
|
|
14
31
|
|
|
@@ -24,12 +41,14 @@ import {
|
|
|
24
41
|
registerAllFacades,
|
|
25
42
|
seedDefaultPlaybooks,
|
|
26
43
|
wrapWithMiddleware,
|
|
44
|
+
CapabilityRegistry,
|
|
45
|
+
loadAllFlows,
|
|
27
46
|
} from '@soleri/core';
|
|
28
|
-
import type { OpDefinition, AgentExtensions } from '@soleri/core'
|
|
47
|
+
import type { OpDefinition, AgentExtensions } from '@soleri/core';${hasDomainPacks ? `\nimport { loadDomainPacksFromConfig, createPackRuntime } from '@soleri/core';` : ''}
|
|
29
48
|
import { z } from 'zod';
|
|
30
49
|
import { PERSONA, getPersonaPrompt } from './identity/persona.js';
|
|
31
50
|
import { activateAgent, deactivateAgent } from './activation/activate.js';
|
|
32
|
-
import { injectClaudeMd, injectClaudeMdGlobal, hasAgentMarker } from './activation/inject-claude-md.js';
|
|
51
|
+
import { injectClaudeMd, injectClaudeMdGlobal, hasAgentMarker, injectAgentsMd, injectAgentsMdGlobal, hasAgentMarkerInAgentsMd } from './activation/inject-claude-md.js';
|
|
33
52
|
|
|
34
53
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
35
54
|
|
|
@@ -37,11 +56,11 @@ async function main(): Promise<void> {
|
|
|
37
56
|
// ─── Runtime — vault, brain, planner, curator, LLM, key pools ───
|
|
38
57
|
const runtime = createAgentRuntime({
|
|
39
58
|
agentId: '${config.id}',
|
|
40
|
-
dataDir: join(__dirname, 'intelligence', 'data')
|
|
59
|
+
dataDir: join(__dirname, 'intelligence', 'data'),${config.sharedVaultPath ? `\n sharedVaultPath: '${config.sharedVaultPath}',` : ''}${config.cognee ? `\n cognee: true,` : ''}
|
|
41
60
|
});
|
|
42
61
|
|
|
43
62
|
const tag = PERSONA.name.toLowerCase();
|
|
44
|
-
|
|
63
|
+
${generateVaultConnections(config)}
|
|
45
64
|
// Seed built-in playbooks (idempotent)
|
|
46
65
|
const seedResult = seedDefaultPlaybooks(runtime.vault);
|
|
47
66
|
if (seedResult.seeded > 0) {
|
|
@@ -102,7 +121,7 @@ async function main(): Promise<void> {
|
|
|
102
121
|
changeReason: 'Initial identity seeded from PERSONA',
|
|
103
122
|
});
|
|
104
123
|
}
|
|
105
|
-
return activateAgent(runtime
|
|
124
|
+
return activateAgent(runtime, (params.projectPath as string) ?? '.');
|
|
106
125
|
},
|
|
107
126
|
},
|
|
108
127
|
{
|
|
@@ -120,9 +139,24 @@ async function main(): Promise<void> {
|
|
|
120
139
|
return injectClaudeMd((params.projectPath as string) ?? '.');
|
|
121
140
|
},
|
|
122
141
|
},
|
|
142
|
+
{
|
|
143
|
+
name: 'inject_agents_md',
|
|
144
|
+
description: 'Inject agent sections into AGENTS.md — project-level or global (~/.config/opencode/AGENTS.md). For OpenCode and Codex. Idempotent.',
|
|
145
|
+
auth: 'write',
|
|
146
|
+
schema: z.object({
|
|
147
|
+
projectPath: z.string().optional().default('.'),
|
|
148
|
+
global: z.boolean().optional().describe('If true, inject into ~/.config/opencode/AGENTS.md instead of project-level'),
|
|
149
|
+
}),
|
|
150
|
+
handler: async (params) => {
|
|
151
|
+
if (params.global) {
|
|
152
|
+
return injectAgentsMdGlobal();
|
|
153
|
+
}
|
|
154
|
+
return injectAgentsMd((params.projectPath as string) ?? '.');
|
|
155
|
+
},
|
|
156
|
+
},
|
|
123
157
|
{
|
|
124
158
|
name: 'setup',
|
|
125
|
-
description: 'Check setup status — CLAUDE.md configured? Vault has entries? What to do next?',
|
|
159
|
+
description: 'Check setup status — CLAUDE.md configured? AGENTS.md configured? Vault has entries? What to do next?',
|
|
126
160
|
auth: 'read',
|
|
127
161
|
schema: z.object({
|
|
128
162
|
projectPath: z.string().optional().default('.'),
|
|
@@ -135,12 +169,19 @@ async function main(): Promise<void> {
|
|
|
135
169
|
|
|
136
170
|
const projectClaudeMd = joinPath(projectPath, 'CLAUDE.md');
|
|
137
171
|
const globalClaudeMd = joinPath(homedir(), '.claude', 'CLAUDE.md');
|
|
172
|
+
const projectAgentsMd = joinPath(projectPath, 'AGENTS.md');
|
|
173
|
+
const globalAgentsMd = joinPath(homedir(), '.config', 'opencode', 'AGENTS.md');
|
|
138
174
|
|
|
139
175
|
const projectExists = existsSync(projectClaudeMd);
|
|
140
176
|
const projectHasAgent = hasAgentMarker(projectClaudeMd);
|
|
141
177
|
const globalExists = existsSync(globalClaudeMd);
|
|
142
178
|
const globalHasAgent = hasAgentMarker(globalClaudeMd);
|
|
143
179
|
|
|
180
|
+
const agentsMdProjectExists = existsSync(projectAgentsMd);
|
|
181
|
+
const agentsMdProjectHasAgent = hasAgentMarkerInAgentsMd(projectAgentsMd);
|
|
182
|
+
const agentsMdGlobalExists = existsSync(globalAgentsMd);
|
|
183
|
+
const agentsMdGlobalHasAgent = hasAgentMarkerInAgentsMd(globalAgentsMd);
|
|
184
|
+
|
|
144
185
|
const s = runtime.vault.stats();
|
|
145
186
|
|
|
146
187
|
const recommendations: string[] = [];
|
|
@@ -149,6 +190,9 @@ async function main(): Promise<void> {
|
|
|
149
190
|
} else if (!globalHasAgent) {
|
|
150
191
|
recommendations.push('Global ~/.claude/CLAUDE.md not configured — run inject_claude_md with global: true to enable in all projects');
|
|
151
192
|
}
|
|
193
|
+
if (!agentsMdGlobalHasAgent && !agentsMdProjectHasAgent) {
|
|
194
|
+
recommendations.push('No AGENTS.md configured — run inject_agents_md for OpenCode/Codex support');
|
|
195
|
+
}
|
|
152
196
|
if (s.totalEntries === 0) {
|
|
153
197
|
recommendations.push('Vault is empty — add intelligence data or capture knowledge via domain facades');
|
|
154
198
|
}
|
|
@@ -193,6 +237,10 @@ async function main(): Promise<void> {
|
|
|
193
237
|
project: { exists: projectExists, has_agent_section: projectHasAgent },
|
|
194
238
|
global: { exists: globalExists, has_agent_section: globalHasAgent },
|
|
195
239
|
},
|
|
240
|
+
agents_md: {
|
|
241
|
+
project: { exists: agentsMdProjectExists, has_agent_section: agentsMdProjectHasAgent },
|
|
242
|
+
global: { exists: agentsMdGlobalExists, has_agent_section: agentsMdGlobalHasAgent },
|
|
243
|
+
},
|
|
196
244
|
vault: { entries: s.totalEntries, domains: Object.keys(s.byDomain) },
|
|
197
245
|
hooks: hookStatus,
|
|
198
246
|
recommendations,
|
|
@@ -209,7 +257,43 @@ async function main(): Promise<void> {
|
|
|
209
257
|
ops: agentOps,
|
|
210
258
|
};
|
|
211
259
|
|
|
212
|
-
|
|
260
|
+
${
|
|
261
|
+
hasDomainPacks
|
|
262
|
+
? ` // ─── Domain packs ─────────────────────────────────────────────
|
|
263
|
+
const domainPacks = await loadDomainPacksFromConfig(${JSON.stringify(config.domainPacks)});
|
|
264
|
+
console.error(\`[\${tag}] Loaded \${domainPacks.length} domain packs\`);
|
|
265
|
+
for (const pack of domainPacks) {
|
|
266
|
+
if (pack.onActivate) await pack.onActivate(runtime);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ─── Capability Registry ─────────────────────────────────────
|
|
270
|
+
const capabilityRegistry = new CapabilityRegistry();
|
|
271
|
+
const packRuntime = createPackRuntime(runtime);
|
|
272
|
+
|
|
273
|
+
// Register domain pack capabilities
|
|
274
|
+
for (const pack of domainPacks) {
|
|
275
|
+
if (pack.capabilities) {
|
|
276
|
+
const handlers = pack.capabilities(packRuntime);
|
|
277
|
+
// Use pack manifest capabilities if available, otherwise derive from handler keys
|
|
278
|
+
const definitions = (pack as Record<string, unknown>).manifest
|
|
279
|
+
? ((pack as Record<string, unknown>).manifest as { capabilities?: Array<{ id: string; description: string; provides: string[]; requires: string[] }> }).capabilities ?? []
|
|
280
|
+
: [...handlers.keys()].map(id => ({ id, description: id, provides: [], requires: [] }));
|
|
281
|
+
capabilityRegistry.registerPack(pack.name, definitions, handlers, 50);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Validate flows against installed capabilities
|
|
286
|
+
const flows = loadAllFlows();
|
|
287
|
+
for (const flow of flows) {
|
|
288
|
+
const validation = capabilityRegistry.validateFlow(flow);
|
|
289
|
+
if (validation.missing.length > 0) {
|
|
290
|
+
console.error(\`[\${tag}] Flow \${flow.id}: \${validation.missing.length} capabilities degraded (\${validation.missing.join(', ')})\`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
console.error(\`[\${tag}] Capability registry: \${capabilityRegistry.size} capabilities from \${capabilityRegistry.packCount} pack(s)\`);
|
|
294
|
+
`
|
|
295
|
+
: ''
|
|
296
|
+
} const domainFacades = createDomainFacades(runtime, '${config.id}', ${domainsLiteral}${hasDomainPacks ? `, domainPacks` : ''});
|
|
213
297
|
|
|
214
298
|
// ─── User extensions (auto-discovered from src/extensions/) ────
|
|
215
299
|
let extensions: AgentExtensions = {};
|
|
@@ -44,6 +44,10 @@ export function generateInjectClaudeMd(config: AgentConfig): string {
|
|
|
44
44
|
' if (existing.includes(startMarker)) {',
|
|
45
45
|
' const startIdx = existing.indexOf(startMarker);',
|
|
46
46
|
' const endIdx = existing.indexOf(endMarker);',
|
|
47
|
+
' if (endIdx !== -1) {',
|
|
48
|
+
' const currentBlock = existing.slice(startIdx, endIdx + endMarker.length);',
|
|
49
|
+
" if (currentBlock === content.trim()) return 'skipped';",
|
|
50
|
+
' }',
|
|
47
51
|
' if (endIdx === -1) {',
|
|
48
52
|
" const updated = existing.slice(0, startIdx) + content + '\\n' + existing.slice(startIdx + startMarker.length);",
|
|
49
53
|
" writeFileSync(filePath, updated, 'utf-8');",
|
|
@@ -166,5 +170,54 @@ export function generateInjectClaudeMd(config: AgentConfig): string {
|
|
|
166
170
|
" const content = readFileSync(filePath, 'utf-8');",
|
|
167
171
|
" return content.includes('<!-- ' + getEngineRulesMarker() + ' -->');",
|
|
168
172
|
'}',
|
|
173
|
+
'',
|
|
174
|
+
'// ─── AGENTS.md support (OpenCode, Codex) ──────────────────────',
|
|
175
|
+
'',
|
|
176
|
+
'/**',
|
|
177
|
+
' * Inject agent sections into a project AGENTS.md.',
|
|
178
|
+
' * Same engine rules + agent block as CLAUDE.md, targeting AGENTS.md instead.',
|
|
179
|
+
' * OpenCode reads AGENTS.md as its primary instruction file.',
|
|
180
|
+
' */',
|
|
181
|
+
'export function injectAgentsMd(projectPath: string): InjectResult {',
|
|
182
|
+
" return injectIntoFile(join(projectPath, 'AGENTS.md'));",
|
|
183
|
+
'}',
|
|
184
|
+
'',
|
|
185
|
+
'/**',
|
|
186
|
+
' * Inject into the global ~/.config/opencode/AGENTS.md.',
|
|
187
|
+
" * Creates ~/.config/opencode/ directory if it doesn't exist.",
|
|
188
|
+
' * This makes the activation phrase work in any OpenCode project.',
|
|
189
|
+
' */',
|
|
190
|
+
'export function injectAgentsMdGlobal(): InjectResult {',
|
|
191
|
+
" const opencodeDir = join(homedir(), '.config', 'opencode');",
|
|
192
|
+
' if (!existsSync(opencodeDir)) {',
|
|
193
|
+
' mkdirSync(opencodeDir, { recursive: true });',
|
|
194
|
+
' }',
|
|
195
|
+
" return injectIntoFile(join(opencodeDir, 'AGENTS.md'));",
|
|
196
|
+
'}',
|
|
197
|
+
'',
|
|
198
|
+
'/**',
|
|
199
|
+
' * Remove agent section from a project AGENTS.md.',
|
|
200
|
+
' */',
|
|
201
|
+
'export function removeAgentsMd(projectPath: string): { removed: boolean; path: string } {',
|
|
202
|
+
" const filePath = join(projectPath, 'AGENTS.md');",
|
|
203
|
+
' return { removed: removeBlock(filePath, getClaudeMdMarker()), path: filePath };',
|
|
204
|
+
'}',
|
|
205
|
+
'',
|
|
206
|
+
'/**',
|
|
207
|
+
' * Remove agent section from the global ~/.config/opencode/AGENTS.md.',
|
|
208
|
+
' */',
|
|
209
|
+
'export function removeAgentsMdGlobal(): { removed: boolean; path: string } {',
|
|
210
|
+
" const filePath = join(homedir(), '.config', 'opencode', 'AGENTS.md');",
|
|
211
|
+
' return { removed: removeBlock(filePath, getClaudeMdMarker()), path: filePath };',
|
|
212
|
+
'}',
|
|
213
|
+
'',
|
|
214
|
+
'/**',
|
|
215
|
+
' * Check if the agent marker exists in an AGENTS.md file.',
|
|
216
|
+
' */',
|
|
217
|
+
'export function hasAgentMarkerInAgentsMd(filePath: string): boolean {',
|
|
218
|
+
' if (!existsSync(filePath)) return false;',
|
|
219
|
+
" const content = readFileSync(filePath, 'utf-8');",
|
|
220
|
+
` return content.includes('<!-- ${marker} -->');`,
|
|
221
|
+
'}',
|
|
169
222
|
].join('\n');
|
|
170
223
|
}
|
|
@@ -2,7 +2,7 @@ import type { AgentConfig } from '../types.js';
|
|
|
2
2
|
|
|
3
3
|
export function generatePackageJson(config: AgentConfig): string {
|
|
4
4
|
const pkg = {
|
|
5
|
-
name:
|
|
5
|
+
name: config.id,
|
|
6
6
|
version: '1.0.0',
|
|
7
7
|
description: config.description,
|
|
8
8
|
type: 'module',
|
|
@@ -31,6 +31,9 @@ export function generatePackageJson(config: AgentConfig): string {
|
|
|
31
31
|
'@soleri/core': '^2.0.0',
|
|
32
32
|
zod: '^3.24.2',
|
|
33
33
|
...(config.telegram ? { grammy: '^1.35.0' } : {}),
|
|
34
|
+
...(config.domainPacks?.length
|
|
35
|
+
? Object.fromEntries(config.domainPacks.map((pack) => [pack.package, pack.version ?? '*']))
|
|
36
|
+
: {}),
|
|
34
37
|
},
|
|
35
38
|
optionalDependencies: {
|
|
36
39
|
'@anthropic-ai/sdk': '^0.39.0',
|
package/src/templates/readme.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { AgentConfig } from '../types.js';
|
|
|
4
4
|
* Generate a README.md for the scaffolded agent.
|
|
5
5
|
*/
|
|
6
6
|
export function generateReadme(config: AgentConfig): string {
|
|
7
|
+
const skillCount = config.skills?.length ?? 17;
|
|
7
8
|
const setupTarget = config.setupTarget ?? 'claude';
|
|
8
9
|
const claudeSetup = setupTarget === 'claude' || setupTarget === 'both';
|
|
9
10
|
const codexSetup = setupTarget === 'codex' || setupTarget === 'both';
|
|
@@ -78,10 +79,10 @@ export function generateReadme(config: AgentConfig): string {
|
|
|
78
79
|
|
|
79
80
|
const skillsLead =
|
|
80
81
|
claudeSetup && codexSetup
|
|
81
|
-
? `${config.name} ships with
|
|
82
|
+
? `${config.name} ships with ${skillCount} structured workflow skills. In Claude Code they are invocable via \`/<skill-name>\`; in Codex they are available via generated AGENTS.md + local skill files.`
|
|
82
83
|
: claudeSetup
|
|
83
|
-
? `${config.name} ships with
|
|
84
|
-
: `${config.name} ships with
|
|
84
|
+
? `${config.name} ships with ${skillCount} structured workflow skills, invocable via \`/<skill-name>\` in Claude Code:`
|
|
85
|
+
: `${config.name} ships with ${skillCount} structured workflow skills, available via generated AGENTS.md + local skill files in Codex:`;
|
|
85
86
|
|
|
86
87
|
const skillsInstallNote =
|
|
87
88
|
claudeSetup && codexSetup
|
|
@@ -6,10 +6,15 @@ import type { AgentConfig } from '../types.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export function generateSetupScript(config: AgentConfig): string {
|
|
8
8
|
const setupTarget = config.setupTarget ?? 'claude';
|
|
9
|
-
const claudeSetup = setupTarget === 'claude' || setupTarget === 'both';
|
|
10
|
-
const codexSetup = setupTarget === 'codex' || setupTarget === 'both';
|
|
11
|
-
const
|
|
12
|
-
|
|
9
|
+
const claudeSetup = setupTarget === 'claude' || setupTarget === 'both' || setupTarget === 'all';
|
|
10
|
+
const codexSetup = setupTarget === 'codex' || setupTarget === 'both' || setupTarget === 'all';
|
|
11
|
+
const opencodeSetup = setupTarget === 'opencode' || setupTarget === 'all';
|
|
12
|
+
const hostParts = [
|
|
13
|
+
...(claudeSetup ? ['Claude Code'] : []),
|
|
14
|
+
...(codexSetup ? ['Codex'] : []),
|
|
15
|
+
...(opencodeSetup ? ['OpenCode'] : []),
|
|
16
|
+
];
|
|
17
|
+
const hostLabel = hostParts.join(' + ');
|
|
13
18
|
|
|
14
19
|
const claudeSection = claudeSetup
|
|
15
20
|
? `
|
|
@@ -195,6 +200,103 @@ if [ -d "$SKILLS_DIR" ]; then
|
|
|
195
200
|
done
|
|
196
201
|
echo "[ok] Codex skills: $skill_installed installed, $skill_skipped already present"
|
|
197
202
|
fi
|
|
203
|
+
`
|
|
204
|
+
: '';
|
|
205
|
+
|
|
206
|
+
const opencodeSection = opencodeSetup
|
|
207
|
+
? `
|
|
208
|
+
# Check and install OpenCode (Soleri fork with title branding)
|
|
209
|
+
if ! command -v opencode &>/dev/null; then
|
|
210
|
+
echo ""
|
|
211
|
+
INSTALLED=false
|
|
212
|
+
# Try Go install from Soleri fork (supports title branding)
|
|
213
|
+
if command -v go &>/dev/null; then
|
|
214
|
+
echo "Installing OpenCode (Soleri fork) via go install..."
|
|
215
|
+
if go install github.com/adrozdenko/opencode@latest 2>/dev/null; then
|
|
216
|
+
if command -v opencode &>/dev/null; then
|
|
217
|
+
echo "[ok] OpenCode installed from Soleri fork ($(opencode --version 2>/dev/null || echo 'installed'))"
|
|
218
|
+
INSTALLED=true
|
|
219
|
+
fi
|
|
220
|
+
fi
|
|
221
|
+
fi
|
|
222
|
+
# Fallback: upstream npm package (no title branding)
|
|
223
|
+
if [ "$INSTALLED" = false ]; then
|
|
224
|
+
echo "Installing OpenCode via npm (upstream — title branding requires Go)..."
|
|
225
|
+
npm install -g opencode-ai
|
|
226
|
+
if command -v opencode &>/dev/null; then
|
|
227
|
+
echo "[ok] OpenCode installed ($(opencode --version 2>/dev/null || echo 'unknown version'))"
|
|
228
|
+
else
|
|
229
|
+
echo ""
|
|
230
|
+
echo "Warning: Could not install OpenCode automatically."
|
|
231
|
+
echo "Install manually using one of:"
|
|
232
|
+
echo " go install github.com/adrozdenko/opencode@latest (recommended — includes title branding)"
|
|
233
|
+
echo " npm install -g opencode-ai (upstream)"
|
|
234
|
+
echo ""
|
|
235
|
+
fi
|
|
236
|
+
fi
|
|
237
|
+
else
|
|
238
|
+
echo "[ok] OpenCode found ($(opencode --version 2>/dev/null || echo 'installed'))"
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# Register MCP server with OpenCode
|
|
242
|
+
echo ""
|
|
243
|
+
echo "Registering ${config.name} with OpenCode..."
|
|
244
|
+
OPENCODE_CONFIG="$HOME/.opencode.json"
|
|
245
|
+
AGENT_DIST="$AGENT_DIR/dist/index.js"
|
|
246
|
+
|
|
247
|
+
OPENCODE_CONFIG="$OPENCODE_CONFIG" AGENT_NAME="$AGENT_NAME" AGENT_DIST="$AGENT_DIST" node <<'NODE'
|
|
248
|
+
const fs = require('node:fs');
|
|
249
|
+
const path = process.env.OPENCODE_CONFIG;
|
|
250
|
+
const agentName = process.env.AGENT_NAME;
|
|
251
|
+
const distPath = process.env.AGENT_DIST;
|
|
252
|
+
|
|
253
|
+
let config = {};
|
|
254
|
+
if (fs.existsSync(path)) {
|
|
255
|
+
try {
|
|
256
|
+
const raw = fs.readFileSync(path, 'utf-8');
|
|
257
|
+
const stripped = raw.replace(/^\\s*\\/\\/.*$/gm, '');
|
|
258
|
+
config = JSON.parse(stripped);
|
|
259
|
+
} catch {
|
|
260
|
+
config = {};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!config.mcp || typeof config.mcp !== 'object') {
|
|
265
|
+
config.mcp = {};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
config.mcp[agentName] = {
|
|
269
|
+
type: 'local',
|
|
270
|
+
command: ['node', distPath],
|
|
271
|
+
enabled: true,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
fs.writeFileSync(path, JSON.stringify(config, null, 2) + '\\n', 'utf-8');
|
|
275
|
+
NODE
|
|
276
|
+
echo "[ok] Registered ${config.name} as MCP server (OpenCode)"
|
|
277
|
+
|
|
278
|
+
# Create launcher script — type "${config.id}" to start OpenCode
|
|
279
|
+
LAUNCHER_PATH="/usr/local/bin/$AGENT_NAME"
|
|
280
|
+
LAUNCHER_CONTENT="#!/usr/bin/env bash
|
|
281
|
+
# Soleri agent launcher — starts OpenCode with $AGENT_NAME MCP agent
|
|
282
|
+
cd \\"$AGENT_DIR\\" || exit 1
|
|
283
|
+
exec opencode \\"\\$@\\""
|
|
284
|
+
|
|
285
|
+
if [ -w "/usr/local/bin" ]; then
|
|
286
|
+
echo "$LAUNCHER_CONTENT" > "$LAUNCHER_PATH"
|
|
287
|
+
chmod +x "$LAUNCHER_PATH"
|
|
288
|
+
echo "[ok] Launcher created: type \\"${config.id}\\" to start OpenCode"
|
|
289
|
+
else
|
|
290
|
+
echo "$LAUNCHER_CONTENT" > "$AGENT_DIR/scripts/$AGENT_NAME"
|
|
291
|
+
chmod +x "$AGENT_DIR/scripts/$AGENT_NAME"
|
|
292
|
+
if command -v sudo &>/dev/null; then
|
|
293
|
+
sudo ln -sf "$AGENT_DIR/scripts/$AGENT_NAME" "$LAUNCHER_PATH" 2>/dev/null && \\
|
|
294
|
+
echo "[ok] Launcher created: type \\"${config.id}\\" to start OpenCode" || \\
|
|
295
|
+
echo "Note: Run 'sudo ln -sf $AGENT_DIR/scripts/$AGENT_NAME $LAUNCHER_PATH' to enable \\"${config.id}\\" command"
|
|
296
|
+
else
|
|
297
|
+
echo "Note: Add $AGENT_DIR/scripts to PATH, or symlink $AGENT_DIR/scripts/$AGENT_NAME to /usr/local/bin/$AGENT_NAME"
|
|
298
|
+
fi
|
|
299
|
+
fi
|
|
198
300
|
`
|
|
199
301
|
: '';
|
|
200
302
|
|
|
@@ -207,6 +309,9 @@ fi
|
|
|
207
309
|
? ['echo " - Start a new Claude Code session (or restart if one is open)"']
|
|
208
310
|
: []),
|
|
209
311
|
...(codexSetup ? ['echo " - Start a new Codex session (or restart if one is open)"'] : []),
|
|
312
|
+
...(opencodeSetup
|
|
313
|
+
? ['echo " - Start a new OpenCode session (or restart if one is open)"']
|
|
314
|
+
: []),
|
|
210
315
|
`echo " - Say: \\"Hello, ${config.name}!\\""`,
|
|
211
316
|
'echo ""',
|
|
212
317
|
`echo "${config.name} is ready."`,
|
|
@@ -248,6 +353,7 @@ fi
|
|
|
248
353
|
${claudeSection}
|
|
249
354
|
${hookPackSection}
|
|
250
355
|
${codexSection}
|
|
356
|
+
${opencodeSection}
|
|
251
357
|
${nextSteps}
|
|
252
358
|
`;
|
|
253
359
|
}
|