@soleri/forge 5.9.0 → 5.11.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.9.0",
3
+ "version": "5.11.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(212);
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,263 +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
- // Vault archival (#86) — 3
233
- 'vault_archive',
234
- 'vault_restore',
235
- 'vault_optimize',
236
- // Admin ops — 8
237
- 'admin_health',
238
- 'admin_tool_list',
239
- 'admin_config',
240
- 'admin_vault_size',
241
- 'admin_uptime',
242
- 'admin_version',
243
- 'admin_reset_cache',
244
- 'admin_diagnostic',
245
- // Loop ops — 9
246
- 'loop_start',
247
- 'loop_iterate',
248
- 'loop_iterate_gate',
249
- 'loop_status',
250
- 'loop_cancel',
251
- 'loop_history',
252
- 'loop_is_active',
253
- 'loop_complete',
254
- 'loop_anomaly_check',
255
- // Orchestrate ops — 5
256
- 'orchestrate_plan',
257
- 'orchestrate_execute',
258
- 'orchestrate_complete',
259
- 'orchestrate_status',
260
- 'orchestrate_quick_capture',
261
- // Grading ops — 5
262
- 'plan_grade',
263
- 'plan_check_history',
264
- 'plan_latest_check',
265
- 'plan_meets_grade',
266
- 'plan_auto_improve',
267
- // Capture ops — 4
268
- 'capture_knowledge',
269
- 'capture_quick',
270
- 'search_intelligent',
271
- 'search_feedback',
272
- // Enriched capture (#154) — 1
273
- 'capture_enriched',
274
- // Cognee graph (#156) — 3
275
- 'cognee_get_node',
276
- 'cognee_graph_stats',
277
- 'cognee_export_status',
278
- // Cognee Sync ops — 3
279
- 'cognee_sync_status',
280
- 'cognee_sync_drain',
281
- 'cognee_sync_reconcile',
282
- // Intake ops — 4
283
- 'intake_ingest_book',
284
- 'intake_process',
285
- 'intake_status',
286
- 'intake_preview',
287
- // Admin Extra ops — 23
288
- 'admin_telemetry',
289
- 'admin_telemetry_recent',
290
- 'admin_telemetry_reset',
291
- 'admin_permissions',
292
- 'admin_vault_analytics',
293
- 'admin_search_insights',
294
- 'admin_module_status',
295
- 'admin_env',
296
- 'admin_gc',
297
- 'admin_export_config',
298
- // Admin key pool (#157)
299
- 'admin_key_pool_status',
300
- 'admin_create_token',
301
- 'admin_revoke_token',
302
- 'admin_list_tokens',
303
- // Admin accounts (#158)
304
- 'admin_add_account',
305
- 'admin_remove_account',
306
- 'admin_rotate_account',
307
- 'admin_list_accounts',
308
- 'admin_account_status',
309
- // Admin plugins (#159)
310
- 'admin_list_plugins',
311
- 'admin_plugin_status',
312
- // Admin instruction validation (#160)
313
- 'admin_validate_instructions',
314
- // Hot reload (#63)
315
- 'admin_hot_reload',
316
- // Admin persistence (#85) — 1
317
- 'admin_persistence_info',
318
- // Curator Extra ops — 5
319
- 'curator_entry_history',
320
- 'curator_record_snapshot',
321
- 'curator_queue_stats',
322
- 'curator_enrich',
323
- // Hybrid contradiction detection (#36) — 1
324
- 'curator_hybrid_contradictions',
325
- // Project ops — 12
326
- 'project_get',
327
- 'project_list',
328
- 'project_unregister',
329
- 'project_get_rules',
330
- 'project_list_rules',
331
- 'project_add_rule',
332
- 'project_remove_rule',
333
- 'project_link',
334
- 'project_unlink',
335
- 'project_get_links',
336
- 'project_linked_projects',
337
- 'project_touch',
338
- // Cross-project memory ops — 3
339
- 'memory_promote_to_global',
340
- 'memory_configure',
341
- 'memory_cross_project_search',
342
- // Playbook ops — 5
343
- 'playbook_list',
344
- 'playbook_get',
345
- 'playbook_create',
346
- 'playbook_match',
347
- 'playbook_seed',
348
- // Agent-specific ops — 5
349
- 'health',
350
- 'identity',
351
- 'activate',
352
- 'inject_claude_md',
353
- 'setup',
160
+ '...admin',
161
+ '...admin-extra',
354
162
  ],
355
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
+ },
356
189
  ];
357
190
 
358
191
  return {
@@ -399,6 +232,10 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
399
232
  'src/identity',
400
233
  'src/activation',
401
234
  'src/__tests__',
235
+ 'src/extensions',
236
+ 'src/extensions/ops',
237
+ 'src/extensions/facades',
238
+ 'src/extensions/middleware',
402
239
  ];
403
240
 
404
241
  if (config.hookPacks?.length) {
@@ -444,6 +281,8 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
444
281
  ['src/activation/activate.ts', generateActivate(config)],
445
282
  ['src/index.ts', generateEntryPoint(config)],
446
283
  ['src/__tests__/facades.test.ts', generateFacadesTest(config)],
284
+ ['src/extensions/index.ts', generateExtensionsIndex(config)],
285
+ ['src/extensions/ops/example.ts', generateExampleOp(config)],
447
286
  ];
448
287
 
449
288
  // Empty intelligence data bundles (domain facades come from @soleri/core at runtime)
@@ -465,7 +304,7 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
465
304
  filesCreated.push(path);
466
305
  }
467
306
 
468
- 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
469
308
 
470
309
  // Auto-build: install dependencies and compile before registering MCP
471
310
  let buildSuccess = false;
@@ -500,7 +339,7 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
500
339
 
501
340
  const summaryLines = [
502
341
  `Created ${config.name} agent at ${agentDir}`,
503
- `${config.domains.length + 1} facades with ${totalOps} operations`,
342
+ `${config.domains.length + 11} facades with ${totalOps} operations`,
504
343
  `${config.domains.length} empty knowledge domains ready for capture`,
505
344
  `Intelligence layer (Brain) — TF-IDF scoring, auto-tagging, duplicate detection`,
506
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
  };