@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.
@@ -12,7 +12,7 @@ export function generateFacadesTest(config: AgentConfig): string {
12
12
  return `import { describe, it, expect, beforeEach, afterEach } from 'vitest';
13
13
  import {
14
14
  createAgentRuntime,
15
- createCoreOps,
15
+ createSemanticFacades,
16
16
  createDomainFacade,
17
17
  } from '@soleri/core';
18
18
  import type { AgentRuntime, IntelligenceEntry, OpDefinition, FacadeConfig } from '@soleri/core';
@@ -57,9 +57,217 @@ describe('Facades', () => {
57
57
 
58
58
  ${domainDescribes}
59
59
 
60
- describe('${config.id}_core', () => {
61
- function buildCoreFacade(): FacadeConfig {
62
- const coreOps = createCoreOps(runtime);
60
+ // ─── Semantic Facades ────────────────────────────────────────
61
+ describe('semantic facades', () => {
62
+ function buildSemanticFacades(): FacadeConfig[] {
63
+ return createSemanticFacades(runtime, '${config.id}');
64
+ }
65
+
66
+ it('should create 10 semantic facades', () => {
67
+ const facades = buildSemanticFacades();
68
+ expect(facades).toHaveLength(10);
69
+ const names = facades.map(f => f.name);
70
+ expect(names).toContain('${config.id}_vault');
71
+ expect(names).toContain('${config.id}_plan');
72
+ expect(names).toContain('${config.id}_brain');
73
+ expect(names).toContain('${config.id}_memory');
74
+ expect(names).toContain('${config.id}_admin');
75
+ expect(names).toContain('${config.id}_curator');
76
+ expect(names).toContain('${config.id}_loop');
77
+ expect(names).toContain('${config.id}_orchestrate');
78
+ expect(names).toContain('${config.id}_control');
79
+ expect(names).toContain('${config.id}_cognee');
80
+ });
81
+
82
+ it('total ops across all facades should be 209', () => {
83
+ const facades = buildSemanticFacades();
84
+ const totalOps = facades.reduce((sum, f) => sum + f.ops.length, 0);
85
+ expect(totalOps).toBe(209);
86
+ });
87
+ });
88
+
89
+ describe('${config.id}_vault', () => {
90
+ function getFacade(): FacadeConfig {
91
+ return createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_vault')!;
92
+ }
93
+
94
+ it('should contain vault ops', () => {
95
+ const opNames = getFacade().ops.map(o => o.name);
96
+ expect(opNames).toContain('search');
97
+ expect(opNames).toContain('vault_stats');
98
+ expect(opNames).toContain('list_all');
99
+ expect(opNames).toContain('export');
100
+ expect(opNames).toContain('vault_get');
101
+ expect(opNames).toContain('vault_import');
102
+ expect(opNames).toContain('capture_knowledge');
103
+ expect(opNames).toContain('intake_ingest_book');
104
+ });
105
+
106
+ it('search should query across all domains', async () => {
107
+ runtime.vault.seed([
108
+ makeEntry({ id: 'c1', domain: 'alpha', title: 'Alpha pattern', tags: ['a'] }),
109
+ makeEntry({ id: 'c2', domain: 'beta', title: 'Beta pattern', tags: ['b'] }),
110
+ ]);
111
+ runtime = createAgentRuntime({ agentId: '${config.id}', vaultPath: ':memory:', plansPath: join(plannerDir, 'plans2.json') });
112
+ runtime.vault.seed([
113
+ makeEntry({ id: 'c1', domain: 'alpha', title: 'Alpha pattern', tags: ['a'] }),
114
+ makeEntry({ id: 'c2', domain: 'beta', title: 'Beta pattern', tags: ['b'] }),
115
+ ]);
116
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_vault')!;
117
+ const searchOp = facade.ops.find(o => o.name === 'search')!;
118
+ const results = (await searchOp.handler({ query: 'pattern' })) as Array<{ entry: unknown; score: number }>;
119
+ expect(Array.isArray(results)).toBe(true);
120
+ expect(results.length).toBe(2);
121
+ });
122
+
123
+ it('vault_stats should return counts', async () => {
124
+ runtime.vault.seed([
125
+ makeEntry({ id: 'vs1', domain: 'd1', tags: ['x'] }),
126
+ makeEntry({ id: 'vs2', domain: 'd2', tags: ['y'] }),
127
+ ]);
128
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_vault')!;
129
+ const statsOp = facade.ops.find(o => o.name === 'vault_stats')!;
130
+ const stats = (await statsOp.handler({})) as { totalEntries: number };
131
+ expect(stats.totalEntries).toBe(2);
132
+ });
133
+ });
134
+
135
+ describe('${config.id}_plan', () => {
136
+ it('should contain planning ops', () => {
137
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_plan')!;
138
+ const opNames = facade.ops.map(o => o.name);
139
+ expect(opNames).toContain('create_plan');
140
+ expect(opNames).toContain('get_plan');
141
+ expect(opNames).toContain('approve_plan');
142
+ expect(opNames).toContain('plan_iterate');
143
+ expect(opNames).toContain('plan_grade');
144
+ });
145
+
146
+ it('create_plan should create a draft plan', async () => {
147
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_plan')!;
148
+ const createOp = facade.ops.find(o => o.name === 'create_plan')!;
149
+ const result = (await createOp.handler({
150
+ objective: 'Add caching',
151
+ scope: 'api layer',
152
+ tasks: [{ title: 'Add Redis', description: 'Set up Redis client' }],
153
+ })) as { created: boolean; plan: { status: string } };
154
+ expect(result.created).toBe(true);
155
+ expect(result.plan.status).toBe('draft');
156
+ });
157
+ });
158
+
159
+ describe('${config.id}_brain', () => {
160
+ it('should contain brain ops', () => {
161
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_brain')!;
162
+ const opNames = facade.ops.map(o => o.name);
163
+ expect(opNames).toContain('brain_stats');
164
+ expect(opNames).toContain('brain_strengths');
165
+ expect(opNames).toContain('brain_build_intelligence');
166
+ expect(opNames).toContain('brain_lifecycle');
167
+ expect(opNames).toContain('brain_decay_report');
168
+ });
169
+
170
+ it('brain_stats should return intelligence stats', async () => {
171
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_brain')!;
172
+ const statsOp = facade.ops.find(o => o.name === 'brain_stats')!;
173
+ const result = (await statsOp.handler({})) as { vocabularySize: number };
174
+ expect(result.vocabularySize).toBe(0);
175
+ });
176
+ });
177
+
178
+ describe('${config.id}_memory', () => {
179
+ it('should contain memory ops', () => {
180
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_memory')!;
181
+ const opNames = facade.ops.map(o => o.name);
182
+ expect(opNames).toContain('memory_search');
183
+ expect(opNames).toContain('memory_capture');
184
+ expect(opNames).toContain('memory_promote_to_global');
185
+ });
186
+ });
187
+
188
+ describe('${config.id}_admin', () => {
189
+ it('should contain admin ops', () => {
190
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_admin')!;
191
+ const opNames = facade.ops.map(o => o.name);
192
+ expect(opNames).toContain('admin_health');
193
+ expect(opNames).toContain('admin_tool_list');
194
+ expect(opNames).toContain('llm_rotate');
195
+ expect(opNames).toContain('render_prompt');
196
+ });
197
+ });
198
+
199
+ describe('${config.id}_curator', () => {
200
+ it('should contain curator ops', () => {
201
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_curator')!;
202
+ const opNames = facade.ops.map(o => o.name);
203
+ expect(opNames).toContain('curator_status');
204
+ expect(opNames).toContain('curator_health_audit');
205
+ expect(opNames).toContain('curator_hybrid_contradictions');
206
+ });
207
+
208
+ it('curator_status should return initialized', async () => {
209
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_curator')!;
210
+ const statusOp = facade.ops.find(o => o.name === 'curator_status')!;
211
+ const result = (await statusOp.handler({})) as { initialized: boolean };
212
+ expect(result.initialized).toBe(true);
213
+ });
214
+ });
215
+
216
+ describe('${config.id}_loop', () => {
217
+ it('should contain loop ops', () => {
218
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_loop')!;
219
+ const opNames = facade.ops.map(o => o.name);
220
+ expect(opNames).toContain('loop_start');
221
+ expect(opNames).toContain('loop_iterate');
222
+ expect(opNames).toContain('loop_cancel');
223
+ });
224
+ });
225
+
226
+ describe('${config.id}_orchestrate', () => {
227
+ it('should contain orchestrate ops', () => {
228
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_orchestrate')!;
229
+ const opNames = facade.ops.map(o => o.name);
230
+ expect(opNames).toContain('register');
231
+ expect(opNames).toContain('orchestrate_plan');
232
+ expect(opNames).toContain('project_get');
233
+ expect(opNames).toContain('playbook_list');
234
+ });
235
+ });
236
+
237
+ describe('${config.id}_control', () => {
238
+ it('should contain control and governance ops', () => {
239
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_control')!;
240
+ const opNames = facade.ops.map(o => o.name);
241
+ expect(opNames).toContain('get_identity');
242
+ expect(opNames).toContain('route_intent');
243
+ expect(opNames).toContain('governance_policy');
244
+ expect(opNames).toContain('governance_dashboard');
245
+ });
246
+
247
+ it('governance_policy should return default policy', async () => {
248
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_control')!;
249
+ const policyOp = facade.ops.find(o => o.name === 'governance_policy')!;
250
+ const result = (await policyOp.handler({ action: 'get', projectPath: '/test' })) as {
251
+ projectPath: string;
252
+ quotas: { maxEntriesTotal: number };
253
+ };
254
+ expect(result.projectPath).toBe('/test');
255
+ expect(result.quotas.maxEntriesTotal).toBe(500);
256
+ });
257
+ });
258
+
259
+ describe('${config.id}_cognee', () => {
260
+ it('should contain cognee ops', () => {
261
+ const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_cognee')!;
262
+ const opNames = facade.ops.map(o => o.name);
263
+ expect(opNames).toContain('cognee_status');
264
+ expect(opNames).toContain('cognee_search');
265
+ expect(opNames).toContain('cognee_sync_status');
266
+ });
267
+ });
268
+
269
+ describe('${config.id}_core (agent-specific)', () => {
270
+ function buildAgentFacade(): FacadeConfig {
63
271
  const agentOps: OpDefinition[] = [
64
272
  {
65
273
  name: 'health',
@@ -167,239 +375,30 @@ ${domainDescribes}
167
375
  ];
168
376
  return {
169
377
  name: '${config.id}_core',
170
- description: 'Core operations',
171
- ops: [...coreOps, ...agentOps],
378
+ description: 'Agent-specific operations',
379
+ ops: agentOps,
172
380
  };
173
381
  }
174
382
 
175
- it('should create core facade with expected ops', () => {
176
- const facade = buildCoreFacade();
177
- expect(facade.name).toBe('${config.id}_core');
178
- const opNames = facade.ops.map((o) => o.name);
179
- // Core ops (37)
180
- expect(opNames).toContain('search');
181
- expect(opNames).toContain('vault_stats');
182
- expect(opNames).toContain('list_all');
183
- expect(opNames).toContain('register');
184
- expect(opNames).toContain('llm_status');
185
- expect(opNames).toContain('curator_status');
186
- expect(opNames).toContain('curator_health_audit');
187
- // Brain Intelligence ops (11)
188
- expect(opNames).toContain('brain_session_context');
189
- expect(opNames).toContain('brain_strengths');
190
- expect(opNames).toContain('brain_global_patterns');
191
- expect(opNames).toContain('brain_recommend');
192
- expect(opNames).toContain('brain_build_intelligence');
193
- expect(opNames).toContain('brain_export');
194
- expect(opNames).toContain('brain_import');
195
- expect(opNames).toContain('brain_extract_knowledge');
196
- expect(opNames).toContain('brain_archive_sessions');
197
- expect(opNames).toContain('brain_promote_proposals');
198
- expect(opNames).toContain('brain_lifecycle');
199
- // Enhanced brain ops (3)
200
- expect(opNames).toContain('brain_feedback');
201
- expect(opNames).toContain('brain_feedback_stats');
202
- expect(opNames).toContain('brain_reset_extracted');
203
- // Brain decay report (#89)
204
- expect(opNames).toContain('brain_decay_report');
205
- // Agent-specific ops (5)
206
- expect(opNames).toContain('health');
207
- expect(opNames).toContain('identity');
208
- expect(opNames).toContain('activate');
209
- expect(opNames).toContain('inject_claude_md');
210
- expect(opNames).toContain('setup');
211
- // Control ops (8)
212
- expect(opNames).toContain('get_identity');
213
- expect(opNames).toContain('update_identity');
214
- expect(opNames).toContain('add_guideline');
215
- expect(opNames).toContain('remove_guideline');
216
- expect(opNames).toContain('rollback_identity');
217
- expect(opNames).toContain('route_intent');
218
- expect(opNames).toContain('morph');
219
- expect(opNames).toContain('get_behavior_rules');
220
- // Cognee ops (5)
221
- expect(opNames).toContain('cognee_status');
222
- expect(opNames).toContain('cognee_search');
223
- expect(opNames).toContain('cognee_add');
224
- expect(opNames).toContain('cognee_cognify');
225
- expect(opNames).toContain('cognee_config');
226
- // LLM ops (2)
227
- expect(opNames).toContain('llm_rotate');
228
- expect(opNames).toContain('llm_call');
229
- // Governance ops (5)
230
- expect(opNames).toContain('governance_policy');
231
- expect(opNames).toContain('governance_proposals');
232
- expect(opNames).toContain('governance_stats');
233
- expect(opNames).toContain('governance_expire');
234
- expect(opNames).toContain('governance_dashboard');
235
- // Planning Extra ops (13)
236
- expect(opNames).toContain('plan_iterate');
237
- expect(opNames).toContain('plan_split');
238
- expect(opNames).toContain('plan_reconcile');
239
- expect(opNames).toContain('plan_complete_lifecycle');
240
- expect(opNames).toContain('plan_dispatch');
241
- expect(opNames).toContain('plan_review');
242
- expect(opNames).toContain('plan_archive');
243
- expect(opNames).toContain('plan_list_tasks');
244
- expect(opNames).toContain('plan_stats');
245
- expect(opNames).toContain('plan_execution_metrics');
246
- expect(opNames).toContain('plan_record_task_metrics');
247
- expect(opNames).toContain('plan_submit_deliverable');
248
- expect(opNames).toContain('plan_verify_deliverables');
249
- // Memory Extra ops (8)
250
- expect(opNames).toContain('memory_delete');
251
- expect(opNames).toContain('memory_stats');
252
- expect(opNames).toContain('memory_export');
253
- expect(opNames).toContain('memory_import');
254
- expect(opNames).toContain('memory_prune');
255
- expect(opNames).toContain('memory_deduplicate');
256
- expect(opNames).toContain('memory_topics');
257
- expect(opNames).toContain('memory_by_project');
258
- // Vault Extra ops (12)
259
- expect(opNames).toContain('vault_get');
260
- expect(opNames).toContain('vault_update');
261
- expect(opNames).toContain('vault_remove');
262
- expect(opNames).toContain('vault_bulk_add');
263
- expect(opNames).toContain('vault_bulk_remove');
264
- expect(opNames).toContain('vault_tags');
265
- expect(opNames).toContain('vault_domains');
266
- expect(opNames).toContain('vault_recent');
267
- expect(opNames).toContain('vault_import');
268
- expect(opNames).toContain('vault_seed');
269
- expect(opNames).toContain('vault_backup');
270
- expect(opNames).toContain('vault_age_report');
271
- // #89: Bi-temporal
272
- expect(opNames).toContain('vault_set_temporal');
273
- expect(opNames).toContain('vault_find_expiring');
274
- expect(opNames).toContain('vault_find_expired');
275
- // Admin ops (8)
276
- expect(opNames).toContain('admin_health');
277
- expect(opNames).toContain('admin_tool_list');
278
- expect(opNames).toContain('admin_config');
279
- expect(opNames).toContain('admin_vault_size');
280
- expect(opNames).toContain('admin_uptime');
281
- expect(opNames).toContain('admin_version');
282
- expect(opNames).toContain('admin_reset_cache');
283
- expect(opNames).toContain('admin_diagnostic');
284
- // Loop ops (8)
285
- expect(opNames).toContain('loop_start');
286
- expect(opNames).toContain('loop_iterate');
287
- expect(opNames).toContain('loop_status');
288
- expect(opNames).toContain('loop_cancel');
289
- expect(opNames).toContain('loop_history');
290
- expect(opNames).toContain('loop_is_active');
291
- expect(opNames).toContain('loop_complete');
292
- expect(opNames).toContain('loop_anomaly_check');
293
- // Orchestrate ops (5)
294
- expect(opNames).toContain('orchestrate_plan');
295
- expect(opNames).toContain('orchestrate_execute');
296
- expect(opNames).toContain('orchestrate_complete');
297
- expect(opNames).toContain('orchestrate_status');
298
- expect(opNames).toContain('orchestrate_quick_capture');
299
- // Capture ops (4)
300
- expect(opNames).toContain('capture_knowledge');
301
- expect(opNames).toContain('capture_quick');
302
- expect(opNames).toContain('search_intelligent');
303
- expect(opNames).toContain('search_feedback');
304
- // Grading ops (5)
305
- expect(opNames).toContain('plan_grade');
306
- expect(opNames).toContain('plan_check_history');
307
- expect(opNames).toContain('plan_latest_check');
308
- expect(opNames).toContain('plan_meets_grade');
309
- expect(opNames).toContain('plan_auto_improve');
310
- // Admin Extra ops (11)
311
- expect(opNames).toContain('admin_telemetry');
312
- expect(opNames).toContain('admin_telemetry_recent');
313
- expect(opNames).toContain('admin_telemetry_reset');
314
- expect(opNames).toContain('admin_permissions');
315
- expect(opNames).toContain('admin_vault_analytics');
316
- expect(opNames).toContain('admin_search_insights');
317
- expect(opNames).toContain('admin_module_status');
318
- expect(opNames).toContain('admin_env');
319
- expect(opNames).toContain('admin_gc');
320
- expect(opNames).toContain('admin_export_config');
321
- expect(opNames).toContain('admin_hot_reload');
322
- // Curator Extra ops (4 + 1 hybrid)
323
- expect(opNames).toContain('curator_entry_history');
324
- expect(opNames).toContain('curator_record_snapshot');
325
- expect(opNames).toContain('curator_queue_stats');
326
- expect(opNames).toContain('curator_enrich');
327
- // #36: Hybrid contradiction detection
328
- expect(opNames).toContain('curator_hybrid_contradictions');
329
- // Project ops (12)
330
- expect(opNames).toContain('project_get');
331
- expect(opNames).toContain('project_list');
332
- expect(opNames).toContain('project_unregister');
333
- expect(opNames).toContain('project_get_rules');
334
- expect(opNames).toContain('project_list_rules');
335
- expect(opNames).toContain('project_add_rule');
336
- expect(opNames).toContain('project_remove_rule');
337
- expect(opNames).toContain('project_link');
338
- expect(opNames).toContain('project_unlink');
339
- expect(opNames).toContain('project_get_links');
340
- expect(opNames).toContain('project_linked_projects');
341
- expect(opNames).toContain('project_touch');
342
- // Cross-project memory ops (3)
343
- expect(opNames).toContain('memory_promote_to_global');
344
- expect(opNames).toContain('memory_configure');
345
- expect(opNames).toContain('memory_cross_project_search');
346
- // Playbook ops (5)
347
- expect(opNames).toContain('playbook_list');
348
- expect(opNames).toContain('playbook_get');
349
- expect(opNames).toContain('playbook_create');
350
- expect(opNames).toContain('playbook_match');
351
- expect(opNames).toContain('playbook_seed');
352
- // Cognee Sync ops (3)
353
- expect(opNames).toContain('cognee_sync_status');
354
- expect(opNames).toContain('cognee_sync_drain');
355
- expect(opNames).toContain('cognee_sync_reconcile');
356
- // Intake ops (4)
357
- expect(opNames).toContain('intake_ingest_book');
358
- expect(opNames).toContain('intake_process');
359
- expect(opNames).toContain('intake_status');
360
- expect(opNames).toContain('intake_preview');
361
- // Total: 208 (203 core + 5 agent-specific)
362
- expect(facade.ops.length).toBe(208);
363
- });
364
-
365
- it('search should query across all domains with ranked results', async () => {
366
- runtime.vault.seed([
367
- makeEntry({ id: 'c1', domain: 'alpha', title: 'Alpha pattern', tags: ['a'] }),
368
- makeEntry({ id: 'c2', domain: 'beta', title: 'Beta pattern', tags: ['b'] }),
369
- ]);
370
- runtime = createAgentRuntime({ agentId: '${config.id}', vaultPath: ':memory:', plansPath: join(plannerDir, 'plans2.json') });
371
- runtime.vault.seed([
372
- makeEntry({ id: 'c1', domain: 'alpha', title: 'Alpha pattern', tags: ['a'] }),
373
- makeEntry({ id: 'c2', domain: 'beta', title: 'Beta pattern', tags: ['b'] }),
374
- ]);
375
- const facade = buildCoreFacade();
376
- const searchOp = facade.ops.find((o) => o.name === 'search')!;
377
- const results = (await searchOp.handler({ query: 'pattern' })) as Array<{ entry: unknown; score: number; breakdown: unknown }>;
378
- expect(Array.isArray(results)).toBe(true);
379
- expect(results.length).toBe(2);
380
- expect(results[0].score).toBeGreaterThan(0);
381
- });
382
-
383
- it('vault_stats should return counts', async () => {
384
- runtime.vault.seed([
385
- makeEntry({ id: 'vs1', domain: 'd1', tags: ['x'] }),
386
- makeEntry({ id: 'vs2', domain: 'd2', tags: ['y'] }),
387
- ]);
388
- const facade = buildCoreFacade();
389
- const statsOp = facade.ops.find((o) => o.name === 'vault_stats')!;
390
- const stats = (await statsOp.handler({})) as { totalEntries: number };
391
- expect(stats.totalEntries).toBe(2);
383
+ it('agent ops should not appear in semantic facades', () => {
384
+ const facades = createSemanticFacades(runtime, '${config.id}');
385
+ const allOps = facades.flatMap(f => f.ops.map(o => o.name));
386
+ expect(allOps).not.toContain('health');
387
+ expect(allOps).not.toContain('identity');
388
+ expect(allOps).not.toContain('activate');
389
+ expect(allOps).not.toContain('inject_claude_md');
390
+ expect(allOps).not.toContain('setup');
392
391
  });
393
392
 
394
393
  it('health should return ok status', async () => {
395
- const facade = buildCoreFacade();
394
+ const facade = buildAgentFacade();
396
395
  const healthOp = facade.ops.find((o) => o.name === 'health')!;
397
396
  const health = (await healthOp.handler({})) as { status: string };
398
397
  expect(health.status).toBe('ok');
399
398
  });
400
399
 
401
400
  it('identity should return persona', async () => {
402
- const facade = buildCoreFacade();
401
+ const facade = buildAgentFacade();
403
402
  const identityOp = facade.ops.find((o) => o.name === 'identity')!;
404
403
  const persona = (await identityOp.handler({})) as { name: string; role: string };
405
404
  expect(persona.name).toBe('${escapeQuotes(config.name)}');
@@ -407,7 +406,7 @@ ${domainDescribes}
407
406
  });
408
407
 
409
408
  it('activate should return persona and setup status', async () => {
410
- const facade = buildCoreFacade();
409
+ const facade = buildAgentFacade();
411
410
  const activateOp = facade.ops.find((o) => o.name === 'activate')!;
412
411
  const result = (await activateOp.handler({ projectPath: '/tmp/nonexistent-test' })) as {
413
412
  activated: boolean;
@@ -418,7 +417,7 @@ ${domainDescribes}
418
417
  });
419
418
 
420
419
  it('activate with deactivate flag should return deactivation', async () => {
421
- const facade = buildCoreFacade();
420
+ const facade = buildAgentFacade();
422
421
  const activateOp = facade.ops.find((o) => o.name === 'activate')!;
423
422
  const result = (await activateOp.handler({ deactivate: true })) as { deactivated: boolean; message: string };
424
423
  expect(result.deactivated).toBe(true);
@@ -429,7 +428,7 @@ ${domainDescribes}
429
428
  const tempDir = join(tmpdir(), 'forge-inject-test-' + Date.now());
430
429
  mkdirSync(tempDir, { recursive: true });
431
430
  try {
432
- const facade = buildCoreFacade();
431
+ const facade = buildAgentFacade();
433
432
  const injectOp = facade.ops.find((o) => o.name === 'inject_claude_md')!;
434
433
  const result = (await injectOp.handler({ projectPath: tempDir })) as {
435
434
  injected: boolean;
@@ -447,7 +446,7 @@ ${domainDescribes}
447
446
  });
448
447
 
449
448
  it('setup should return project and global CLAUDE.md status', async () => {
450
- const facade = buildCoreFacade();
449
+ const facade = buildAgentFacade();
451
450
  const setupOp = facade.ops.find((o) => o.name === 'setup')!;
452
451
  const result = (await setupOp.handler({ projectPath: '/tmp/nonexistent-test' })) as {
453
452
  agent: { name: string };
@@ -460,75 +459,6 @@ ${domainDescribes}
460
459
  expect(result.vault.entries).toBe(0);
461
460
  expect(result.recommendations.length).toBeGreaterThan(0);
462
461
  });
463
-
464
- it('create_plan should create a draft plan', async () => {
465
- const facade = buildCoreFacade();
466
- const createOp = facade.ops.find((o) => o.name === 'create_plan')!;
467
- const result = (await createOp.handler({
468
- objective: 'Add caching',
469
- scope: 'api layer',
470
- tasks: [{ title: 'Add Redis', description: 'Set up Redis client' }],
471
- })) as { created: boolean; plan: { id: string; status: string; tasks: unknown[] } };
472
- expect(result.created).toBe(true);
473
- expect(result.plan.status).toBe('draft');
474
- expect(result.plan.tasks).toHaveLength(1);
475
- });
476
-
477
- it('brain_stats should return intelligence stats', async () => {
478
- const facade = buildCoreFacade();
479
- const statsOp = facade.ops.find((o) => o.name === 'brain_stats')!;
480
- const result = (await statsOp.handler({})) as {
481
- vocabularySize: number;
482
- feedbackCount: number;
483
- };
484
- expect(result.vocabularySize).toBe(0);
485
- expect(result.feedbackCount).toBe(0);
486
- });
487
-
488
- it('curator_status should return table counts', async () => {
489
- const facade = buildCoreFacade();
490
- const statusOp = facade.ops.find((o) => o.name === 'curator_status')!;
491
- const result = (await statusOp.handler({})) as { initialized: boolean };
492
- expect(result.initialized).toBe(true);
493
- });
494
-
495
- it('curator_health_audit should return score', async () => {
496
- runtime.vault.seed([
497
- makeEntry({ id: 'ha1', type: 'pattern', tags: ['a', 'b'] }),
498
- makeEntry({ id: 'ha2', type: 'anti-pattern', tags: ['c', 'd'] }),
499
- ]);
500
- runtime.curator.groomAll();
501
- const facade = buildCoreFacade();
502
- const healthOp = facade.ops.find((o) => o.name === 'curator_health_audit')!;
503
- const result = (await healthOp.handler({})) as { score: number };
504
- expect(result.score).toBeGreaterThan(0);
505
- });
506
-
507
- it('governance_policy get should return default policy', async () => {
508
- const facade = buildCoreFacade();
509
- const policyOp = facade.ops.find((o) => o.name === 'governance_policy')!;
510
- const result = (await policyOp.handler({ action: 'get', projectPath: '/test' })) as {
511
- projectPath: string;
512
- quotas: { maxEntriesTotal: number };
513
- autoCapture: { enabled: boolean };
514
- };
515
- expect(result.projectPath).toBe('/test');
516
- expect(result.quotas.maxEntriesTotal).toBe(500);
517
- expect(result.autoCapture.enabled).toBe(true);
518
- });
519
-
520
- it('governance_dashboard should return combined view', async () => {
521
- const facade = buildCoreFacade();
522
- const dashOp = facade.ops.find((o) => o.name === 'governance_dashboard')!;
523
- const result = (await dashOp.handler({ projectPath: '/test' })) as {
524
- vaultSize: number;
525
- quotaPercent: number;
526
- pendingProposals: number;
527
- };
528
- expect(typeof result.vaultSize).toBe('number');
529
- expect(typeof result.quotaPercent).toBe('number');
530
- expect(result.pendingProposals).toBe(0);
531
- });
532
462
  });
533
463
  });
534
464
  `;
package/src/types.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ /** Communication tone for the agent persona */
4
+ export const TONES = ['precise', 'mentor', 'pragmatic'] as const;
5
+ export type Tone = (typeof TONES)[number];
6
+
3
7
  /** Agent configuration — everything needed to scaffold */
4
8
  export const AgentConfigSchema = z.object({
5
9
  /** Agent identifier (kebab-case, used for directory and package name) */
@@ -14,12 +18,16 @@ export const AgentConfigSchema = z.object({
14
18
  domains: z.array(z.string().min(1)).min(1).max(20),
15
19
  /** Core principles the agent follows (3-7 recommended) */
16
20
  principles: z.array(z.string()).min(1).max(10),
21
+ /** Communication tone: precise, mentor, or pragmatic */
22
+ tone: z.enum(TONES).optional().default('pragmatic'),
17
23
  /** Greeting message when agent introduces itself (auto-generated if omitted) */
18
24
  greeting: z.string().min(10).max(300).optional(),
19
25
  /** Output directory (parent — agent dir will be created inside, defaults to cwd) */
20
26
  outputDir: z.string().min(1).optional().default(process.cwd()),
21
27
  /** Hook packs to install after scaffolding (optional) */
22
28
  hookPacks: z.array(z.string()).optional(),
29
+ /** Skills to include (if omitted, all skills are included for backward compat) */
30
+ skills: z.array(z.string()).optional(),
23
31
  });
24
32
 
25
33
  export type AgentConfig = z.infer<typeof AgentConfigSchema>;