@soleri/forge 5.8.0 → 5.10.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/types.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { z } from 'zod';
2
+ /** Communication tone for the agent persona */
3
+ export const TONES = ['precise', 'mentor', 'pragmatic'];
2
4
  /** Agent configuration — everything needed to scaffold */
3
5
  export const AgentConfigSchema = z.object({
4
6
  /** Agent identifier (kebab-case, used for directory and package name) */
@@ -13,11 +15,15 @@ export const AgentConfigSchema = z.object({
13
15
  domains: z.array(z.string().min(1)).min(1).max(20),
14
16
  /** Core principles the agent follows (3-7 recommended) */
15
17
  principles: z.array(z.string()).min(1).max(10),
18
+ /** Communication tone: precise, mentor, or pragmatic */
19
+ tone: z.enum(TONES).optional().default('pragmatic'),
16
20
  /** Greeting message when agent introduces itself (auto-generated if omitted) */
17
21
  greeting: z.string().min(10).max(300).optional(),
18
22
  /** Output directory (parent — agent dir will be created inside, defaults to cwd) */
19
23
  outputDir: z.string().min(1).optional().default(process.cwd()),
20
24
  /** Hook packs to install after scaffolding (optional) */
21
25
  hookPacks: z.array(z.string()).optional(),
26
+ /** Skills to include (if omitted, all skills are included for backward compat) */
27
+ skills: z.array(z.string()).optional(),
22
28
  });
23
29
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,0DAA0D;AAC1D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,yEAAyE;IACzE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mBAAmB,EAAE,gDAAgD,CAAC;IAC3F,kCAAkC;IAClC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/B,gCAAgC;IAChC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAChC,iDAAiD;IACjD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACxC,0CAA0C;IAC1C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAClD,0DAA0D;IAC1D,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9C,gFAAgF;IAChF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAChD,oFAAoF;IACpF,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9D,yDAAyD;IACzD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,+CAA+C;AAC/C,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAU,CAAC;AAGjE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,yEAAyE;IACzE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mBAAmB,EAAE,gDAAgD,CAAC;IAC3F,kCAAkC;IAClC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/B,gCAAgC;IAChC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAChC,iDAAiD;IACjD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACxC,0CAA0C;IAC1C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAClD,0DAA0D;IAC1D,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9C,wDAAwD;IACxD,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC;IACnD,gFAAgF;IAChF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAChD,oFAAoF;IACpF,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9D,yDAAyD;IACzD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACzC,kFAAkF;IAClF,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleri/forge",
3
- "version": "5.8.0",
3
+ "version": "5.10.0",
4
4
  "description": "Scaffold AI agents that learn, remember, and grow with you.",
5
5
  "keywords": [
6
6
  "agent",
@@ -31,6 +31,10 @@
31
31
  "./lib": {
32
32
  "types": "./dist/lib.d.ts",
33
33
  "import": "./dist/lib.js"
34
+ },
35
+ "./templates/extensions": {
36
+ "types": "./dist/templates/extensions.d.ts",
37
+ "import": "./dist/templates/extensions.js"
34
38
  }
35
39
  },
36
40
  "publishConfig": {
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { scaffold, previewScaffold } from '../scaffolder.js';
3
+ import { existsSync, readFileSync, rmSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+
7
+ describe('scaffold extensions', () => {
8
+ const outputDir = join(tmpdir(), 'soleri-ext-test-' + Date.now());
9
+ const agentId = 'ext-test-agent';
10
+ const agentDir = join(outputDir, agentId);
11
+
12
+ afterEach(() => {
13
+ rmSync(outputDir, { recursive: true, force: true });
14
+ });
15
+
16
+ it('should create extensions directory with index and example op', () => {
17
+ scaffold({
18
+ id: agentId,
19
+ name: 'Extension Test',
20
+ role: 'Test agent',
21
+ description: 'Agent for testing extensions scaffold',
22
+ domains: ['testing'],
23
+ principles: ['Test everything'],
24
+ tone: 'pragmatic',
25
+ outputDir,
26
+ });
27
+
28
+ // Extensions directory exists
29
+ expect(existsSync(join(agentDir, 'src', 'extensions', 'index.ts'))).toBe(true);
30
+ expect(existsSync(join(agentDir, 'src', 'extensions', 'ops', 'example.ts'))).toBe(true);
31
+ expect(existsSync(join(agentDir, 'src', 'extensions', 'facades'))).toBe(true);
32
+ expect(existsSync(join(agentDir, 'src', 'extensions', 'middleware'))).toBe(true);
33
+
34
+ // Extensions index references the agent ID
35
+ const indexContent = readFileSync(join(agentDir, 'src', 'extensions', 'index.ts'), 'utf-8');
36
+ expect(indexContent).toContain('ext-test-agent_core');
37
+ expect(indexContent).toContain('loadExtensions');
38
+ expect(indexContent).toContain('AgentExtensions');
39
+
40
+ // Entry point imports extensions
41
+ const entryPoint = readFileSync(join(agentDir, 'src', 'index.ts'), 'utf-8');
42
+ expect(entryPoint).toContain('wrapWithMiddleware');
43
+ expect(entryPoint).toContain('./extensions/index.js');
44
+ expect(entryPoint).toContain('AgentExtensions');
45
+ });
46
+
47
+ it('should include extensions in preview', () => {
48
+ const preview = previewScaffold({
49
+ id: agentId,
50
+ name: 'Extension Test',
51
+ role: 'Test agent',
52
+ description: 'Agent for testing extensions scaffold',
53
+ domains: ['testing'],
54
+ principles: ['Test everything'],
55
+ tone: 'pragmatic',
56
+ outputDir,
57
+ });
58
+
59
+ const extFile = preview.files.find((f) => f.path === 'src/extensions/');
60
+ expect(extFile).toBeDefined();
61
+ expect(extFile!.description).toContain('extension');
62
+ });
63
+ });
@@ -56,15 +56,18 @@ describe('Scaffolder', () => {
56
56
  expect(paths).not.toContain('src/facades/data-pipelines.facade.ts');
57
57
 
58
58
  // Should have domain facades + core facade in preview
59
- expect(preview.facades).toHaveLength(4); // 3 domains + core
59
+ expect(preview.facades).toHaveLength(14); // 3 domains + 10 semantic + 1 agent core
60
60
  expect(preview.facades[0].name).toBe('atlas_data_pipelines');
61
61
 
62
- // Core facade should list all 208 ops (203 core + 5 agent-specific)
62
+ // Agent-specific facade has 5 ops
63
63
  const coreFacade = preview.facades.find((f) => f.name === 'atlas_core')!;
64
- expect(coreFacade.ops.length).toBe(208);
65
- expect(coreFacade.ops).toContain('curator_status');
64
+ expect(coreFacade.ops.length).toBe(5);
66
65
  expect(coreFacade.ops).toContain('health');
67
66
 
67
+ // Semantic facades cover the rest
68
+ const vaultFacade = preview.facades.find((f) => f.name === 'atlas_vault')!;
69
+ expect(vaultFacade).toBeDefined();
70
+
68
71
  // Should NOT create any files
69
72
  expect(existsSync(join(tempDir, 'atlas'))).toBe(false);
70
73
  });
@@ -138,7 +141,7 @@ describe('Scaffolder', () => {
138
141
 
139
142
  // v5.0 runtime factory pattern
140
143
  expect(entry).toContain('createAgentRuntime');
141
- expect(entry).toContain('createCoreOps');
144
+ expect(entry).toContain('createSemanticFacades');
142
145
  expect(entry).toContain('createDomainFacades');
143
146
  expect(entry).toContain("from '@soleri/core'");
144
147
  expect(entry).toContain("agentId: 'atlas'");
@@ -221,7 +224,7 @@ describe('Scaffolder', () => {
221
224
 
222
225
  // Should use runtime factories from @soleri/core
223
226
  expect(facadesTest).toContain('createAgentRuntime');
224
- expect(facadesTest).toContain('createCoreOps');
227
+ expect(facadesTest).toContain('createSemanticFacades');
225
228
  expect(facadesTest).toContain('createDomainFacade');
226
229
  expect(facadesTest).toContain("from '@soleri/core'");
227
230
 
package/src/lib.ts CHANGED
@@ -17,3 +17,4 @@ export type {
17
17
  AddDomainResult,
18
18
  } from './types.js';
19
19
  export { AgentConfigSchema } from './types.js';
20
+ export { generateExtensionsIndex, generateExampleOp } from './templates/extensions.js';
package/src/scaffolder.ts CHANGED
@@ -23,6 +23,7 @@ import { generateActivate } from './templates/activate.js';
23
23
  import { generateReadme } from './templates/readme.js';
24
24
  import { generateSetupScript } from './templates/setup-script.js';
25
25
  import { generateSkills } from './templates/skills.js';
26
+ import { generateExtensionsIndex, generateExampleOp } from './templates/extensions.js';
26
27
 
27
28
  /**
28
29
  * Preview what scaffold will create without writing anything.
@@ -42,7 +43,7 @@ export function previewScaffold(config: AgentConfig): ScaffoldPreview {
42
43
  {
43
44
  path: 'src/index.ts',
44
45
  description:
45
- 'Entry point — thin shell using createAgentRuntime() + createCoreOps() from @soleri/core',
46
+ 'Entry point — thin shell using createAgentRuntime() + createSemanticFacades() from @soleri/core',
46
47
  },
47
48
  ...config.domains.map((d) => ({
48
49
  path: `src/intelligence/data/${d}.json`,
@@ -68,6 +69,10 @@ export function previewScaffold(config: AgentConfig): ScaffoldPreview {
68
69
  path: 'src/__tests__/facades.test.ts',
69
70
  description: `Facade integration tests — all ${config.domains.length + 1} facades`,
70
71
  },
72
+ {
73
+ path: 'src/extensions/',
74
+ description: 'User extension directory — custom ops, facades, middleware, hooks',
75
+ },
71
76
  { path: '.mcp.json', description: 'MCP client config for connecting to this agent' },
72
77
  {
73
78
  path: 'README.md',
@@ -79,8 +84,9 @@ export function previewScaffold(config: AgentConfig): ScaffoldPreview {
79
84
  },
80
85
  {
81
86
  path: 'skills/',
82
- description:
83
- '17 built-in skills TDD, debugging, planning, vault, brain, code patrol, retrospective, onboarding',
87
+ description: config.skills?.length
88
+ ? `${config.skills.length} selected skills`
89
+ : '17 built-in skills — TDD, debugging, planning, vault, brain, code patrol, retrospective, onboarding',
84
90
  },
85
91
  ];
86
92
 
@@ -96,257 +102,90 @@ export function previewScaffold(config: AgentConfig): ScaffoldPreview {
96
102
  name: `${config.id}_${d.replace(/-/g, '_')}`,
97
103
  ops: ['get_patterns', 'search', 'get_entry', 'capture', 'remove'],
98
104
  })),
105
+ // 10 semantic facades from createSemanticFacades()
99
106
  {
100
- name: `${config.id}_core`,
107
+ name: `${config.id}_vault`,
101
108
  ops: [
102
- // From createCoreOps() — 150 generic ops
103
109
  'search',
104
110
  'vault_stats',
105
111
  'list_all',
106
- 'register',
107
- 'memory_search',
108
- 'memory_capture',
109
- 'memory_list',
110
- 'session_capture',
111
112
  'export',
113
+ 'capture_enriched',
114
+ '...vault-extra',
115
+ '...capture',
116
+ '...intake',
117
+ ],
118
+ },
119
+ {
120
+ name: `${config.id}_plan`,
121
+ ops: [
112
122
  'create_plan',
113
123
  'get_plan',
114
124
  'approve_plan',
115
125
  'update_task',
116
126
  'complete_plan',
127
+ '...planning-extra',
128
+ '...grading',
129
+ ],
130
+ },
131
+ {
132
+ name: `${config.id}_brain`,
133
+ ops: [
117
134
  'record_feedback',
118
135
  'brain_feedback',
119
- 'brain_feedback_stats',
120
- 'rebuild_vocabulary',
121
136
  'brain_stats',
122
137
  'llm_status',
123
- 'brain_session_context',
124
138
  'brain_strengths',
125
- 'brain_global_patterns',
126
- 'brain_recommend',
127
- 'brain_build_intelligence',
128
- 'brain_export',
129
- 'brain_import',
130
- 'brain_extract_knowledge',
131
- 'brain_archive_sessions',
132
- 'brain_promote_proposals',
133
- 'brain_lifecycle',
134
- 'brain_reset_extracted',
135
- // Brain decay report (#89) — 1
136
- 'brain_decay_report',
137
- // Cognee ops — 5
138
- 'cognee_status',
139
- 'cognee_search',
140
- 'cognee_add',
141
- 'cognee_cognify',
142
- 'cognee_config',
143
- // LLM ops — 2
139
+ '...19 brain ops',
140
+ ],
141
+ },
142
+ {
143
+ name: `${config.id}_memory`,
144
+ ops: [
145
+ 'memory_search',
146
+ 'memory_capture',
147
+ 'memory_list',
148
+ 'session_capture',
149
+ '...memory-extra',
150
+ '...cross-project',
151
+ ],
152
+ },
153
+ {
154
+ name: `${config.id}_admin`,
155
+ ops: [
144
156
  'llm_rotate',
145
157
  'llm_call',
146
- // Prompt ops — 2
147
158
  'render_prompt',
148
159
  'list_templates',
149
- 'curator_status',
150
- 'curator_detect_duplicates',
151
- 'curator_contradictions',
152
- 'curator_resolve_contradiction',
153
- 'curator_groom',
154
- 'curator_groom_all',
155
- 'curator_consolidate',
156
- 'curator_health_audit',
157
- 'get_identity',
158
- 'update_identity',
159
- 'add_guideline',
160
- 'remove_guideline',
161
- 'rollback_identity',
162
- 'route_intent',
163
- 'morph',
164
- 'get_behavior_rules',
165
- // Governance ops — 5
166
- 'governance_policy',
167
- 'governance_proposals',
168
- 'governance_stats',
169
- 'governance_expire',
170
- 'governance_dashboard',
171
- // Planning Extra ops — 22
172
- 'plan_iterate',
173
- 'plan_split',
174
- 'plan_reconcile',
175
- 'plan_complete_lifecycle',
176
- 'plan_dispatch',
177
- 'plan_review',
178
- 'plan_archive',
179
- 'plan_list_tasks',
180
- 'plan_stats',
181
- // Planning evidence (#148)
182
- 'plan_submit_evidence',
183
- 'plan_verify_task',
184
- 'plan_verify_plan',
185
- // Subagent dispatch (#149)
186
- 'plan_review_spec',
187
- 'plan_review_quality',
188
- 'plan_review_outcome',
189
- // Brainstorm (#150)
190
- 'plan_brainstorm',
191
- // Auto-reconcile (#151)
192
- 'plan_auto_reconcile',
193
- // Validate plan (#152)
194
- 'plan_validate',
195
- // Execution metrics + deliverables (#80, #83)
196
- 'plan_execution_metrics',
197
- 'plan_record_task_metrics',
198
- 'plan_submit_deliverable',
199
- 'plan_verify_deliverables',
200
- // Memory Extra ops — 8
201
- 'memory_delete',
202
- 'memory_stats',
203
- 'memory_export',
204
- 'memory_import',
205
- 'memory_prune',
206
- 'memory_deduplicate',
207
- 'memory_topics',
208
- 'memory_by_project',
209
- // Vault Extra ops — 12
210
- 'vault_get',
211
- 'vault_update',
212
- 'vault_remove',
213
- 'vault_bulk_add',
214
- 'vault_bulk_remove',
215
- 'vault_tags',
216
- 'vault_domains',
217
- 'vault_recent',
218
- 'vault_import',
219
- 'vault_seed',
220
- 'vault_backup',
221
- 'vault_age_report',
222
- // Vault extra — seed canonical + knowledge lifecycle (5)
223
- 'vault_seed_canonical',
224
- 'knowledge_audit',
225
- 'knowledge_health',
226
- 'knowledge_merge',
227
- 'knowledge_reorganize',
228
- // Bi-temporal vault ops (#89) — 3
229
- 'vault_set_temporal',
230
- 'vault_find_expiring',
231
- 'vault_find_expired',
232
- // Admin ops — 8
233
- 'admin_health',
234
- 'admin_tool_list',
235
- 'admin_config',
236
- 'admin_vault_size',
237
- 'admin_uptime',
238
- 'admin_version',
239
- 'admin_reset_cache',
240
- 'admin_diagnostic',
241
- // Loop ops — 9
242
- 'loop_start',
243
- 'loop_iterate',
244
- 'loop_iterate_gate',
245
- 'loop_status',
246
- 'loop_cancel',
247
- 'loop_history',
248
- 'loop_is_active',
249
- 'loop_complete',
250
- 'loop_anomaly_check',
251
- // Orchestrate ops — 5
252
- 'orchestrate_plan',
253
- 'orchestrate_execute',
254
- 'orchestrate_complete',
255
- 'orchestrate_status',
256
- 'orchestrate_quick_capture',
257
- // Grading ops — 5
258
- 'plan_grade',
259
- 'plan_check_history',
260
- 'plan_latest_check',
261
- 'plan_meets_grade',
262
- 'plan_auto_improve',
263
- // Capture ops — 4
264
- 'capture_knowledge',
265
- 'capture_quick',
266
- 'search_intelligent',
267
- 'search_feedback',
268
- // Enriched capture (#154) — 1
269
- 'capture_enriched',
270
- // Cognee graph (#156) — 3
271
- 'cognee_get_node',
272
- 'cognee_graph_stats',
273
- 'cognee_export_status',
274
- // Cognee Sync ops — 3
275
- 'cognee_sync_status',
276
- 'cognee_sync_drain',
277
- 'cognee_sync_reconcile',
278
- // Intake ops — 4
279
- 'intake_ingest_book',
280
- 'intake_process',
281
- 'intake_status',
282
- 'intake_preview',
283
- // Admin Extra ops — 23
284
- 'admin_telemetry',
285
- 'admin_telemetry_recent',
286
- 'admin_telemetry_reset',
287
- 'admin_permissions',
288
- 'admin_vault_analytics',
289
- 'admin_search_insights',
290
- 'admin_module_status',
291
- 'admin_env',
292
- 'admin_gc',
293
- 'admin_export_config',
294
- // Admin key pool (#157)
295
- 'admin_key_pool_status',
296
- 'admin_create_token',
297
- 'admin_revoke_token',
298
- 'admin_list_tokens',
299
- // Admin accounts (#158)
300
- 'admin_add_account',
301
- 'admin_remove_account',
302
- 'admin_rotate_account',
303
- 'admin_list_accounts',
304
- 'admin_account_status',
305
- // Admin plugins (#159)
306
- 'admin_list_plugins',
307
- 'admin_plugin_status',
308
- // Admin instruction validation (#160)
309
- 'admin_validate_instructions',
310
- // Hot reload (#63)
311
- 'admin_hot_reload',
312
- // Curator Extra ops — 5
313
- 'curator_entry_history',
314
- 'curator_record_snapshot',
315
- 'curator_queue_stats',
316
- 'curator_enrich',
317
- // Hybrid contradiction detection (#36) — 1
318
- 'curator_hybrid_contradictions',
319
- // Project ops — 12
320
- 'project_get',
321
- 'project_list',
322
- 'project_unregister',
323
- 'project_get_rules',
324
- 'project_list_rules',
325
- 'project_add_rule',
326
- 'project_remove_rule',
327
- 'project_link',
328
- 'project_unlink',
329
- 'project_get_links',
330
- 'project_linked_projects',
331
- 'project_touch',
332
- // Cross-project memory ops — 3
333
- 'memory_promote_to_global',
334
- 'memory_configure',
335
- 'memory_cross_project_search',
336
- // Playbook ops — 5
337
- 'playbook_list',
338
- 'playbook_get',
339
- 'playbook_create',
340
- 'playbook_match',
341
- 'playbook_seed',
342
- // Agent-specific ops — 5
343
- 'health',
344
- 'identity',
345
- 'activate',
346
- 'inject_claude_md',
347
- 'setup',
160
+ '...admin',
161
+ '...admin-extra',
348
162
  ],
349
163
  },
164
+ {
165
+ name: `${config.id}_curator`,
166
+ ops: ['curator_status', 'curator_health_audit', '...8 curator ops', '...curator-extra'],
167
+ },
168
+ {
169
+ name: `${config.id}_loop`,
170
+ ops: ['loop_start', 'loop_iterate', 'loop_cancel', '...loop ops'],
171
+ },
172
+ {
173
+ name: `${config.id}_orchestrate`,
174
+ ops: ['register', '...orchestrate', '...project', '...playbook'],
175
+ },
176
+ {
177
+ name: `${config.id}_control`,
178
+ ops: ['get_identity', 'route_intent', 'governance_policy', '...control+governance ops'],
179
+ },
180
+ {
181
+ name: `${config.id}_cognee`,
182
+ ops: ['cognee_status', 'cognee_search', '...cognee ops', '...cognee-sync'],
183
+ },
184
+ // Agent-specific facade
185
+ {
186
+ name: `${config.id}_core`,
187
+ ops: ['health', 'identity', 'activate', 'inject_claude_md', 'setup'],
188
+ },
350
189
  ];
351
190
 
352
191
  return {
@@ -393,6 +232,10 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
393
232
  'src/identity',
394
233
  'src/activation',
395
234
  'src/__tests__',
235
+ 'src/extensions',
236
+ 'src/extensions/ops',
237
+ 'src/extensions/facades',
238
+ 'src/extensions/middleware',
396
239
  ];
397
240
 
398
241
  if (config.hookPacks?.length) {
@@ -438,6 +281,8 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
438
281
  ['src/activation/activate.ts', generateActivate(config)],
439
282
  ['src/index.ts', generateEntryPoint(config)],
440
283
  ['src/__tests__/facades.test.ts', generateFacadesTest(config)],
284
+ ['src/extensions/index.ts', generateExtensionsIndex(config)],
285
+ ['src/extensions/ops/example.ts', generateExampleOp(config)],
441
286
  ];
442
287
 
443
288
  // Empty intelligence data bundles (domain facades come from @soleri/core at runtime)
@@ -459,7 +304,7 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
459
304
  filesCreated.push(path);
460
305
  }
461
306
 
462
- const totalOps = config.domains.length * 5 + 61; // 5 per domain + 56 core (from createCoreOps) + 5 agent-specific
307
+ const totalOps = config.domains.length * 5 + 214; // 5 per domain + 209 semantic + 5 agent-specific
463
308
 
464
309
  // Auto-build: install dependencies and compile before registering MCP
465
310
  let buildSuccess = false;
@@ -494,7 +339,7 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
494
339
 
495
340
  const summaryLines = [
496
341
  `Created ${config.name} agent at ${agentDir}`,
497
- `${config.domains.length + 1} facades with ${totalOps} operations`,
342
+ `${config.domains.length + 11} facades with ${totalOps} operations`,
498
343
  `${config.domains.length} empty knowledge domains ready for capture`,
499
344
  `Intelligence layer (Brain) — TF-IDF scoring, auto-tagging, duplicate detection`,
500
345
  `Activation system included — say "Hello, ${config.name}!" to activate`,
@@ -3,7 +3,7 @@ import type { AgentConfig } from '../types.js';
3
3
  /**
4
4
  * Generate the main index.ts entry point for the agent.
5
5
  *
6
- * v5.0: Thin shell — delegates to createAgentRuntime(), createCoreOps(),
6
+ * v5.0: Thin shell — delegates to createAgentRuntime(), createSemanticFacades(),
7
7
  * and createDomainFacades() from @soleri/core. Only agent-specific code
8
8
  * (persona, activation) lives here.
9
9
  */
@@ -19,12 +19,13 @@ import { fileURLToPath } from 'node:url';
19
19
 
20
20
  import {
21
21
  createAgentRuntime,
22
- createCoreOps,
22
+ createSemanticFacades,
23
23
  createDomainFacades,
24
24
  registerAllFacades,
25
25
  seedDefaultPlaybooks,
26
+ wrapWithMiddleware,
26
27
  } from '@soleri/core';
27
- import type { OpDefinition } from '@soleri/core';
28
+ import type { OpDefinition, AgentExtensions } from '@soleri/core';
28
29
  import { z } from 'zod';
29
30
  import { PERSONA, getPersonaPrompt } from './identity/persona.js';
30
31
  import { activateAgent, deactivateAgent } from './activation/activate.js';
@@ -201,15 +202,51 @@ async function main(): Promise<void> {
201
202
  ];
202
203
 
203
204
  // ─── Assemble facades ──────────────────────────────────────────
204
- const coreOps = createCoreOps(runtime);
205
- const coreFacade = {
205
+ const semanticFacades = createSemanticFacades(runtime, '${config.id}');
206
+ const agentFacade = {
206
207
  name: '${config.id}_core',
207
- description: 'Core operations — vault stats, cross-domain search, health check, identity, and activation system.',
208
- ops: [...coreOps, ...agentOps],
208
+ description: 'Agent-specific operations — health, identity, activation, CLAUDE.md injection, setup.',
209
+ ops: agentOps,
209
210
  };
210
211
 
211
212
  const domainFacades = createDomainFacades(runtime, '${config.id}', ${domainsLiteral});
212
213
 
214
+ // ─── User extensions (auto-discovered from src/extensions/) ────
215
+ let extensions: AgentExtensions = {};
216
+ try {
217
+ const ext = await import('./extensions/index.js');
218
+ const loader = ext.default ?? (ext as Record<string, unknown>).loadExtensions;
219
+ if (typeof loader === 'function') {
220
+ extensions = loader(runtime);
221
+ } else if (typeof loader === 'object') {
222
+ extensions = loader;
223
+ }
224
+ if (extensions.ops?.length || extensions.facades?.length || extensions.middleware?.length) {
225
+ console.error(\`[\${tag}] Extensions loaded: \${extensions.ops?.length ?? 0} ops, \${extensions.facades?.length ?? 0} facades, \${extensions.middleware?.length ?? 0} middleware\`);
226
+ }
227
+ } catch {
228
+ // No extensions directory or load error — run vanilla
229
+ }
230
+
231
+ // Merge user ops into agent facade
232
+ if (extensions.ops?.length) {
233
+ agentFacade.ops.push(...extensions.ops);
234
+ }
235
+
236
+ // Collect user facades
237
+ const userFacades = extensions.facades ?? [];
238
+
239
+ // Apply middleware to all facades
240
+ const allFacades = [...semanticFacades, agentFacade, ...domainFacades, ...userFacades];
241
+ if (extensions.middleware?.length) {
242
+ wrapWithMiddleware(allFacades, extensions.middleware);
243
+ }
244
+
245
+ // Lifecycle: onStartup
246
+ if (extensions.hooks?.onStartup) {
247
+ await extensions.hooks.onStartup(runtime);
248
+ }
249
+
213
250
  // ─── MCP server ────────────────────────────────────────────────
214
251
  const server = new McpServer({
215
252
  name: '${config.id}-mcp',
@@ -220,11 +257,10 @@ async function main(): Promise<void> {
220
257
  messages: [{ role: 'assistant' as const, content: { type: 'text' as const, text: getPersonaPrompt() } }],
221
258
  }));
222
259
 
223
- const facades = [coreFacade, ...domainFacades];
224
- registerAllFacades(server, facades);
260
+ registerAllFacades(server, allFacades);
225
261
 
226
262
  console.error(\`[\${tag}] \${PERSONA.name} — \${PERSONA.role}\`);
227
- console.error(\`[\${tag}] Registered \${facades.length} facades with \${facades.reduce((sum, f) => sum + f.ops.length, 0)} operations\`);
263
+ console.error(\`[\${tag}] Registered \${allFacades.length} facades with \${allFacades.reduce((sum, f) => sum + f.ops.length, 0)} operations\`);
228
264
 
229
265
  // ─── Transport + shutdown ──────────────────────────────────────
230
266
  const transport = new StdioServerTransport();
@@ -234,6 +270,13 @@ async function main(): Promise<void> {
234
270
 
235
271
  const shutdown = async (): Promise<void> => {
236
272
  console.error(\`[\${tag}] Shutting down...\`);
273
+ if (extensions.hooks?.onShutdown) {
274
+ try {
275
+ await extensions.hooks.onShutdown(runtime);
276
+ } catch (err) {
277
+ console.error(\`[\${tag}] Extension shutdown error:\`, err);
278
+ }
279
+ }
237
280
  runtime.close();
238
281
  process.exit(0);
239
282
  };