@soleri/forge 4.2.2 → 5.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/domain-manager.d.ts +2 -2
- package/dist/domain-manager.js +35 -16
- package/dist/domain-manager.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/knowledge-installer.js +18 -12
- package/dist/knowledge-installer.js.map +1 -1
- package/dist/patching.d.ts +15 -6
- package/dist/patching.js +39 -12
- package/dist/patching.js.map +1 -1
- package/dist/scaffolder.js +18 -28
- package/dist/scaffolder.js.map +1 -1
- package/dist/templates/brain.d.ts +6 -0
- package/dist/templates/brain.js +478 -0
- package/dist/templates/brain.js.map +1 -0
- package/dist/templates/core-facade.js +95 -47
- package/dist/templates/core-facade.js.map +1 -1
- package/dist/templates/entry-point.d.ts +4 -0
- package/dist/templates/entry-point.js +146 -89
- package/dist/templates/entry-point.js.map +1 -1
- package/dist/templates/facade-factory.d.ts +1 -0
- package/dist/templates/facade-factory.js +63 -0
- package/dist/templates/facade-factory.js.map +1 -0
- package/dist/templates/facade-types.d.ts +1 -0
- package/dist/templates/facade-types.js +46 -0
- package/dist/templates/facade-types.js.map +1 -0
- package/dist/templates/intelligence-loader.d.ts +1 -0
- package/dist/templates/intelligence-loader.js +43 -0
- package/dist/templates/intelligence-loader.js.map +1 -0
- package/dist/templates/intelligence-types.d.ts +1 -0
- package/dist/templates/intelligence-types.js +24 -0
- package/dist/templates/intelligence-types.js.map +1 -0
- package/dist/templates/llm-key-pool.d.ts +7 -0
- package/dist/templates/llm-key-pool.js +211 -0
- package/dist/templates/llm-key-pool.js.map +1 -0
- package/dist/templates/llm-types.d.ts +5 -0
- package/dist/templates/llm-types.js +161 -0
- package/dist/templates/llm-types.js.map +1 -0
- package/dist/templates/llm-utils.d.ts +5 -0
- package/dist/templates/llm-utils.js +260 -0
- package/dist/templates/llm-utils.js.map +1 -0
- package/dist/templates/package-json.js +3 -1
- package/dist/templates/package-json.js.map +1 -1
- package/dist/templates/planner.d.ts +5 -0
- package/dist/templates/planner.js +150 -0
- package/dist/templates/planner.js.map +1 -0
- package/dist/templates/test-brain.d.ts +6 -0
- package/dist/templates/test-brain.js +474 -0
- package/dist/templates/test-brain.js.map +1 -0
- package/dist/templates/test-facades.d.ts +1 -1
- package/dist/templates/test-facades.js +170 -456
- package/dist/templates/test-facades.js.map +1 -1
- package/dist/templates/test-llm.d.ts +7 -0
- package/dist/templates/test-llm.js +574 -0
- package/dist/templates/test-llm.js.map +1 -0
- package/dist/templates/test-loader.d.ts +5 -0
- package/dist/templates/test-loader.js +146 -0
- package/dist/templates/test-loader.js.map +1 -0
- package/dist/templates/test-planner.d.ts +5 -0
- package/dist/templates/test-planner.js +271 -0
- package/dist/templates/test-planner.js.map +1 -0
- package/dist/templates/test-vault.d.ts +5 -0
- package/dist/templates/test-vault.js +380 -0
- package/dist/templates/test-vault.js.map +1 -0
- package/dist/templates/vault.d.ts +5 -0
- package/dist/templates/vault.js +263 -0
- package/dist/templates/vault.js.map +1 -0
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/src/__tests__/scaffolder.test.ts +52 -109
- package/src/domain-manager.ts +34 -15
- package/src/knowledge-installer.ts +22 -15
- package/src/patching.ts +49 -11
- package/src/scaffolder.ts +18 -29
- package/src/templates/entry-point.ts +146 -91
- package/src/templates/package-json.ts +3 -1
- package/src/templates/test-facades.ts +170 -458
- package/src/templates/core-facade.ts +0 -517
- package/src/templates/llm-client.ts +0 -301
package/src/domain-manager.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Domain manager — add new knowledge domains to existing agents.
|
|
3
|
+
*
|
|
4
|
+
* v5.0: Domain facades come from @soleri/core at runtime (createDomainFacades).
|
|
5
|
+
* Adding a domain just needs: empty data bundle + patch index.ts domains array.
|
|
6
|
+
* Falls back to generating facade files for v4.x agents.
|
|
3
7
|
*/
|
|
4
8
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
5
9
|
import { join } from 'node:path';
|
|
@@ -15,14 +19,24 @@ interface AddDomainParams {
|
|
|
15
19
|
noBuild?: boolean;
|
|
16
20
|
}
|
|
17
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Detect if this is a v5.0+ agent (uses createAgentRuntime from @soleri/core).
|
|
24
|
+
*/
|
|
25
|
+
function isV5Agent(agentPath: string): boolean {
|
|
26
|
+
const indexPath = join(agentPath, 'src', 'index.ts');
|
|
27
|
+
if (!existsSync(indexPath)) return false;
|
|
28
|
+
const content = readFileSync(indexPath, 'utf-8');
|
|
29
|
+
return content.includes('createAgentRuntime') || content.includes('createDomainFacades');
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
/**
|
|
19
33
|
* Add a new knowledge domain to an existing agent.
|
|
20
34
|
*
|
|
21
35
|
* Steps:
|
|
22
36
|
* 1. Validate agent path and domain name
|
|
23
37
|
* 2. Create empty intelligence bundle
|
|
24
|
-
* 3. Generate domain facade
|
|
25
|
-
* 4. Patch src/index.ts with
|
|
38
|
+
* 3. Generate domain facade (v4.x only — v5.0+ uses core factory)
|
|
39
|
+
* 4. Patch src/index.ts with domain registration
|
|
26
40
|
* 5. Patch src/activation/claude-md-content.ts with facade table rows
|
|
27
41
|
* 6. Rebuild (unless noBuild)
|
|
28
42
|
*/
|
|
@@ -68,7 +82,8 @@ export async function addDomain(params: AddDomainParams): Promise<AddDomainResul
|
|
|
68
82
|
return fail(agentPath, domain, `Domain "${domain}" already exists`);
|
|
69
83
|
}
|
|
70
84
|
|
|
71
|
-
const
|
|
85
|
+
const v5 = isV5Agent(agentPath);
|
|
86
|
+
const hasBrain = v5 || existsSync(join(agentPath, 'src', 'brain'));
|
|
72
87
|
|
|
73
88
|
// ── Step 1: Create empty bundle ──
|
|
74
89
|
|
|
@@ -82,18 +97,22 @@ export async function addDomain(params: AddDomainParams): Promise<AddDomainResul
|
|
|
82
97
|
writeFileSync(join(distDataDir, `${domain}.json`), emptyBundle, 'utf-8');
|
|
83
98
|
}
|
|
84
99
|
|
|
85
|
-
// ── Step 2: Generate facade ──
|
|
100
|
+
// ── Step 2: Generate facade (v4.x only) ──
|
|
86
101
|
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
let facadeGenerated = false;
|
|
103
|
+
if (!v5) {
|
|
104
|
+
const facadesDir = join(agentPath, 'src', 'facades');
|
|
105
|
+
const facadePath = join(facadesDir, `${domain}.facade.ts`);
|
|
89
106
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
if (existsSync(facadePath)) {
|
|
108
|
+
warnings.push(`Facade ${domain}.facade.ts already exists — skipped`);
|
|
109
|
+
} else {
|
|
110
|
+
const facadeCode = hasBrain
|
|
111
|
+
? generateDomainFacade(agentId, domain)
|
|
112
|
+
: generateVaultOnlyDomainFacade(agentId, domain);
|
|
113
|
+
writeFileSync(facadePath, facadeCode, 'utf-8');
|
|
114
|
+
facadeGenerated = true;
|
|
115
|
+
}
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
// ── Step 3: Patch src/index.ts ──
|
|
@@ -147,10 +166,10 @@ export async function addDomain(params: AddDomainParams): Promise<AddDomainResul
|
|
|
147
166
|
agentPath,
|
|
148
167
|
domain,
|
|
149
168
|
agentId,
|
|
150
|
-
facadeGenerated
|
|
169
|
+
facadeGenerated,
|
|
151
170
|
buildOutput,
|
|
152
171
|
warnings,
|
|
153
|
-
summary: `Added domain "${domain}" to ${agentId}${warnings.length > 0 ? ` (${warnings.length} warning(s))` : ''}`,
|
|
172
|
+
summary: `Added domain "${domain}" to ${agentId}${v5 ? ' (v5.0 — no facade file needed)' : ''}${warnings.length > 0 ? ` (${warnings.length} warning(s))` : ''}`,
|
|
154
173
|
};
|
|
155
174
|
}
|
|
156
175
|
|
|
@@ -185,6 +185,10 @@ export async function installKnowledge(
|
|
|
185
185
|
|
|
186
186
|
const hasBrain = existsSync(join(agentPath, 'src', 'brain'));
|
|
187
187
|
|
|
188
|
+
// v5.0+ agents use createDomainFacades() from @soleri/core — no facade files needed
|
|
189
|
+
const indexPath5 = join(agentPath, 'src', 'index.ts');
|
|
190
|
+
const isV5 = existsSync(indexPath5) && readFileSync(indexPath5, 'utf-8').includes('createDomainFacades');
|
|
191
|
+
|
|
188
192
|
// ── Step 2: Read and validate bundles ────────────────────────────
|
|
189
193
|
|
|
190
194
|
const bundleFiles = collectBundleFiles(bundlePath);
|
|
@@ -253,22 +257,25 @@ export async function installKnowledge(
|
|
|
253
257
|
// ── Step 4: Generate facades for new domains ─────────────────────
|
|
254
258
|
|
|
255
259
|
if (generateFacades && domainsAdded.length > 0) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
260
|
+
// v5.0+ agents: no facade files needed (createDomainFacades from @soleri/core)
|
|
261
|
+
// v4.x agents: generate facade files
|
|
262
|
+
if (!isV5) {
|
|
263
|
+
const facadesDir = join(agentPath, 'src', 'facades');
|
|
264
|
+
|
|
265
|
+
for (const domain of domainsAdded) {
|
|
266
|
+
const facadePath = join(facadesDir, `${domain}.facade.ts`);
|
|
267
|
+
if (existsSync(facadePath)) {
|
|
268
|
+
warnings.push(`Facade ${domain}.facade.ts already exists — skipped`);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const facadeCode = hasBrain
|
|
273
|
+
? generateDomainFacade(agentId, domain)
|
|
274
|
+
: generateVaultOnlyDomainFacade(agentId, domain);
|
|
275
|
+
|
|
276
|
+
writeFileSync(facadePath, facadeCode, 'utf-8');
|
|
277
|
+
facadesGenerated.push(`${domain}.facade.ts`);
|
|
264
278
|
}
|
|
265
|
-
|
|
266
|
-
const facadeCode = hasBrain
|
|
267
|
-
? generateDomainFacade(agentId, domain)
|
|
268
|
-
: generateVaultOnlyDomainFacade(agentId, domain);
|
|
269
|
-
|
|
270
|
-
writeFileSync(facadePath, facadeCode, 'utf-8');
|
|
271
|
-
facadesGenerated.push(`${domain}.facade.ts`);
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
// ── Step 5: Patch src/index.ts ───────────────────────────────────
|
package/src/patching.ts
CHANGED
|
@@ -3,42 +3,74 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These functions modify an agent's src/index.ts and src/activation/claude-md-content.ts
|
|
5
5
|
* to register new domain facades and their CLAUDE.md table rows.
|
|
6
|
+
*
|
|
7
|
+
* v5.0: Entry point uses createDomainFacades() from @soleri/core.
|
|
8
|
+
* New domain patching inserts domain names into the domains array literal.
|
|
6
9
|
*/
|
|
7
|
-
import { pascalCase } from './templates/domain-facade.js';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
|
-
* Patch the agent's src/index.ts to add
|
|
11
|
-
*
|
|
12
|
+
* Patch the agent's src/index.ts to add new domains.
|
|
13
|
+
*
|
|
14
|
+
* v5.0 format: The entry point has a `createDomainFacades(runtime, 'agentId', [...domains])` call.
|
|
15
|
+
* We insert new domain strings into that array.
|
|
12
16
|
*
|
|
13
|
-
*
|
|
14
|
-
* - Import: insert before `import { createCoreFacade }`
|
|
15
|
-
* - Facade array: insert before `createCoreFacade(`
|
|
17
|
+
* Falls back to v4.x format (import + facade creation) for older agents.
|
|
16
18
|
*/
|
|
17
19
|
export function patchIndexTs(
|
|
20
|
+
source: string,
|
|
21
|
+
newDomains: string[],
|
|
22
|
+
_hasBrain?: boolean,
|
|
23
|
+
): string | null {
|
|
24
|
+
// ── v5.0 format: createDomainFacades() with domains array literal ──
|
|
25
|
+
const v5Pattern = /createDomainFacades\(runtime,\s*'[^']+',\s*\[([\s\S]*?)\]\)/;
|
|
26
|
+
const v5Match = source.match(v5Pattern);
|
|
27
|
+
|
|
28
|
+
if (v5Match) {
|
|
29
|
+
const existingArrayContent = v5Match[1];
|
|
30
|
+
const domainsToAdd = newDomains.filter(
|
|
31
|
+
(d) => !existingArrayContent.includes(`'${d}'`) && !existingArrayContent.includes(`"${d}"`),
|
|
32
|
+
);
|
|
33
|
+
if (domainsToAdd.length === 0) return source;
|
|
34
|
+
|
|
35
|
+
const newEntries = domainsToAdd.map((d) => `'${d}'`).join(', ');
|
|
36
|
+
const currentContent = existingArrayContent.trim();
|
|
37
|
+
const updatedContent = currentContent
|
|
38
|
+
? `${currentContent}, ${newEntries}`
|
|
39
|
+
: newEntries;
|
|
40
|
+
|
|
41
|
+
return source.replace(v5Pattern, (match) => {
|
|
42
|
+
return match.replace(v5Match[1], updatedContent);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── v4.x fallback: import + facade creation anchors ──
|
|
47
|
+
return patchIndexTsV4(source, newDomains, _hasBrain ?? true);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Legacy v4.x patching — import-based domain facade registration.
|
|
52
|
+
*/
|
|
53
|
+
function patchIndexTsV4(
|
|
18
54
|
source: string,
|
|
19
55
|
newDomains: string[],
|
|
20
56
|
hasBrain: boolean,
|
|
21
57
|
): string | null {
|
|
22
|
-
// Filter out domains whose imports already exist (idempotent)
|
|
23
58
|
const domainsToImport = newDomains.filter((d) => {
|
|
24
59
|
const fn = `create${pascalCase(d)}Facade`;
|
|
25
60
|
return !source.includes(`import { ${fn} }`);
|
|
26
61
|
});
|
|
27
62
|
|
|
28
|
-
// Filter out domains whose facade calls already exist
|
|
29
63
|
const domainsToRegister = newDomains.filter((d) => {
|
|
30
64
|
const fn = `create${pascalCase(d)}Facade(`;
|
|
31
65
|
return !source.includes(fn);
|
|
32
66
|
});
|
|
33
67
|
|
|
34
|
-
// Nothing to patch
|
|
35
68
|
if (domainsToImport.length === 0 && domainsToRegister.length === 0) {
|
|
36
69
|
return source;
|
|
37
70
|
}
|
|
38
71
|
|
|
39
72
|
let patched = source;
|
|
40
73
|
|
|
41
|
-
// ── Insert imports ──
|
|
42
74
|
if (domainsToImport.length > 0) {
|
|
43
75
|
const importAnchor = /^(import \{ createCoreFacade \}.*$)/m;
|
|
44
76
|
if (!importAnchor.test(patched)) return null;
|
|
@@ -53,7 +85,6 @@ export function patchIndexTs(
|
|
|
53
85
|
patched = patched.replace(importAnchor, `${newImports}\n$1`);
|
|
54
86
|
}
|
|
55
87
|
|
|
56
|
-
// ── Insert facade creations ──
|
|
57
88
|
if (domainsToRegister.length > 0) {
|
|
58
89
|
const facadeAnchor = /^(\s+createCoreFacade\()/m;
|
|
59
90
|
if (!facadeAnchor.test(patched)) return null;
|
|
@@ -121,3 +152,10 @@ export function patchClaudeMdContent(
|
|
|
121
152
|
|
|
122
153
|
return source.slice(0, anchorIdx) + newRows.join('\n') + '\n' + source.slice(anchorIdx);
|
|
123
154
|
}
|
|
155
|
+
|
|
156
|
+
function pascalCase(s: string): string {
|
|
157
|
+
return s
|
|
158
|
+
.split(/[-_\s]+/)
|
|
159
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
160
|
+
.join('');
|
|
161
|
+
}
|
package/src/scaffolder.ts
CHANGED
|
@@ -14,8 +14,6 @@ import { generatePackageJson } from './templates/package-json.js';
|
|
|
14
14
|
import { generateTsconfig } from './templates/tsconfig.js';
|
|
15
15
|
import { generateVitestConfig } from './templates/vitest-config.js';
|
|
16
16
|
import { generatePersona } from './templates/persona.js';
|
|
17
|
-
import { generateDomainFacade } from './templates/domain-facade.js';
|
|
18
|
-
import { generateCoreFacade } from './templates/core-facade.js';
|
|
19
17
|
import { generateEntryPoint } from './templates/entry-point.js';
|
|
20
18
|
import { generateFacadesTest } from './templates/test-facades.js';
|
|
21
19
|
import { generateClaudeMdTemplate } from './templates/claude-md-template.js';
|
|
@@ -23,7 +21,6 @@ import { generateInjectClaudeMd } from './templates/inject-claude-md.js';
|
|
|
23
21
|
import { generateActivate } from './templates/activate.js';
|
|
24
22
|
import { generateReadme } from './templates/readme.js';
|
|
25
23
|
import { generateSetupScript } from './templates/setup-script.js';
|
|
26
|
-
import { generateLLMClient } from './templates/llm-client.js';
|
|
27
24
|
|
|
28
25
|
/**
|
|
29
26
|
* Preview what scaffold will create without writing anything.
|
|
@@ -43,21 +40,8 @@ export function previewScaffold(config: AgentConfig): ScaffoldPreview {
|
|
|
43
40
|
{
|
|
44
41
|
path: 'src/index.ts',
|
|
45
42
|
description:
|
|
46
|
-
'Entry point —
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
path: 'src/llm/llm-client.ts',
|
|
50
|
-
description:
|
|
51
|
-
'LLM client — unified OpenAI/Anthropic caller with model routing (optional, needs API keys)',
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
path: 'src/facades/core.facade.ts',
|
|
55
|
-
description: 'Core facade — search, vault stats, health, identity',
|
|
43
|
+
'Entry point — thin shell using createAgentRuntime() + createCoreOps() from @soleri/core',
|
|
56
44
|
},
|
|
57
|
-
...config.domains.map((d) => ({
|
|
58
|
-
path: `src/facades/${d}.facade.ts`,
|
|
59
|
-
description: `${d} facade — search, get_patterns, capture, remove`,
|
|
60
|
-
})),
|
|
61
45
|
...config.domains.map((d) => ({
|
|
62
46
|
path: `src/intelligence/data/${d}.json`,
|
|
63
47
|
description: `Empty ${d} intelligence bundle — ready for knowledge capture`,
|
|
@@ -101,14 +85,10 @@ export function previewScaffold(config: AgentConfig): ScaffoldPreview {
|
|
|
101
85
|
{
|
|
102
86
|
name: `${config.id}_core`,
|
|
103
87
|
ops: [
|
|
88
|
+
// From createCoreOps() — 26 generic ops
|
|
104
89
|
'search',
|
|
105
90
|
'vault_stats',
|
|
106
91
|
'list_all',
|
|
107
|
-
'health',
|
|
108
|
-
'identity',
|
|
109
|
-
'activate',
|
|
110
|
-
'inject_claude_md',
|
|
111
|
-
'setup',
|
|
112
92
|
'register',
|
|
113
93
|
'memory_search',
|
|
114
94
|
'memory_capture',
|
|
@@ -124,6 +104,20 @@ export function previewScaffold(config: AgentConfig): ScaffoldPreview {
|
|
|
124
104
|
'rebuild_vocabulary',
|
|
125
105
|
'brain_stats',
|
|
126
106
|
'llm_status',
|
|
107
|
+
'curator_status',
|
|
108
|
+
'curator_detect_duplicates',
|
|
109
|
+
'curator_contradictions',
|
|
110
|
+
'curator_resolve_contradiction',
|
|
111
|
+
'curator_groom',
|
|
112
|
+
'curator_groom_all',
|
|
113
|
+
'curator_consolidate',
|
|
114
|
+
'curator_health_audit',
|
|
115
|
+
// Agent-specific ops — 5
|
|
116
|
+
'health',
|
|
117
|
+
'identity',
|
|
118
|
+
'activate',
|
|
119
|
+
'inject_claude_md',
|
|
120
|
+
'setup',
|
|
127
121
|
],
|
|
128
122
|
},
|
|
129
123
|
];
|
|
@@ -159,12 +153,10 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
|
|
|
159
153
|
'',
|
|
160
154
|
'scripts',
|
|
161
155
|
'src',
|
|
162
|
-
'src/facades',
|
|
163
156
|
'src/intelligence',
|
|
164
157
|
'src/intelligence/data',
|
|
165
158
|
'src/identity',
|
|
166
159
|
'src/activation',
|
|
167
|
-
'src/llm',
|
|
168
160
|
'src/__tests__',
|
|
169
161
|
];
|
|
170
162
|
|
|
@@ -201,19 +193,16 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
|
|
|
201
193
|
|
|
202
194
|
// Write source files
|
|
203
195
|
const sourceFiles: Array<[string, string]> = [
|
|
204
|
-
['src/facades/core.facade.ts', generateCoreFacade(config)],
|
|
205
196
|
['src/identity/persona.ts', generatePersona(config)],
|
|
206
197
|
['src/activation/claude-md-content.ts', generateClaudeMdTemplate(config)],
|
|
207
198
|
['src/activation/inject-claude-md.ts', generateInjectClaudeMd(config)],
|
|
208
199
|
['src/activation/activate.ts', generateActivate(config)],
|
|
209
200
|
['src/index.ts', generateEntryPoint(config)],
|
|
210
|
-
['src/llm/llm-client.ts', generateLLMClient(config)],
|
|
211
201
|
['src/__tests__/facades.test.ts', generateFacadesTest(config)],
|
|
212
202
|
];
|
|
213
203
|
|
|
214
|
-
//
|
|
204
|
+
// Empty intelligence data bundles (domain facades come from @soleri/core at runtime)
|
|
215
205
|
for (const domain of config.domains) {
|
|
216
|
-
sourceFiles.push([`src/facades/${domain}.facade.ts`, generateDomainFacade(config.id, domain)]);
|
|
217
206
|
sourceFiles.push([`src/intelligence/data/${domain}.json`, generateEmptyBundle(domain)]);
|
|
218
207
|
}
|
|
219
208
|
|
|
@@ -222,7 +211,7 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
|
|
|
222
211
|
filesCreated.push(path);
|
|
223
212
|
}
|
|
224
213
|
|
|
225
|
-
const totalOps = config.domains.length * 5 +
|
|
214
|
+
const totalOps = config.domains.length * 5 + 31; // 5 per domain + 26 core (from createCoreOps) + 5 agent-specific
|
|
226
215
|
|
|
227
216
|
// Register the agent as an MCP server in ~/.claude.json
|
|
228
217
|
const mcpReg = registerMcpServer(config.id, agentDir);
|
|
@@ -2,19 +2,13 @@ import type { AgentConfig } from '../types.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Generate the main index.ts entry point for the agent.
|
|
5
|
+
*
|
|
6
|
+
* v5.0: Thin shell — delegates to createAgentRuntime(), createCoreOps(),
|
|
7
|
+
* and createDomainFacades() from @soleri/core. Only agent-specific code
|
|
8
|
+
* (persona, activation) lives here.
|
|
5
9
|
*/
|
|
6
10
|
export function generateEntryPoint(config: AgentConfig): string {
|
|
7
|
-
const
|
|
8
|
-
.map((d) => {
|
|
9
|
-
const fn = `create${pascalCase(d)}Facade`;
|
|
10
|
-
const file = `${d}.facade.js`;
|
|
11
|
-
return `import { ${fn} } from './facades/${file}';`;
|
|
12
|
-
})
|
|
13
|
-
.join('\n');
|
|
14
|
-
|
|
15
|
-
const facadeCreations = config.domains
|
|
16
|
-
.map((d) => ` create${pascalCase(d)}Facade(vault, brain),`)
|
|
17
|
-
.join('\n');
|
|
11
|
+
const domainsLiteral = JSON.stringify(config.domains);
|
|
18
12
|
|
|
19
13
|
return `#!/usr/bin/env node
|
|
20
14
|
|
|
@@ -22,102 +16,170 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
22
16
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
23
17
|
import { dirname, join } from 'node:path';
|
|
24
18
|
import { fileURLToPath } from 'node:url';
|
|
25
|
-
import { homedir } from 'node:os';
|
|
26
19
|
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
import {
|
|
21
|
+
createAgentRuntime,
|
|
22
|
+
createCoreOps,
|
|
23
|
+
createDomainFacades,
|
|
24
|
+
registerAllFacades,
|
|
25
|
+
} from '@soleri/core';
|
|
26
|
+
import type { OpDefinition } from '@soleri/core';
|
|
27
|
+
import { z } from 'zod';
|
|
31
28
|
import { PERSONA, getPersonaPrompt } from './identity/persona.js';
|
|
29
|
+
import { activateAgent, deactivateAgent } from './activation/activate.js';
|
|
30
|
+
import { injectClaudeMd, injectClaudeMdGlobal, hasAgentMarker } from './activation/inject-claude-md.js';
|
|
32
31
|
|
|
33
32
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
34
33
|
|
|
35
34
|
async function main(): Promise<void> {
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Load and seed intelligence data
|
|
42
|
-
const dataDir = join(__dirname, 'intelligence', 'data');
|
|
43
|
-
const entries = loadIntelligenceData(dataDir);
|
|
44
|
-
if (entries.length > 0) {
|
|
45
|
-
const seeded = vault.seed(entries);
|
|
46
|
-
console.error(\`[\${PERSONA.name.toLowerCase()}] Seeded vault with \${seeded} intelligence entries\`);
|
|
47
|
-
} else {
|
|
48
|
-
console.error(\`[\${PERSONA.name.toLowerCase()}] Vault is empty — ready for knowledge capture\`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Initialize planner at ~/.${config.id}/plans.json
|
|
52
|
-
const plansPath = join(homedir(), '.${config.id}', 'plans.json');
|
|
53
|
-
const planner = new Planner(plansPath);
|
|
54
|
-
console.error(\`[\${PERSONA.name.toLowerCase()}] Planner: \${plansPath}\`);
|
|
55
|
-
|
|
56
|
-
// Initialize Cognee client (optional — vector search + knowledge graph)
|
|
57
|
-
// Auto-registers a service account on first use. Override with env vars.
|
|
58
|
-
const cognee = new CogneeClient({
|
|
59
|
-
baseUrl: process.env.COGNEE_URL ?? 'http://localhost:8000',
|
|
60
|
-
dataset: '${config.id}-vault',
|
|
61
|
-
...(process.env.COGNEE_API_TOKEN ? { apiToken: process.env.COGNEE_API_TOKEN } : {}),
|
|
62
|
-
...(process.env.COGNEE_EMAIL ? { serviceEmail: process.env.COGNEE_EMAIL } : {}),
|
|
63
|
-
...(process.env.COGNEE_PASSWORD ? { servicePassword: process.env.COGNEE_PASSWORD } : {}),
|
|
35
|
+
// ─── Runtime — vault, brain, planner, curator, LLM, key pools ───
|
|
36
|
+
const runtime = createAgentRuntime({
|
|
37
|
+
agentId: '${config.id}',
|
|
38
|
+
dataDir: join(__dirname, 'intelligence', 'data'),
|
|
64
39
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
40
|
+
|
|
41
|
+
const tag = PERSONA.name.toLowerCase();
|
|
42
|
+
const stats = runtime.vault.stats();
|
|
43
|
+
console.error(\`[\${tag}] Vault: \${stats.totalEntries} entries, Brain: \${runtime.brain.getVocabularySize()} terms\`);
|
|
44
|
+
|
|
45
|
+
const llmAvail = runtime.llmClient.isAvailable();
|
|
46
|
+
console.error(\`[\${tag}] LLM: OpenAI \${llmAvail.openai ? 'available' : 'not configured'}, Anthropic \${llmAvail.anthropic ? 'available' : 'not configured'}\`);
|
|
47
|
+
|
|
48
|
+
// ─── Agent-specific ops (reference persona + activation) ────────
|
|
49
|
+
const agentOps: OpDefinition[] = [
|
|
50
|
+
{
|
|
51
|
+
name: 'health',
|
|
52
|
+
description: 'Health check — vault status and agent info.',
|
|
53
|
+
auth: 'read',
|
|
54
|
+
handler: async () => {
|
|
55
|
+
const s = runtime.vault.stats();
|
|
56
|
+
return {
|
|
57
|
+
status: 'ok',
|
|
58
|
+
agent: { name: PERSONA.name, role: PERSONA.role },
|
|
59
|
+
vault: { entries: s.totalEntries, domains: Object.keys(s.byDomain) },
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'identity',
|
|
65
|
+
description: 'Get agent identity — name, role, principles.',
|
|
66
|
+
auth: 'read',
|
|
67
|
+
handler: async () => PERSONA,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'activate',
|
|
71
|
+
description: 'Activate agent persona — returns full context for Claude to adopt. Say "Hello, ${config.name}!" to trigger.',
|
|
72
|
+
auth: 'read',
|
|
73
|
+
schema: z.object({
|
|
74
|
+
projectPath: z.string().optional().default('.'),
|
|
75
|
+
deactivate: z.boolean().optional(),
|
|
76
|
+
}),
|
|
77
|
+
handler: async (params) => {
|
|
78
|
+
if (params.deactivate) {
|
|
79
|
+
return deactivateAgent();
|
|
80
|
+
}
|
|
81
|
+
return activateAgent(runtime.vault, (params.projectPath as string) ?? '.', runtime.planner);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'inject_claude_md',
|
|
86
|
+
description: 'Inject agent sections into CLAUDE.md — project-level or global (~/.claude/CLAUDE.md). Idempotent.',
|
|
87
|
+
auth: 'write',
|
|
88
|
+
schema: z.object({
|
|
89
|
+
projectPath: z.string().optional().default('.'),
|
|
90
|
+
global: z.boolean().optional().describe('If true, inject into ~/.claude/CLAUDE.md instead of project-level'),
|
|
91
|
+
}),
|
|
92
|
+
handler: async (params) => {
|
|
93
|
+
if (params.global) {
|
|
94
|
+
return injectClaudeMdGlobal();
|
|
95
|
+
}
|
|
96
|
+
return injectClaudeMd((params.projectPath as string) ?? '.');
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'setup',
|
|
101
|
+
description: 'Check setup status — CLAUDE.md configured? Vault has entries? What to do next?',
|
|
102
|
+
auth: 'read',
|
|
103
|
+
schema: z.object({
|
|
104
|
+
projectPath: z.string().optional().default('.'),
|
|
105
|
+
}),
|
|
106
|
+
handler: async (params) => {
|
|
107
|
+
const { existsSync } = await import('node:fs');
|
|
108
|
+
const { join: joinPath } = await import('node:path');
|
|
109
|
+
const { homedir } = await import('node:os');
|
|
110
|
+
const projectPath = (params.projectPath as string) ?? '.';
|
|
111
|
+
|
|
112
|
+
const projectClaudeMd = joinPath(projectPath, 'CLAUDE.md');
|
|
113
|
+
const globalClaudeMd = joinPath(homedir(), '.claude', 'CLAUDE.md');
|
|
114
|
+
|
|
115
|
+
const projectExists = existsSync(projectClaudeMd);
|
|
116
|
+
const projectHasAgent = hasAgentMarker(projectClaudeMd);
|
|
117
|
+
const globalExists = existsSync(globalClaudeMd);
|
|
118
|
+
const globalHasAgent = hasAgentMarker(globalClaudeMd);
|
|
119
|
+
|
|
120
|
+
const s = runtime.vault.stats();
|
|
121
|
+
|
|
122
|
+
const recommendations: string[] = [];
|
|
123
|
+
if (!globalHasAgent && !projectHasAgent) {
|
|
124
|
+
recommendations.push('No CLAUDE.md configured — run inject_claude_md with global: true for all projects, or without for this project');
|
|
125
|
+
} else if (!globalHasAgent) {
|
|
126
|
+
recommendations.push('Global ~/.claude/CLAUDE.md not configured — run inject_claude_md with global: true to enable in all projects');
|
|
127
|
+
}
|
|
128
|
+
if (s.totalEntries === 0) {
|
|
129
|
+
recommendations.push('Vault is empty — add intelligence data or capture knowledge via domain facades');
|
|
130
|
+
}
|
|
131
|
+
if (recommendations.length === 0) {
|
|
132
|
+
recommendations.push('${config.name} is fully set up and ready!');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
agent: { name: PERSONA.name, role: PERSONA.role },
|
|
137
|
+
claude_md: {
|
|
138
|
+
project: { exists: projectExists, has_agent_section: projectHasAgent },
|
|
139
|
+
global: { exists: globalExists, has_agent_section: globalHasAgent },
|
|
140
|
+
},
|
|
141
|
+
vault: { entries: s.totalEntries, domains: Object.keys(s.byDomain) },
|
|
142
|
+
recommendations,
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
// ─── Assemble facades ──────────────────────────────────────────
|
|
149
|
+
const coreOps = createCoreOps(runtime);
|
|
150
|
+
const coreFacade = {
|
|
151
|
+
name: '${config.id}_core',
|
|
152
|
+
description: 'Core operations — vault stats, cross-domain search, health check, identity, and activation system.',
|
|
153
|
+
ops: [...coreOps, ...agentOps],
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const domainFacades = createDomainFacades(runtime, '${config.id}', ${domainsLiteral});
|
|
157
|
+
|
|
158
|
+
// ─── MCP server ────────────────────────────────────────────────
|
|
88
159
|
const server = new McpServer({
|
|
89
160
|
name: '${config.id}-mcp',
|
|
90
161
|
version: '1.0.0',
|
|
91
162
|
});
|
|
92
163
|
|
|
93
|
-
// Register persona prompt
|
|
94
164
|
server.prompt('persona', 'Get agent persona and principles', async () => ({
|
|
95
165
|
messages: [{ role: 'assistant' as const, content: { type: 'text' as const, text: getPersonaPrompt() } }],
|
|
96
166
|
}));
|
|
97
167
|
|
|
98
|
-
|
|
99
|
-
const facades = [
|
|
100
|
-
${facadeCreations}
|
|
101
|
-
createCoreFacade(vault, planner, brain, cognee.isAvailable ? cognee : undefined, llmClient, openaiKeyPool, anthropicKeyPool),
|
|
102
|
-
];
|
|
103
|
-
|
|
168
|
+
const facades = [coreFacade, ...domainFacades];
|
|
104
169
|
registerAllFacades(server, facades);
|
|
105
170
|
|
|
106
|
-
|
|
107
|
-
console.error(\`[\${
|
|
108
|
-
console.error(\`[\${PERSONA.name.toLowerCase()}] Vault: \${stats.totalEntries} entries across \${Object.keys(stats.byDomain).length} domains\`);
|
|
109
|
-
console.error(\`[\${PERSONA.name.toLowerCase()}] Registered \${facades.length} facades with \${facades.reduce((sum, f) => sum + f.ops.length, 0)} operations\`);
|
|
171
|
+
console.error(\`[\${tag}] \${PERSONA.name} — \${PERSONA.role}\`);
|
|
172
|
+
console.error(\`[\${tag}] Registered \${facades.length} facades with \${facades.reduce((sum, f) => sum + f.ops.length, 0)} operations\`);
|
|
110
173
|
|
|
111
|
-
//
|
|
174
|
+
// ─── Transport + shutdown ──────────────────────────────────────
|
|
112
175
|
const transport = new StdioServerTransport();
|
|
113
176
|
await server.connect(transport);
|
|
114
|
-
console.error(\`[\${
|
|
115
|
-
console.error(\`[\${
|
|
177
|
+
console.error(\`[\${tag}] Connected via stdio transport\`);
|
|
178
|
+
console.error(\`[\${tag}] Say "Hello, \${PERSONA.name}!" to activate\`);
|
|
116
179
|
|
|
117
|
-
// Graceful shutdown
|
|
118
180
|
const shutdown = async (): Promise<void> => {
|
|
119
|
-
console.error(\`[\${
|
|
120
|
-
|
|
181
|
+
console.error(\`[\${tag}] Shutting down...\`);
|
|
182
|
+
runtime.close();
|
|
121
183
|
process.exit(0);
|
|
122
184
|
};
|
|
123
185
|
process.on('SIGTERM', shutdown);
|
|
@@ -130,10 +192,3 @@ main().catch((err) => {
|
|
|
130
192
|
});
|
|
131
193
|
`;
|
|
132
194
|
}
|
|
133
|
-
|
|
134
|
-
function pascalCase(s: string): string {
|
|
135
|
-
return s
|
|
136
|
-
.split(/[-_\s]+/)
|
|
137
|
-
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
138
|
-
.join('');
|
|
139
|
-
}
|
|
@@ -21,11 +21,13 @@ export function generatePackageJson(config: AgentConfig): string {
|
|
|
21
21
|
license: 'MIT',
|
|
22
22
|
engines: { node: '>=18.0.0' },
|
|
23
23
|
dependencies: {
|
|
24
|
-
'@anthropic-ai/sdk': '^0.39.0',
|
|
25
24
|
'@modelcontextprotocol/sdk': '^1.12.1',
|
|
26
25
|
'@soleri/core': '^2.0.0',
|
|
27
26
|
zod: '^3.24.2',
|
|
28
27
|
},
|
|
28
|
+
optionalDependencies: {
|
|
29
|
+
'@anthropic-ai/sdk': '^0.39.0',
|
|
30
|
+
},
|
|
29
31
|
devDependencies: {
|
|
30
32
|
'@types/node': '^22.13.4',
|
|
31
33
|
'@vitest/coverage-v8': '^3.0.5',
|