@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.
- package/dist/agent-schema.d.ts +323 -0
- package/dist/agent-schema.js +151 -0
- package/dist/agent-schema.js.map +1 -0
- package/dist/compose-claude-md.d.ts +24 -0
- package/dist/compose-claude-md.js +197 -0
- package/dist/compose-claude-md.js.map +1 -0
- package/dist/index.js +0 -0
- package/dist/lib.d.ts +12 -1
- package/dist/lib.js +10 -1
- package/dist/lib.js.map +1 -1
- package/dist/scaffold-filetree.d.ts +22 -0
- package/dist/scaffold-filetree.js +361 -0
- package/dist/scaffold-filetree.js.map +1 -0
- package/dist/scaffolder.js +261 -11
- package/dist/scaffolder.js.map +1 -1
- package/dist/templates/activate.js +39 -1
- package/dist/templates/activate.js.map +1 -1
- package/dist/templates/agents-md.d.ts +10 -1
- package/dist/templates/agents-md.js +76 -16
- package/dist/templates/agents-md.js.map +1 -1
- package/dist/templates/claude-md-template.js +9 -1
- package/dist/templates/claude-md-template.js.map +1 -1
- package/dist/templates/entry-point.js +83 -6
- package/dist/templates/entry-point.js.map +1 -1
- package/dist/templates/inject-claude-md.js +53 -0
- package/dist/templates/inject-claude-md.js.map +1 -1
- package/dist/templates/package-json.js +4 -1
- package/dist/templates/package-json.js.map +1 -1
- package/dist/templates/readme.js +4 -3
- package/dist/templates/readme.js.map +1 -1
- package/dist/templates/setup-script.js +109 -3
- package/dist/templates/setup-script.js.map +1 -1
- package/dist/templates/shared-rules.js +54 -17
- package/dist/templates/shared-rules.js.map +1 -1
- package/dist/templates/test-facades.js +151 -6
- package/dist/templates/test-facades.js.map +1 -1
- package/dist/types.d.ts +71 -6
- package/dist/types.js +39 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/detect-domain-packs.d.ts +25 -0
- package/dist/utils/detect-domain-packs.js +104 -0
- package/dist/utils/detect-domain-packs.js.map +1 -0
- package/package.json +2 -1
- package/src/__tests__/detect-domain-packs.test.ts +178 -0
- package/src/__tests__/scaffold-filetree.test.ts +257 -0
- package/src/__tests__/scaffolder.test.ts +5 -3
- package/src/agent-schema.ts +184 -0
- package/src/compose-claude-md.ts +252 -0
- package/src/lib.ts +14 -1
- package/src/scaffold-filetree.ts +421 -0
- package/src/scaffolder.ts +299 -15
- package/src/templates/activate.ts +39 -0
- package/src/templates/agents-md.ts +78 -16
- package/src/templates/claude-md-template.ts +12 -1
- package/src/templates/entry-point.ts +90 -6
- package/src/templates/inject-claude-md.ts +53 -0
- package/src/templates/package-json.ts +4 -1
- package/src/templates/readme.ts +4 -3
- package/src/templates/setup-script.ts +110 -4
- package/src/templates/shared-rules.ts +55 -17
- package/src/templates/test-facades.ts +156 -6
- package/src/types.ts +44 -1
- package/src/utils/detect-domain-packs.ts +129 -0
- package/tsconfig.json +0 -1
- 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
|
|
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
|
|
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).
|
|
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>;
|