@soleri/forge 8.0.0 → 9.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.
Files changed (39) hide show
  1. package/dist/agent-schema.d.ts +16 -18
  2. package/dist/agent-schema.js +6 -6
  3. package/dist/agent-schema.js.map +1 -1
  4. package/dist/compose-claude-md.js +10 -8
  5. package/dist/compose-claude-md.js.map +1 -1
  6. package/dist/facades/forge.facade.js +20 -4
  7. package/dist/facades/forge.facade.js.map +1 -1
  8. package/dist/scaffold-filetree.js +80 -9
  9. package/dist/scaffold-filetree.js.map +1 -1
  10. package/dist/scaffolder.js +0 -4
  11. package/dist/scaffolder.js.map +1 -1
  12. package/dist/skills/agent-guide.md +1 -1
  13. package/dist/templates/agents-md.js +0 -1
  14. package/dist/templates/agents-md.js.map +1 -1
  15. package/dist/templates/claude-md-template.js +2 -2
  16. package/dist/templates/claude-md-template.js.map +1 -1
  17. package/dist/templates/entry-point.js +3 -3
  18. package/dist/templates/entry-point.js.map +1 -1
  19. package/dist/templates/shared-rules.js +19 -0
  20. package/dist/templates/shared-rules.js.map +1 -1
  21. package/dist/templates/test-facades.js +0 -11
  22. package/dist/templates/test-facades.js.map +1 -1
  23. package/dist/types.d.ts +20 -20
  24. package/dist/types.js +6 -6
  25. package/dist/types.js.map +1 -1
  26. package/package.json +2 -1
  27. package/src/__tests__/scaffold-filetree.test.ts +0 -2
  28. package/src/agent-schema.ts +6 -6
  29. package/src/compose-claude-md.ts +10 -18
  30. package/src/facades/forge.facade.ts +21 -4
  31. package/src/scaffold-filetree.ts +83 -8
  32. package/src/scaffolder.ts +0 -4
  33. package/src/skills/agent-guide.md +1 -1
  34. package/src/templates/agents-md.ts +0 -1
  35. package/src/templates/claude-md-template.ts +1 -2
  36. package/src/templates/entry-point.ts +3 -3
  37. package/src/templates/shared-rules.ts +20 -0
  38. package/src/templates/test-facades.ts +0 -11
  39. package/src/types.ts +6 -6
@@ -7,8 +7,9 @@
7
7
  * Replaces the old scaffold() that generated TypeScript projects.
8
8
  */
9
9
 
10
- import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
11
- import { join } from 'node:path';
10
+ import { mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync } from 'node:fs';
11
+ import { join, dirname } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
12
13
  import { stringify as yamlStringify } from 'yaml';
13
14
  import type { AgentYaml, AgentYamlInput } from './agent-schema.js';
14
15
  import { AgentYamlSchema } from './agent-schema.js';
@@ -93,8 +94,8 @@ When building a new feature, adding functionality, or creating components.
93
94
  - soleri_vault op:search_intelligent
94
95
  - soleri_vault op:capture_knowledge
95
96
  - soleri_vault op:link_entries
96
- - soleri_planner op:create_plan
97
- - soleri_planner op:approve_plan
97
+ - soleri_plan op:create_plan
98
+ - soleri_plan op:approve_plan
98
99
  - soleri_brain op:recommend
99
100
  `,
100
101
  },
@@ -146,7 +147,7 @@ When fixing bugs, resolving errors, or addressing regressions.
146
147
  tools: `tools:
147
148
  - soleri_vault op:search_intelligent
148
149
  - soleri_vault op:capture_knowledge
149
- - soleri_planner op:create_plan
150
+ - soleri_plan op:create_plan
150
151
  - soleri_brain op:recommend
151
152
  `,
152
153
  },
@@ -307,12 +308,16 @@ export function scaffoldFileTree(input: AgentYamlInput, outputDir: string): File
307
308
  writeFile(agentDir, `workflows/${wf.name}/tools.yaml`, wf.tools, filesCreated);
308
309
  }
309
310
 
310
- // ─── 8. Write empty knowledge bundle ────────────────────────
311
+ // ─── 8. Write knowledge bundles (seed from starter packs if available) ──
312
+ const starterPacksDir = resolveStarterPacksDir();
313
+ let totalSeeded = 0;
314
+
311
315
  for (const domain of config.domains) {
316
+ const starterEntries = loadStarterEntries(starterPacksDir, domain);
312
317
  const bundle = {
313
318
  domain,
314
319
  version: '1.0.0',
315
- entries: [],
320
+ entries: starterEntries,
316
321
  };
317
322
  writeFile(
318
323
  agentDir,
@@ -320,6 +325,7 @@ export function scaffoldFileTree(input: AgentYamlInput, outputDir: string): File
320
325
  JSON.stringify(bundle, null, 2) + '\n',
321
326
  filesCreated,
322
327
  );
328
+ totalSeeded += starterEntries.length;
323
329
  }
324
330
 
325
331
  // ─── 9. Generate CLAUDE.md ──────────────────────────────────
@@ -332,6 +338,7 @@ export function scaffoldFileTree(input: AgentYamlInput, outputDir: string): File
332
338
  '',
333
339
  ` Files: ${filesCreated.length}`,
334
340
  ` Domains: ${config.domains.join(', ')}`,
341
+ ` Knowledge: ${totalSeeded} starter entries seeded`,
335
342
  ` Workflows: ${BUILTIN_WORKFLOWS.map((w) => w.name).join(', ')}`,
336
343
  '',
337
344
  'Next steps:',
@@ -393,11 +400,15 @@ function buildAgentYaml(config: AgentYaml): Record<string, unknown> {
393
400
  yaml.greeting = config.greeting;
394
401
  }
395
402
 
403
+ // Persona config — include if present
404
+ if (config.persona && Object.keys(config.persona).length > 0) {
405
+ yaml.persona = config.persona;
406
+ }
407
+
396
408
  // Engine config — only include non-defaults
397
409
  const engine: Record<string, unknown> = {};
398
410
  if (config.engine?.vault) engine.vault = config.engine.vault;
399
411
  if (config.engine?.learning === false) engine.learning = false;
400
- if (config.engine?.cognee === true) engine.cognee = true;
401
412
  if (Object.keys(engine).length > 0) yaml.engine = engine;
402
413
 
403
414
  // Vaults
@@ -419,3 +430,67 @@ function buildAgentYaml(config: AgentYaml): Record<string, unknown> {
419
430
 
420
431
  return yaml;
421
432
  }
433
+
434
+ // ─── Starter Pack Helpers ────────────────────────────────────────────
435
+
436
+ /** Domain aliases — map agent domains to starter pack directories. */
437
+ const DOMAIN_TO_STARTER: Record<string, string> = {
438
+ // design starter
439
+ frontend: 'design',
440
+ design: 'design',
441
+ 'ui-design': 'design',
442
+ accessibility: 'design',
443
+ styling: 'design',
444
+ react: 'design',
445
+ 'component-patterns': 'design',
446
+ 'responsive-design': 'design',
447
+ // security starter
448
+ security: 'security',
449
+ auth: 'security',
450
+ authentication: 'security',
451
+ // architecture starter
452
+ architecture: 'architecture',
453
+ 'api-design': 'architecture',
454
+ database: 'architecture',
455
+ backend: 'architecture',
456
+ infrastructure: 'architecture',
457
+ };
458
+
459
+ function resolveStarterPacksDir(): string | null {
460
+ // Try repo-relative path (monorepo development)
461
+ const forgeDir = dirname(fileURLToPath(import.meta.url));
462
+ const candidates = [
463
+ join(forgeDir, '..', '..', '..', 'knowledge-packs', 'starter'),
464
+ join(forgeDir, '..', '..', 'knowledge-packs', 'starter'),
465
+ ];
466
+ for (const dir of candidates) {
467
+ if (existsSync(dir)) return dir;
468
+ }
469
+ return null;
470
+ }
471
+
472
+ function loadStarterEntries(starterDir: string | null, domain: string): unknown[] {
473
+ if (!starterDir) return [];
474
+
475
+ const packName = DOMAIN_TO_STARTER[domain];
476
+ if (!packName) return [];
477
+
478
+ const vaultDir = join(starterDir, packName, 'vault');
479
+ if (!existsSync(vaultDir)) return [];
480
+
481
+ const entries: unknown[] = [];
482
+ try {
483
+ const files = readdirSync(vaultDir).filter((f: string) => f.endsWith('.json'));
484
+ for (const file of files) {
485
+ const data = JSON.parse(readFileSync(join(vaultDir, file), 'utf-8'));
486
+ if (Array.isArray(data)) {
487
+ entries.push(...data);
488
+ } else if (data.entries && Array.isArray(data.entries)) {
489
+ entries.push(...data.entries);
490
+ }
491
+ }
492
+ } catch {
493
+ // Starter pack unavailable — return empty
494
+ }
495
+ return entries;
496
+ }
package/src/scaffolder.ts CHANGED
@@ -257,10 +257,6 @@ export function previewScaffold(config: AgentConfig): ScaffoldPreview {
257
257
  name: `${config.id}_control`,
258
258
  ops: ['get_identity', 'route_intent', 'governance_policy', '...control+governance ops'],
259
259
  },
260
- {
261
- name: `${config.id}_cognee`,
262
- ops: ['cognee_status', 'cognee_search', '...cognee ops', '...cognee-sync'],
263
- },
264
260
  // Agent-specific facade
265
261
  {
266
262
  name: `${config.id}_core`,
@@ -38,7 +38,7 @@ This returns the agent's persona: name, role, description, tone, principles, and
38
38
  YOUR_AGENT_core op:admin_health
39
39
  ```
40
40
 
41
- Shows what subsystems are active: vault (how many entries), brain (vocabulary size), LLM availability, cognee status. This tells the user what the agent currently has to work with.
41
+ Shows what subsystems are active: vault (how many entries), brain (vocabulary size), LLM availability. This tells the user what the agent currently has to work with.
42
42
 
43
43
  ### Step 3: Available Tools
44
44
 
@@ -80,7 +80,6 @@ ${domainRows}
80
80
  | ${bt}${tp}_memory${bt} | ${bt}memory_search${bt}, ${bt}memory_capture${bt}, ${bt}session_capture${bt} |
81
81
  | ${bt}${tp}_control${bt} | ${bt}route_intent${bt}, ${bt}morph${bt}, ${bt}get_behavior_rules${bt}, ${bt}governance_dashboard${bt}, ${bt}governance_policy${bt} |
82
82
  | ${bt}${tp}_loop${bt} | ${bt}loop_start${bt}, ${bt}loop_iterate${bt}, ${bt}loop_status${bt}, ${bt}loop_cancel${bt} |
83
- | ${bt}${tp}_cognee${bt} | ${bt}cognee_search${bt}, ${bt}cognee_graph_stats${bt}, ${bt}cognee_export_status${bt} |
84
83
  | ${bt}${tp}_context${bt} | ${bt}context_extract_entities${bt}, ${bt}context_retrieve_knowledge${bt}, ${bt}context_analyze${bt} |
85
84
  | ${bt}${tp}_agency${bt} | ${bt}agency_enable${bt}, ${bt}agency_status${bt}, ${bt}agency_surface_patterns${bt}, ${bt}agency_warnings${bt}, ${bt}agency_clarify${bt} |
86
85
  | ${bt}${tp}_admin${bt} | ${bt}admin_health${bt}, ${bt}admin_tool_list${bt}, ${bt}admin_diagnostic${bt} |
@@ -115,8 +115,7 @@ export function generateClaudeMdTemplate(config: AgentConfig): string {
115
115
  `| ${bt}${toolPrefix}_memory${bt} | ${bt}memory_search${bt}, ${bt}memory_capture${bt}, ${bt}session_capture${bt} |`,
116
116
  `| ${bt}${toolPrefix}_control${bt} | ${bt}route_intent${bt}, ${bt}morph${bt}, ${bt}get_behavior_rules${bt}, ${bt}governance_dashboard${bt}, ${bt}governance_policy${bt} |`,
117
117
  `| ${bt}${toolPrefix}_loop${bt} | ${bt}loop_start${bt}, ${bt}loop_iterate${bt}, ${bt}loop_status${bt}, ${bt}loop_cancel${bt} |`,
118
- // Intelligence — cognee, context, agency
119
- `| ${bt}${toolPrefix}_cognee${bt} | ${bt}cognee_search${bt}, ${bt}cognee_graph_stats${bt}, ${bt}cognee_export_status${bt} |`,
118
+ // Intelligence — context, agency
120
119
  `| ${bt}${toolPrefix}_context${bt} | ${bt}context_extract_entities${bt}, ${bt}context_retrieve_knowledge${bt}, ${bt}context_analyze${bt} |`,
121
120
  `| ${bt}${toolPrefix}_agency${bt} | ${bt}agency_enable${bt}, ${bt}agency_status${bt}, ${bt}agency_surface_patterns${bt}, ${bt}agency_warnings${bt}, ${bt}agency_clarify${bt} |`,
122
121
  // Admin
@@ -56,7 +56,7 @@ async function main(): Promise<void> {
56
56
  // ─── Runtime — vault, brain, planner, curator, LLM, key pools ───
57
57
  const runtime = createAgentRuntime({
58
58
  agentId: '${config.id}',
59
- dataDir: join(__dirname, 'intelligence', 'data'),${config.sharedVaultPath ? `\n sharedVaultPath: '${config.sharedVaultPath}',` : ''}${config.cognee ? `\n cognee: true,` : ''}
59
+ dataDir: join(__dirname, 'intelligence', 'data'),${config.sharedVaultPath ? `\n sharedVaultPath: '${config.sharedVaultPath}',` : ''}
60
60
  });
61
61
 
62
62
  const tag = PERSONA.name.toLowerCase();
@@ -263,12 +263,12 @@ ${
263
263
  const domainPacks = await loadDomainPacksFromConfig(${JSON.stringify(config.domainPacks)});
264
264
  console.error(\`[\${tag}] Loaded \${domainPacks.length} domain packs\`);
265
265
  for (const pack of domainPacks) {
266
- if (pack.onActivate) await pack.onActivate(runtime);
266
+ const packRuntime = createPackRuntime(runtime);
267
+ if (pack.onActivate) await pack.onActivate(packRuntime, runtime);
267
268
  }
268
269
 
269
270
  // ─── Capability Registry ─────────────────────────────────────
270
271
  const capabilityRegistry = new CapabilityRegistry();
271
- const packRuntime = createPackRuntime(runtime);
272
272
 
273
273
  // Register domain pack capabilities
274
274
  for (const pack of domainPacks) {
@@ -69,6 +69,26 @@ const ENGINE_RULES_LINES: string[] = [
69
69
  '- After every response, rate your confidence from 1 to 10. Anything below 7, flag it.',
70
70
  '',
71
71
 
72
+ // ─── MCP Tool Schema Validation ─────────────────────────
73
+ '## MCP Tool Schema Validation',
74
+ '<!-- soleri:tool-schema-validation -->',
75
+ '',
76
+ '**MANDATORY**: Before calling any MCP tool for the first time in a session, fetch its full JSON schema first.',
77
+ '',
78
+ '- Use `ToolSearch` (or platform equivalent) to retrieve the tool definition before invoking it.',
79
+ '- Read required fields, types, enum constraints, and nesting structure.',
80
+ '- Do NOT guess parameter shapes from memory or training data — schemas evolve between versions.',
81
+ '- Once fetched, the schema is valid for the remainder of the session.',
82
+ '',
83
+ '**Why:** MCP tools have strict parameter validation. Guessing formats causes repeated failures (wrong nesting, invalid enums, missing required fields), wasting tokens and eroding user trust. The schema is always available — use it.',
84
+ '',
85
+ '| Wrong | Right |',
86
+ '|-------|-------|',
87
+ '| Call tool, fail, retry with different shape | ToolSearch first, call once correctly |',
88
+ '| Assume `severity: "suggestion"` is valid | Read schema: `"critical" \\| "warning" \\| "info"` |',
89
+ '| Pass flat params when tool expects `entries[]` | Read schema: `entries` is required array |',
90
+ '',
91
+
72
92
  // ─── Memory Quality Gate ───────────────────────────────
73
93
  '## Memory Quality Gate',
74
94
  '<!-- soleri:memory-quality -->',
@@ -86,7 +86,6 @@ ${domainPackDescribes ? `\n${domainPackDescribes}\n` : ''}
86
86
  expect(names).toContain('${config.id}_loop');
87
87
  expect(names).toContain('${config.id}_orchestrate');
88
88
  expect(names).toContain('${config.id}_control');
89
- expect(names).toContain('${config.id}_cognee');
90
89
  });
91
90
 
92
91
  it('total ops across all facades should meet minimum threshold', () => {
@@ -273,16 +272,6 @@ ${domainPackDescribes ? `\n${domainPackDescribes}\n` : ''}
273
272
  });
274
273
  });
275
274
 
276
- describe('${config.id}_cognee', () => {
277
- it('should contain cognee ops', () => {
278
- const facade = createSemanticFacades(runtime, '${config.id}').find(f => f.name === '${config.id}_cognee')!;
279
- const opNames = facade.ops.map(o => o.name);
280
- expect(opNames).toContain('cognee_status');
281
- expect(opNames).toContain('cognee_search');
282
- expect(opNames).toContain('cognee_sync_status');
283
- });
284
- });
285
-
286
275
  describe('${config.id}_core (agent-specific)', () => {
287
276
  function buildAgentFacade(): FacadeConfig {
288
277
  const agentOps: OpDefinition[] = [
package/src/types.ts CHANGED
@@ -31,10 +31,10 @@ export const AgentConfigSchema = z.object({
31
31
  role: z.string().min(1).max(100),
32
32
  /** Longer description of what this agent does */
33
33
  description: z.string().min(10).max(500),
34
- /** Knowledge domains this agent covers */
35
- domains: z.array(z.string().min(1)).min(1).max(20),
36
- /** Core principles the agent follows (3-7 recommended) */
37
- principles: z.array(z.string()).min(1).max(10),
34
+ /** Knowledge domains this agent covers (discovered from usage if empty) */
35
+ domains: z.array(z.string().min(1)).max(20).optional().default([]),
36
+ /** Core principles the agent follows (discovered from usage if empty) */
37
+ principles: z.array(z.string()).max(10).optional().default([]),
38
38
  /** Communication tone: precise, mentor, or pragmatic */
39
39
  tone: z.enum(TONES).optional().default('pragmatic'),
40
40
  /** Greeting message when agent introduces itself (auto-generated if omitted) */
@@ -51,8 +51,6 @@ export const AgentConfigSchema = z.object({
51
51
  setupTarget: z.enum(SETUP_TARGETS).optional().default('claude'),
52
52
  /** Enable Telegram transport scaffolding. Default: false. */
53
53
  telegram: z.boolean().optional().default(false),
54
- /** Enable Cognee vector search integration. Default: false. */
55
- cognee: z.boolean().optional().default(false),
56
54
  /** Domain packs — npm packages with custom ops, knowledge, rules, and skills. */
57
55
  domainPacks: z
58
56
  .array(
@@ -78,6 +76,8 @@ export const AgentConfigSchema = z.object({
78
76
  .optional(),
79
77
  /** @deprecated Use vaults[] instead. Shorthand for a single shared vault at priority 0.6. */
80
78
  sharedVaultPath: z.string().optional(),
79
+ /** Composable persona configuration. If omitted, Italian Craftsperson default is used. */
80
+ persona: z.record(z.unknown()).optional(),
81
81
  });
82
82
 
83
83
  export type AgentConfig = z.infer<typeof AgentConfigSchema>;