@soleri/forge 5.14.10 → 8.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 (65) hide show
  1. package/dist/agent-schema.d.ts +323 -0
  2. package/dist/agent-schema.js +151 -0
  3. package/dist/agent-schema.js.map +1 -0
  4. package/dist/compose-claude-md.d.ts +24 -0
  5. package/dist/compose-claude-md.js +197 -0
  6. package/dist/compose-claude-md.js.map +1 -0
  7. package/dist/index.js +0 -0
  8. package/dist/lib.d.ts +12 -1
  9. package/dist/lib.js +10 -1
  10. package/dist/lib.js.map +1 -1
  11. package/dist/scaffold-filetree.d.ts +22 -0
  12. package/dist/scaffold-filetree.js +361 -0
  13. package/dist/scaffold-filetree.js.map +1 -0
  14. package/dist/scaffolder.js +261 -11
  15. package/dist/scaffolder.js.map +1 -1
  16. package/dist/templates/activate.js +39 -1
  17. package/dist/templates/activate.js.map +1 -1
  18. package/dist/templates/agents-md.d.ts +10 -1
  19. package/dist/templates/agents-md.js +76 -16
  20. package/dist/templates/agents-md.js.map +1 -1
  21. package/dist/templates/claude-md-template.js +9 -1
  22. package/dist/templates/claude-md-template.js.map +1 -1
  23. package/dist/templates/entry-point.js +83 -6
  24. package/dist/templates/entry-point.js.map +1 -1
  25. package/dist/templates/inject-claude-md.js +53 -0
  26. package/dist/templates/inject-claude-md.js.map +1 -1
  27. package/dist/templates/package-json.js +4 -1
  28. package/dist/templates/package-json.js.map +1 -1
  29. package/dist/templates/readme.js +4 -3
  30. package/dist/templates/readme.js.map +1 -1
  31. package/dist/templates/setup-script.js +109 -3
  32. package/dist/templates/setup-script.js.map +1 -1
  33. package/dist/templates/shared-rules.js +54 -17
  34. package/dist/templates/shared-rules.js.map +1 -1
  35. package/dist/templates/test-facades.js +151 -6
  36. package/dist/templates/test-facades.js.map +1 -1
  37. package/dist/types.d.ts +71 -6
  38. package/dist/types.js +39 -1
  39. package/dist/types.js.map +1 -1
  40. package/dist/utils/detect-domain-packs.d.ts +25 -0
  41. package/dist/utils/detect-domain-packs.js +104 -0
  42. package/dist/utils/detect-domain-packs.js.map +1 -0
  43. package/package.json +2 -1
  44. package/src/__tests__/detect-domain-packs.test.ts +178 -0
  45. package/src/__tests__/scaffold-filetree.test.ts +257 -0
  46. package/src/__tests__/scaffolder.test.ts +5 -3
  47. package/src/agent-schema.ts +184 -0
  48. package/src/compose-claude-md.ts +252 -0
  49. package/src/lib.ts +14 -1
  50. package/src/scaffold-filetree.ts +421 -0
  51. package/src/scaffolder.ts +299 -15
  52. package/src/templates/activate.ts +39 -0
  53. package/src/templates/agents-md.ts +78 -16
  54. package/src/templates/claude-md-template.ts +12 -1
  55. package/src/templates/entry-point.ts +90 -6
  56. package/src/templates/inject-claude-md.ts +53 -0
  57. package/src/templates/package-json.ts +4 -1
  58. package/src/templates/readme.ts +4 -3
  59. package/src/templates/setup-script.ts +110 -4
  60. package/src/templates/shared-rules.ts +55 -17
  61. package/src/templates/test-facades.ts +156 -6
  62. package/src/types.ts +44 -1
  63. package/src/utils/detect-domain-packs.ts +129 -0
  64. package/tsconfig.json +0 -1
  65. package/vitest.config.ts +1 -2
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Tests for the file-tree scaffolder.
3
+ *
4
+ * Validates that scaffoldFileTree() produces a valid agent folder
5
+ * with all expected files and no TypeScript output.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
9
+ import { mkdirSync, rmSync, existsSync, readFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { tmpdir } from 'node:os';
12
+ import { parse as parseYaml } from 'yaml';
13
+ import { scaffoldFileTree } from '../scaffold-filetree.js';
14
+
15
+ let tempDir: string;
16
+
17
+ beforeEach(() => {
18
+ tempDir = join(tmpdir(), `soleri-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
19
+ mkdirSync(tempDir, { recursive: true });
20
+ });
21
+
22
+ afterEach(() => {
23
+ rmSync(tempDir, { recursive: true, force: true });
24
+ });
25
+
26
+ const MINIMAL_CONFIG = {
27
+ id: 'test-agent',
28
+ name: 'Test Agent',
29
+ role: 'Testing Advisor',
30
+ description: 'A test agent for validating the file-tree scaffolder output.',
31
+ domains: ['testing', 'quality'],
32
+ principles: ['Test everything', 'Fail fast'],
33
+ };
34
+
35
+ describe('scaffoldFileTree', () => {
36
+ it('creates agent directory with all expected files', () => {
37
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
38
+
39
+ expect(result.success).toBe(true);
40
+ expect(result.agentDir).toBe(join(tempDir, 'test-agent'));
41
+ expect(result.filesCreated.length).toBeGreaterThan(10);
42
+
43
+ // Core files exist
44
+ expect(existsSync(join(result.agentDir, 'agent.yaml'))).toBe(true);
45
+ expect(existsSync(join(result.agentDir, '.mcp.json'))).toBe(true);
46
+ expect(existsSync(join(result.agentDir, 'opencode.json'))).toBe(true);
47
+ expect(existsSync(join(result.agentDir, '.gitignore'))).toBe(true);
48
+ expect(existsSync(join(result.agentDir, 'CLAUDE.md'))).toBe(true);
49
+
50
+ // Directories exist
51
+ expect(existsSync(join(result.agentDir, 'instructions'))).toBe(true);
52
+ expect(existsSync(join(result.agentDir, 'workflows'))).toBe(true);
53
+ expect(existsSync(join(result.agentDir, 'knowledge'))).toBe(true);
54
+ expect(existsSync(join(result.agentDir, 'skills'))).toBe(true);
55
+ expect(existsSync(join(result.agentDir, 'hooks'))).toBe(true);
56
+ expect(existsSync(join(result.agentDir, 'data'))).toBe(true);
57
+ });
58
+
59
+ it('generates NO TypeScript files', () => {
60
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
61
+ expect(result.success).toBe(true);
62
+
63
+ // No .ts files anywhere
64
+ const tsFiles = result.filesCreated.filter((f) => f.endsWith('.ts'));
65
+ expect(tsFiles).toEqual([]);
66
+
67
+ // No package.json
68
+ expect(existsSync(join(result.agentDir, 'package.json'))).toBe(false);
69
+
70
+ // No tsconfig.json
71
+ expect(existsSync(join(result.agentDir, 'tsconfig.json'))).toBe(false);
72
+
73
+ // No src/ directory
74
+ expect(existsSync(join(result.agentDir, 'src'))).toBe(false);
75
+
76
+ // No node_modules
77
+ expect(existsSync(join(result.agentDir, 'node_modules'))).toBe(false);
78
+ });
79
+
80
+ it('generates valid agent.yaml', () => {
81
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
82
+ expect(result.success).toBe(true);
83
+
84
+ const content = readFileSync(join(result.agentDir, 'agent.yaml'), 'utf-8');
85
+ const parsed = parseYaml(content);
86
+
87
+ expect(parsed.id).toBe('test-agent');
88
+ expect(parsed.name).toBe('Test Agent');
89
+ expect(parsed.role).toBe('Testing Advisor');
90
+ expect(parsed.domains).toEqual(['testing', 'quality']);
91
+ expect(parsed.principles).toEqual(['Test everything', 'Fail fast']);
92
+ });
93
+
94
+ it('generates valid .mcp.json pointing to soleri-engine', () => {
95
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
96
+ expect(result.success).toBe(true);
97
+
98
+ const content = readFileSync(join(result.agentDir, '.mcp.json'), 'utf-8');
99
+ const parsed = JSON.parse(content);
100
+
101
+ expect(parsed.mcpServers['soleri-engine']).toBeDefined();
102
+ expect(parsed.mcpServers['soleri-engine'].command).toBe('npx');
103
+ expect(parsed.mcpServers['soleri-engine'].args).toContain('@soleri/engine');
104
+ expect(parsed.mcpServers['soleri-engine'].args).toContain('./agent.yaml');
105
+ });
106
+
107
+ it('generates valid opencode.json for OpenCode', () => {
108
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
109
+ expect(result.success).toBe(true);
110
+
111
+ const content = readFileSync(join(result.agentDir, 'opencode.json'), 'utf-8');
112
+ const parsed = JSON.parse(content);
113
+
114
+ expect(parsed.$schema).toBe('https://opencode.ai/config.json');
115
+ expect(parsed.mcp['soleri-engine']).toBeDefined();
116
+ expect(parsed.mcp['soleri-engine'].type).toBe('local');
117
+ expect(parsed.instructions).toContain('CLAUDE.md');
118
+ });
119
+
120
+ it('generates engine rules in instructions/_engine.md', () => {
121
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
122
+ expect(result.success).toBe(true);
123
+
124
+ const content = readFileSync(join(result.agentDir, 'instructions', '_engine.md'), 'utf-8');
125
+ expect(content).toContain('soleri:engine-rules');
126
+ expect(content).toContain('Vault as Source of Truth');
127
+ expect(content).toContain('Planning');
128
+ expect(content).toContain('Clean Commits');
129
+ });
130
+
131
+ it('generates domain instruction file', () => {
132
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
133
+ expect(result.success).toBe(true);
134
+
135
+ const content = readFileSync(join(result.agentDir, 'instructions', 'domain.md'), 'utf-8');
136
+ expect(content).toContain('testing, quality');
137
+ expect(content).toContain('Test everything');
138
+ });
139
+
140
+ it('generates workflow folders with prompt, gates, and tools', () => {
141
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
142
+ expect(result.success).toBe(true);
143
+
144
+ // Check feature-dev workflow
145
+ const featureDevDir = join(result.agentDir, 'workflows', 'feature-dev');
146
+ expect(existsSync(join(featureDevDir, 'prompt.md'))).toBe(true);
147
+ expect(existsSync(join(featureDevDir, 'gates.yaml'))).toBe(true);
148
+ expect(existsSync(join(featureDevDir, 'tools.yaml'))).toBe(true);
149
+
150
+ const prompt = readFileSync(join(featureDevDir, 'prompt.md'), 'utf-8');
151
+ expect(prompt).toContain('Feature Development');
152
+ expect(prompt).toContain('op:search_intelligent');
153
+
154
+ // Check bug-fix workflow
155
+ expect(existsSync(join(result.agentDir, 'workflows', 'bug-fix', 'prompt.md'))).toBe(true);
156
+
157
+ // Check code-review workflow
158
+ expect(existsSync(join(result.agentDir, 'workflows', 'code-review', 'prompt.md'))).toBe(true);
159
+ });
160
+
161
+ it('generates knowledge bundles per domain', () => {
162
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
163
+ expect(result.success).toBe(true);
164
+
165
+ // One bundle per domain
166
+ expect(existsSync(join(result.agentDir, 'knowledge', 'testing.json'))).toBe(true);
167
+ expect(existsSync(join(result.agentDir, 'knowledge', 'quality.json'))).toBe(true);
168
+
169
+ const bundle = JSON.parse(
170
+ readFileSync(join(result.agentDir, 'knowledge', 'testing.json'), 'utf-8'),
171
+ );
172
+ expect(bundle.domain).toBe('testing');
173
+ expect(bundle.version).toBe('1.0.0');
174
+ expect(bundle.entries).toEqual([]);
175
+ });
176
+
177
+ it('generates CLAUDE.md with correct agent identity', () => {
178
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
179
+ expect(result.success).toBe(true);
180
+
181
+ const claudeMd = readFileSync(join(result.agentDir, 'CLAUDE.md'), 'utf-8');
182
+ expect(claudeMd).toContain('# Test Agent Mode');
183
+ expect(claudeMd).toContain('**Role:** Testing Advisor');
184
+ expect(claudeMd).toContain('test-agent_core op:activate');
185
+ expect(claudeMd).toContain('test-agent_vault');
186
+ expect(claudeMd).toContain('Available Workflows');
187
+ expect(claudeMd).toContain('feature-dev');
188
+ });
189
+
190
+ it('.gitignore excludes auto-generated files', () => {
191
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
192
+ expect(result.success).toBe(true);
193
+
194
+ const gitignore = readFileSync(join(result.agentDir, '.gitignore'), 'utf-8');
195
+ expect(gitignore).toContain('CLAUDE.md');
196
+ expect(gitignore).toContain('AGENTS.md');
197
+ expect(gitignore).toContain('_engine.md');
198
+ });
199
+
200
+ it('fails if directory already exists', () => {
201
+ scaffoldFileTree(MINIMAL_CONFIG, tempDir);
202
+ const result2 = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
203
+
204
+ expect(result2.success).toBe(false);
205
+ expect(result2.summary).toContain('already exists');
206
+ });
207
+
208
+ it('fails on invalid config', () => {
209
+ const result = scaffoldFileTree(
210
+ { id: 'INVALID_ID', name: '', role: '', description: '', domains: [], principles: [] } as any,
211
+ tempDir,
212
+ );
213
+ expect(result.success).toBe(false);
214
+ expect(result.summary).toContain('Invalid config');
215
+ });
216
+
217
+ it('omits default values from agent.yaml for clean output', () => {
218
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
219
+ expect(result.success).toBe(true);
220
+
221
+ const content = readFileSync(join(result.agentDir, 'agent.yaml'), 'utf-8');
222
+
223
+ // tone: pragmatic is the default — should NOT appear
224
+ expect(content).not.toContain('tone:');
225
+ // setup.target: claude is the default — should NOT appear
226
+ expect(content).not.toContain('target:');
227
+ // engine.learning: true is the default — should NOT appear
228
+ expect(content).not.toContain('learning:');
229
+ });
230
+
231
+ it('includes non-default values in agent.yaml', () => {
232
+ const result = scaffoldFileTree(
233
+ {
234
+ ...MINIMAL_CONFIG,
235
+ tone: 'precise',
236
+ setup: { target: 'opencode', model: 'claude-code-opus-4' },
237
+ engine: { cognee: true },
238
+ },
239
+ tempDir,
240
+ );
241
+ expect(result.success).toBe(true);
242
+
243
+ const content = readFileSync(join(result.agentDir, 'agent.yaml'), 'utf-8');
244
+ const parsed = parseYaml(content);
245
+
246
+ expect(parsed.tone).toBe('precise');
247
+ expect(parsed.setup.target).toBe('opencode');
248
+ expect(parsed.setup.model).toBe('claude-code-opus-4');
249
+ expect(parsed.engine.cognee).toBe(true);
250
+ });
251
+
252
+ it('summary says no build step needed', () => {
253
+ const result = scaffoldFileTree(MINIMAL_CONFIG, tempDir);
254
+ expect(result.success).toBe(true);
255
+ expect(result.summary).toContain('No build step needed');
256
+ });
257
+ });
@@ -99,7 +99,7 @@ describe('Scaffolder', () => {
99
99
  scaffold(testConfig);
100
100
  const pkg = JSON.parse(readFileSync(join(tempDir, 'atlas', 'package.json'), 'utf-8'));
101
101
 
102
- expect(pkg.name).toBe('atlas-mcp');
102
+ expect(pkg.name).toBe('atlas');
103
103
  expect(pkg.type).toBe('module');
104
104
  expect(pkg.dependencies['@modelcontextprotocol/sdk']).toBeDefined();
105
105
  expect(pkg.dependencies['@soleri/core']).toBe('^2.0.0');
@@ -121,7 +121,7 @@ describe('Scaffolder', () => {
121
121
  expect(persona).toContain('Data quality is non-negotiable');
122
122
  });
123
123
 
124
- it('should create empty intelligence data files', () => {
124
+ it('should create seeded intelligence data files', () => {
125
125
  scaffold(testConfig);
126
126
  const dataDir = join(tempDir, 'atlas', 'src', 'intelligence', 'data');
127
127
  const files = readdirSync(dataDir);
@@ -132,7 +132,9 @@ describe('Scaffolder', () => {
132
132
 
133
133
  const bundle = JSON.parse(readFileSync(join(dataDir, 'data-pipelines.json'), 'utf-8'));
134
134
  expect(bundle.domain).toBe('data-pipelines');
135
- expect(bundle.entries).toEqual([]);
135
+ expect(bundle.entries.length).toBe(1);
136
+ expect(bundle.entries[0].id).toBe('data-pipelines-seed');
137
+ expect(bundle.entries[0].tags).toContain('seed');
136
138
  });
137
139
 
138
140
  it('should create entry point using runtime factories from @soleri/core', () => {
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Soleri v7 — File-Tree Agent Schema
3
+ *
4
+ * Defines the agent.yaml format for file-tree agents.
5
+ * This replaces the old AgentConfigSchema that generated TypeScript projects.
6
+ *
7
+ * An agent is a folder. This schema defines agent.yaml — the single source
8
+ * of truth for identity and engine configuration.
9
+ */
10
+
11
+ import { z } from 'zod';
12
+
13
+ // ─── Constants ────────────────────────────────────────────────────────
14
+
15
+ /** Communication tones */
16
+ export const TONES = ['precise', 'mentor', 'pragmatic'] as const;
17
+ export type Tone = (typeof TONES)[number];
18
+
19
+ /** Where to set up client integration */
20
+ export const SETUP_TARGETS = ['claude', 'codex', 'opencode', 'both', 'all'] as const;
21
+ export type SetupTarget = (typeof SETUP_TARGETS)[number];
22
+
23
+ // ─── Sub-Schemas ──────────────────────────────────────────────────────
24
+
25
+ /** External vault connection */
26
+ const VaultConnectionSchema = z.object({
27
+ /** Display name for this vault */
28
+ name: z.string().min(1),
29
+ /** Absolute path to vault SQLite database */
30
+ path: z.string().min(1),
31
+ /** Search priority (0–1). Higher = results ranked higher. Default: 0.5 */
32
+ priority: z.number().min(0).max(1).optional().default(0.5),
33
+ });
34
+
35
+ /** Domain pack reference */
36
+ const DomainPackSchema = z.object({
37
+ /** Domain name (e.g., "design", "code-review") */
38
+ name: z.string().min(1),
39
+ /** npm package name (e.g., "@soleri/domain-design") */
40
+ package: z.string().min(1),
41
+ /** Semver version constraint (optional) */
42
+ version: z.string().optional(),
43
+ });
44
+
45
+ /** Engine configuration */
46
+ const EngineConfigSchema = z.object({
47
+ /** Path to agent's vault SQLite database. Default: ~/.{id}/vault.db */
48
+ vault: z.string().optional(),
49
+ /** Enable brain/learning loop. Default: true */
50
+ learning: z.boolean().optional().default(true),
51
+ /** Enable Cognee vector search. Default: false */
52
+ cognee: z.boolean().optional().default(false),
53
+ });
54
+
55
+ /** Client setup configuration */
56
+ const SetupConfigSchema = z.object({
57
+ /** Target client for MCP registration */
58
+ target: z.enum(SETUP_TARGETS).optional().default('claude'),
59
+ /** Primary model for the client */
60
+ model: z.string().optional().default('claude-code-sonnet-4'),
61
+ });
62
+
63
+ // ─── Workflow Sub-Schemas ─────────────────────────────────────────────
64
+
65
+ /** Gate phases in a workflow */
66
+ export const GATE_PHASES = ['brainstorming', 'pre-execution', 'post-task', 'completion'] as const;
67
+ export type GatePhase = (typeof GATE_PHASES)[number];
68
+
69
+ /** Workflow gate definition (maps to gates.yaml) */
70
+ export const WorkflowGateSchema = z.object({
71
+ phase: z.enum(GATE_PHASES),
72
+ requirement: z.string().min(1),
73
+ check: z.string().min(1),
74
+ });
75
+
76
+ /** Task template ordering */
77
+ export const TASK_ORDERS = ['before-implementation', 'after-implementation', 'parallel'] as const;
78
+
79
+ /** Task template types */
80
+ export const TASK_TYPES = [
81
+ 'implementation',
82
+ 'test',
83
+ 'story',
84
+ 'documentation',
85
+ 'verification',
86
+ ] as const;
87
+
88
+ /** Workflow task template (injected during plan generation) */
89
+ export const WorkflowTaskTemplateSchema = z.object({
90
+ taskType: z.enum(TASK_TYPES),
91
+ titleTemplate: z.string().min(1),
92
+ acceptanceCriteria: z.array(z.string()).optional().default([]),
93
+ tools: z.array(z.string()).optional().default([]),
94
+ order: z.enum(TASK_ORDERS),
95
+ });
96
+
97
+ /** Workflow intent types */
98
+ export const INTENTS = ['BUILD', 'FIX', 'REVIEW', 'PLAN', 'IMPROVE', 'DELIVER'] as const;
99
+ export type Intent = (typeof INTENTS)[number];
100
+
101
+ /** Workflow definition (maps to workflow folder contents) */
102
+ export const WorkflowDefinitionSchema = z.object({
103
+ /** Unique workflow ID (derived from folder name if not specified) */
104
+ id: z.string().optional(),
105
+ /** generic or domain tier */
106
+ tier: z.enum(['generic', 'domain']).optional().default('generic'),
107
+ /** Human-readable title */
108
+ title: z.string().min(1),
109
+ /** When to activate this workflow */
110
+ trigger: z.string().optional(),
111
+ /** What this workflow does */
112
+ description: z.string().optional(),
113
+ /** Numbered step-by-step process (from prompt.md, parsed at runtime) */
114
+ steps: z.string().optional(),
115
+ /** Success criteria */
116
+ expectedOutcome: z.string().optional(),
117
+ /** ID of generic workflow this domain workflow extends */
118
+ extends: z.string().optional(),
119
+ /** Domain filtering: skip UI playbooks for backend tasks */
120
+ domain: z.enum(['ui', 'backend', 'any']).optional().default('any'),
121
+ /** Intents that trigger this workflow */
122
+ matchIntents: z.array(z.enum(INTENTS)).optional().default([]),
123
+ /** Keywords in plan text that trigger this workflow */
124
+ matchKeywords: z.array(z.string()).optional().default([]),
125
+ /** Lifecycle checkpoints */
126
+ gates: z.array(WorkflowGateSchema).optional().default([]),
127
+ /** Task templates injected during plan generation */
128
+ taskTemplates: z.array(WorkflowTaskTemplateSchema).optional().default([]),
129
+ /** Tools auto-added to plan's tool chain */
130
+ toolInjections: z.array(z.string()).optional().default([]),
131
+ /** Completion gate validation rules */
132
+ verificationCriteria: z.array(z.string()).optional().default([]),
133
+ });
134
+
135
+ // ─── Main Agent Schema ────────────────────────────────────────────────
136
+
137
+ /**
138
+ * agent.yaml schema — the single source of truth for a file-tree agent.
139
+ *
140
+ * This is what `soleri create` generates and what the engine reads on startup.
141
+ */
142
+ export const AgentYamlSchema = z.object({
143
+ // ─── Identity (required) ────────────────────────
144
+ /** Agent identifier — kebab-case, used for directories and tool prefixes */
145
+ id: z.string().regex(/^[a-z][a-z0-9-]*$/, 'Must be kebab-case (e.g., "gaudi", "my-agent")'),
146
+ /** Human-readable display name */
147
+ name: z.string().min(1).max(50),
148
+ /** One-line role description */
149
+ role: z.string().min(1).max(100),
150
+ /** Longer description of capabilities */
151
+ description: z.string().min(10).max(500),
152
+ /** Knowledge domains (1–20) */
153
+ domains: z.array(z.string().min(1)).min(1).max(20),
154
+ /** Core principles (1–10) */
155
+ principles: z.array(z.string().min(1)).min(1).max(10),
156
+
157
+ // ─── Personality (optional) ─────────────────────
158
+ /** Communication tone */
159
+ tone: z.enum(TONES).optional().default('pragmatic'),
160
+ /** Greeting message (auto-generated if omitted) */
161
+ greeting: z.string().min(10).max(300).optional(),
162
+
163
+ // ─── Engine ─────────────────────────────────────
164
+ /** Knowledge engine configuration */
165
+ engine: EngineConfigSchema.optional().default({}),
166
+
167
+ // ─── Vault Connections ──────────────────────────
168
+ /** Link to external vaults for shared knowledge */
169
+ vaults: z.array(VaultConnectionSchema).optional(),
170
+
171
+ // ─── Client Setup ──────────────────────────────
172
+ /** LLM client integration settings */
173
+ setup: SetupConfigSchema.optional().default({}),
174
+
175
+ // ─── Domain Packs ──────────────────────────────
176
+ /** npm domain packs with custom ops and knowledge */
177
+ packs: z.array(DomainPackSchema).optional(),
178
+ });
179
+
180
+ export type AgentYaml = z.infer<typeof AgentYamlSchema>;
181
+ export type AgentYamlInput = z.input<typeof AgentYamlSchema>;
182
+ export type WorkflowDefinition = z.infer<typeof WorkflowDefinitionSchema>;
183
+ export type WorkflowGate = z.infer<typeof WorkflowGateSchema>;
184
+ export type WorkflowTaskTemplate = z.infer<typeof WorkflowTaskTemplateSchema>;