@soleri/forge 9.7.2 → 9.9.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 +177 -6
- package/dist/agent-schema.js +58 -0
- package/dist/agent-schema.js.map +1 -1
- package/dist/compose-claude-md.js +56 -3
- package/dist/compose-claude-md.js.map +1 -1
- package/dist/domain-manager.d.ts +1 -0
- package/dist/domain-manager.js +57 -1
- package/dist/domain-manager.js.map +1 -1
- package/dist/knowledge-installer.d.ts +2 -0
- package/dist/knowledge-installer.js +107 -1
- package/dist/knowledge-installer.js.map +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.js +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/scaffold-filetree.d.ts +12 -0
- package/dist/scaffold-filetree.js +356 -10
- package/dist/scaffold-filetree.js.map +1 -1
- package/dist/scaffolder.js +12 -0
- package/dist/scaffolder.js.map +1 -1
- package/dist/skills/subagent-driven-development/SKILL.md +87 -20
- package/dist/templates/setup-script.js +71 -0
- package/dist/templates/setup-script.js.map +1 -1
- package/dist/templates/shared-rules.js +163 -6
- package/dist/templates/shared-rules.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/domain-manager.test.ts +140 -0
- package/src/__tests__/scaffold-filetree.test.ts +326 -1
- package/src/__tests__/scaffolder.test.ts +7 -5
- package/src/__tests__/shared-rules.test.ts +48 -0
- package/src/agent-schema.ts +66 -0
- package/src/compose-claude-md.ts +63 -3
- package/src/domain-manager.ts +74 -1
- package/src/knowledge-installer.ts +124 -1
- package/src/lib.ts +6 -1
- package/src/scaffold-filetree.ts +404 -10
- package/src/scaffolder.ts +17 -0
- package/src/skills/subagent-driven-development/SKILL.md +87 -20
- package/src/templates/setup-script.ts +71 -0
- package/src/templates/shared-rules.ts +166 -6
package/src/scaffold-filetree.ts
CHANGED
|
@@ -18,6 +18,64 @@ import { composeClaudeMd } from './compose-claude-md.js';
|
|
|
18
18
|
import { generateSkills } from './templates/skills.js';
|
|
19
19
|
import type { AgentConfig } from './types.js';
|
|
20
20
|
|
|
21
|
+
// ─── Skills Registry ─────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Skills classified as essential (always scaffolded by default) or optional
|
|
25
|
+
* (installed on demand via `soleri skills install`).
|
|
26
|
+
*/
|
|
27
|
+
export const SKILLS_REGISTRY: Record<string, 'essential' | 'optional'> = {
|
|
28
|
+
'agent-guide': 'essential',
|
|
29
|
+
'agent-persona': 'essential',
|
|
30
|
+
'vault-navigator': 'essential',
|
|
31
|
+
'vault-capture': 'essential',
|
|
32
|
+
'systematic-debugging': 'essential',
|
|
33
|
+
'writing-plans': 'essential',
|
|
34
|
+
'context-resume': 'essential',
|
|
35
|
+
// ─── Optional (installed on demand) ────────────
|
|
36
|
+
'agent-dev': 'optional',
|
|
37
|
+
'agent-issues': 'optional',
|
|
38
|
+
'brain-debrief': 'optional',
|
|
39
|
+
brainstorming: 'optional',
|
|
40
|
+
'code-patrol': 'optional',
|
|
41
|
+
'deep-review': 'optional',
|
|
42
|
+
'deliver-and-ship': 'optional',
|
|
43
|
+
'discovery-phase': 'optional',
|
|
44
|
+
'env-setup': 'optional',
|
|
45
|
+
'executing-plans': 'optional',
|
|
46
|
+
'finishing-a-development-branch': 'optional',
|
|
47
|
+
'fix-and-learn': 'optional',
|
|
48
|
+
'health-check': 'optional',
|
|
49
|
+
'knowledge-harvest': 'optional',
|
|
50
|
+
'mcp-doctor': 'optional',
|
|
51
|
+
'onboard-me': 'optional',
|
|
52
|
+
'parallel-execute': 'optional',
|
|
53
|
+
retrospective: 'optional',
|
|
54
|
+
'second-opinion': 'optional',
|
|
55
|
+
'subagent-driven-development': 'optional',
|
|
56
|
+
'test-driven-development': 'optional',
|
|
57
|
+
'using-git-worktrees': 'optional',
|
|
58
|
+
'vault-curate': 'optional',
|
|
59
|
+
'vault-smells': 'optional',
|
|
60
|
+
'verification-before-completion': 'optional',
|
|
61
|
+
'yolo-mode': 'optional',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Names of essential skills (always scaffolded when skillsFilter is 'essential'). */
|
|
65
|
+
export const ESSENTIAL_SKILLS = Object.entries(SKILLS_REGISTRY)
|
|
66
|
+
.filter(([, tier]) => tier === 'essential')
|
|
67
|
+
.map(([name]) => name);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the skill names to scaffold based on the skillsFilter config value.
|
|
71
|
+
* Returns null when all skills should be included (no filtering).
|
|
72
|
+
*/
|
|
73
|
+
export function resolveSkillsFilter(skillsFilter: 'all' | 'essential' | string[]): string[] | null {
|
|
74
|
+
if (skillsFilter === 'all') return null; // null = include all
|
|
75
|
+
if (skillsFilter === 'essential') return ESSENTIAL_SKILLS;
|
|
76
|
+
return skillsFilter; // explicit list
|
|
77
|
+
}
|
|
78
|
+
|
|
21
79
|
// ─── Types ────────────────────────────────────────────────────────────
|
|
22
80
|
|
|
23
81
|
export interface FileTreeScaffoldResult {
|
|
@@ -75,7 +133,9 @@ When building a new feature, adding functionality, or creating components.
|
|
|
75
133
|
- Link new entries to related knowledge: \`op:link_entries\`
|
|
76
134
|
- Complete orchestration: \`op:orchestrate_complete\`
|
|
77
135
|
`,
|
|
78
|
-
gates:
|
|
136
|
+
gates: `# Workflow gates — engine reads these and enforces them during plan execution.
|
|
137
|
+
# Format: phase (brainstorming|pre-execution|post-task|completion), requirement, check
|
|
138
|
+
gates:
|
|
79
139
|
- phase: brainstorming
|
|
80
140
|
requirement: Requirements are clear and user has approved the approach
|
|
81
141
|
check: user-approval
|
|
@@ -92,7 +152,9 @@ When building a new feature, adding functionality, or creating components.
|
|
|
92
152
|
requirement: Knowledge captured to vault with links
|
|
93
153
|
check: knowledge-captured
|
|
94
154
|
`,
|
|
95
|
-
tools:
|
|
155
|
+
tools: `# Workflow tools — engine merges these into plan steps.
|
|
156
|
+
# Format: list of operation strings (agentId_facade op:operation_name)
|
|
157
|
+
tools:
|
|
96
158
|
- soleri_vault op:search_intelligent
|
|
97
159
|
- soleri_vault op:capture_knowledge
|
|
98
160
|
- soleri_links op:link_entries
|
|
@@ -133,7 +195,9 @@ When fixing bugs, resolving errors, or addressing regressions.
|
|
|
133
195
|
- If the bug reveals a pattern or anti-pattern, capture it: \`op:capture_knowledge\`
|
|
134
196
|
- Complete orchestration: \`op:orchestrate_complete\`
|
|
135
197
|
`,
|
|
136
|
-
gates:
|
|
198
|
+
gates: `# Workflow gates — engine reads these and enforces them during plan execution.
|
|
199
|
+
# Format: phase (brainstorming|pre-execution|post-task|completion), requirement, check
|
|
200
|
+
gates:
|
|
137
201
|
- phase: pre-execution
|
|
138
202
|
requirement: Root cause identified and fix plan approved
|
|
139
203
|
check: plan-approved
|
|
@@ -146,7 +210,9 @@ When fixing bugs, resolving errors, or addressing regressions.
|
|
|
146
210
|
requirement: Anti-pattern captured if applicable
|
|
147
211
|
check: knowledge-captured
|
|
148
212
|
`,
|
|
149
|
-
tools:
|
|
213
|
+
tools: `# Workflow tools — engine merges these into plan steps.
|
|
214
|
+
# Format: list of operation strings (agentId_facade op:operation_name)
|
|
215
|
+
tools:
|
|
150
216
|
- soleri_vault op:search_intelligent
|
|
151
217
|
- soleri_vault op:capture_knowledge
|
|
152
218
|
- soleri_plan op:create_plan
|
|
@@ -180,12 +246,16 @@ When reviewing code, auditing quality, or checking for issues.
|
|
|
180
246
|
### 4. Capture
|
|
181
247
|
- If review reveals new patterns or anti-patterns, capture them: \`op:capture_knowledge\`
|
|
182
248
|
`,
|
|
183
|
-
gates:
|
|
249
|
+
gates: `# Workflow gates — engine reads these and enforces them during plan execution.
|
|
250
|
+
# Format: phase (brainstorming|pre-execution|post-task|completion), requirement, check
|
|
251
|
+
gates:
|
|
184
252
|
- phase: completion
|
|
185
253
|
requirement: All blocking issues addressed
|
|
186
254
|
check: issues-resolved
|
|
187
255
|
`,
|
|
188
|
-
tools:
|
|
256
|
+
tools: `# Workflow tools — engine merges these into plan steps.
|
|
257
|
+
# Format: list of operation strings (agentId_facade op:operation_name)
|
|
258
|
+
tools:
|
|
189
259
|
- soleri_vault op:search_intelligent
|
|
190
260
|
- soleri_vault op:capture_knowledge
|
|
191
261
|
- soleri_brain op:recommend
|
|
@@ -217,7 +287,9 @@ Before crossing a context window boundary — \`/clear\`, context compaction, or
|
|
|
217
287
|
- Use plan IDs to look up active plans: \`op:orchestrate_status\`
|
|
218
288
|
- Continue from where the handoff left off
|
|
219
289
|
`,
|
|
220
|
-
gates:
|
|
290
|
+
gates: `# Workflow gates — engine reads these and enforces them during plan execution.
|
|
291
|
+
# Format: phase (brainstorming|pre-execution|post-task|completion), requirement, check
|
|
292
|
+
gates:
|
|
221
293
|
- phase: pre-transition
|
|
222
294
|
requirement: Handoff document generated with current state
|
|
223
295
|
check: handoff-generated
|
|
@@ -226,7 +298,9 @@ Before crossing a context window boundary — \`/clear\`, context compaction, or
|
|
|
226
298
|
requirement: New context has loaded handoff and can reference active plans
|
|
227
299
|
check: context-restored
|
|
228
300
|
`,
|
|
229
|
-
tools:
|
|
301
|
+
tools: `# Workflow tools — engine merges these into plan steps.
|
|
302
|
+
# Format: list of operation strings (agentId_facade op:operation_name)
|
|
303
|
+
tools:
|
|
230
304
|
- soleri_memory op:handoff_generate
|
|
231
305
|
- soleri_memory op:session_capture
|
|
232
306
|
- soleri_orchestrate op:orchestrate_status
|
|
@@ -235,6 +309,164 @@ Before crossing a context window boundary — \`/clear\`, context compaction, or
|
|
|
235
309
|
},
|
|
236
310
|
];
|
|
237
311
|
|
|
312
|
+
// ─── Example Instruction Files ───────────────────────────────────────
|
|
313
|
+
|
|
314
|
+
const INSTRUCTIONS_CONVENTIONS = `# Conventions
|
|
315
|
+
|
|
316
|
+
<!-- Customize this file with your project's naming conventions, coding standards, and rules. -->
|
|
317
|
+
<!-- This file is composed into CLAUDE.md automatically — your agent will follow these rules. -->
|
|
318
|
+
|
|
319
|
+
## Naming Conventions
|
|
320
|
+
|
|
321
|
+
- Use \`kebab-case\` for file and directory names
|
|
322
|
+
- Use \`camelCase\` for variables and functions
|
|
323
|
+
- Use \`PascalCase\` for classes, types, and interfaces
|
|
324
|
+
- Prefix private helpers with \`_\` (e.g., \`_validateInput\`)
|
|
325
|
+
|
|
326
|
+
## File Organization
|
|
327
|
+
|
|
328
|
+
- Source code goes in \`src/\`
|
|
329
|
+
- Tests live next to the code they test (\`*.test.ts\`)
|
|
330
|
+
- Shared utilities go in \`src/utils/\`
|
|
331
|
+
- Types and interfaces go in \`src/types/\`
|
|
332
|
+
|
|
333
|
+
## Code Standards
|
|
334
|
+
|
|
335
|
+
- Every function must have a JSDoc comment explaining its purpose
|
|
336
|
+
- Prefer \`const\` over \`let\`; never use \`var\`
|
|
337
|
+
- Maximum file length: 300 lines — split if larger
|
|
338
|
+
- No default exports — use named exports only
|
|
339
|
+
|
|
340
|
+
## What to Avoid
|
|
341
|
+
|
|
342
|
+
- Do not add new npm dependencies without approval
|
|
343
|
+
- Do not use \`any\` type — use \`unknown\` and narrow
|
|
344
|
+
- Do not commit commented-out code
|
|
345
|
+
- Do not use hardcoded values — extract to constants or config
|
|
346
|
+
`;
|
|
347
|
+
|
|
348
|
+
const INSTRUCTIONS_GETTING_STARTED = `# Getting Started with Instructions
|
|
349
|
+
|
|
350
|
+
This folder contains your agent's custom behavioral rules. Every \`.md\` file here
|
|
351
|
+
is automatically composed into \`CLAUDE.md\` when you run \`soleri dev\`.
|
|
352
|
+
|
|
353
|
+
## How It Works
|
|
354
|
+
|
|
355
|
+
1. Create a new \`.md\` file in this folder (e.g., \`api-guidelines.md\`)
|
|
356
|
+
2. Write your rules, conventions, or guidelines in Markdown
|
|
357
|
+
3. Run \`soleri dev\` — it watches for changes and regenerates \`CLAUDE.md\`
|
|
358
|
+
4. Your agent now follows these rules in every conversation
|
|
359
|
+
|
|
360
|
+
## File Naming
|
|
361
|
+
|
|
362
|
+
- Files are included in **alphabetical order** (prefix with numbers to control order)
|
|
363
|
+
- \`_engine.md\` is auto-generated by Soleri — **do not edit it manually**
|
|
364
|
+
- \`domain.md\` was generated from your agent's domain config
|
|
365
|
+
|
|
366
|
+
## Tips
|
|
367
|
+
|
|
368
|
+
- Keep each file focused on one topic (conventions, workflows, constraints)
|
|
369
|
+
- Use clear headings — your agent reads these as instructions
|
|
370
|
+
- Add "What to Avoid" sections — agents benefit from explicit anti-patterns
|
|
371
|
+
- See the [Soleri docs](https://soleri.ai/docs) for more examples
|
|
372
|
+
`;
|
|
373
|
+
|
|
374
|
+
// ─── Workspace & Routing Seeds ───────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
/** Default workspaces seeded based on agent domains. */
|
|
377
|
+
const DOMAIN_WORKSPACE_SEEDS: Record<string, { id: string; name: string; description: string }[]> =
|
|
378
|
+
{
|
|
379
|
+
// Design-related domains
|
|
380
|
+
design: [
|
|
381
|
+
{
|
|
382
|
+
id: 'design',
|
|
383
|
+
name: 'Design',
|
|
384
|
+
description: 'Design system patterns, tokens, and components',
|
|
385
|
+
},
|
|
386
|
+
{ id: 'review', name: 'Review', description: 'Design review and accessibility audits' },
|
|
387
|
+
],
|
|
388
|
+
'ui-design': [
|
|
389
|
+
{ id: 'design', name: 'Design', description: 'UI design patterns, tokens, and components' },
|
|
390
|
+
{ id: 'review', name: 'Review', description: 'Design review and accessibility audits' },
|
|
391
|
+
],
|
|
392
|
+
accessibility: [
|
|
393
|
+
{ id: 'design', name: 'Design', description: 'Accessible design patterns and tokens' },
|
|
394
|
+
{ id: 'review', name: 'Review', description: 'Accessibility audits and compliance checks' },
|
|
395
|
+
],
|
|
396
|
+
// Dev-related domains
|
|
397
|
+
architecture: [
|
|
398
|
+
{
|
|
399
|
+
id: 'planning',
|
|
400
|
+
name: 'Planning',
|
|
401
|
+
description: 'Architecture decisions and technical planning',
|
|
402
|
+
},
|
|
403
|
+
{ id: 'src', name: 'Source', description: 'Implementation code and modules' },
|
|
404
|
+
{ id: 'docs', name: 'Documentation', description: 'Technical documentation and ADRs' },
|
|
405
|
+
],
|
|
406
|
+
backend: [
|
|
407
|
+
{ id: 'planning', name: 'Planning', description: 'Backend architecture and API design' },
|
|
408
|
+
{ id: 'src', name: 'Source', description: 'Implementation code and modules' },
|
|
409
|
+
{ id: 'docs', name: 'Documentation', description: 'API documentation and guides' },
|
|
410
|
+
],
|
|
411
|
+
frontend: [
|
|
412
|
+
{
|
|
413
|
+
id: 'planning',
|
|
414
|
+
name: 'Planning',
|
|
415
|
+
description: 'Frontend architecture and component design',
|
|
416
|
+
},
|
|
417
|
+
{ id: 'src', name: 'Source', description: 'Implementation code and components' },
|
|
418
|
+
{
|
|
419
|
+
id: 'docs',
|
|
420
|
+
name: 'Documentation',
|
|
421
|
+
description: 'Component documentation and style guides',
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
security: [
|
|
425
|
+
{
|
|
426
|
+
id: 'planning',
|
|
427
|
+
name: 'Planning',
|
|
428
|
+
description: 'Security architecture and threat modeling',
|
|
429
|
+
},
|
|
430
|
+
{ id: 'src', name: 'Source', description: 'Security implementations and policies' },
|
|
431
|
+
{ id: 'docs', name: 'Documentation', description: 'Security documentation and runbooks' },
|
|
432
|
+
],
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/** Default routing entries seeded based on agent domains. */
|
|
436
|
+
const DOMAIN_ROUTING_SEEDS: Record<
|
|
437
|
+
string,
|
|
438
|
+
{ pattern: string; workspace: string; skills: string[] }[]
|
|
439
|
+
> = {
|
|
440
|
+
design: [
|
|
441
|
+
{ pattern: 'design component', workspace: 'design', skills: ['vault-navigator'] },
|
|
442
|
+
{ pattern: 'review design', workspace: 'review', skills: ['deep-review'] },
|
|
443
|
+
],
|
|
444
|
+
'ui-design': [
|
|
445
|
+
{ pattern: 'design component', workspace: 'design', skills: ['vault-navigator'] },
|
|
446
|
+
{ pattern: 'review design', workspace: 'review', skills: ['deep-review'] },
|
|
447
|
+
],
|
|
448
|
+
architecture: [
|
|
449
|
+
{ pattern: 'plan architecture', workspace: 'planning', skills: ['writing-plans'] },
|
|
450
|
+
{ pattern: 'implement feature', workspace: 'src', skills: ['test-driven-development'] },
|
|
451
|
+
{ pattern: 'write documentation', workspace: 'docs', skills: ['vault-capture'] },
|
|
452
|
+
],
|
|
453
|
+
backend: [
|
|
454
|
+
{ pattern: 'plan API', workspace: 'planning', skills: ['writing-plans'] },
|
|
455
|
+
{ pattern: 'implement endpoint', workspace: 'src', skills: ['test-driven-development'] },
|
|
456
|
+
{ pattern: 'write docs', workspace: 'docs', skills: ['vault-capture'] },
|
|
457
|
+
],
|
|
458
|
+
frontend: [
|
|
459
|
+
{ pattern: 'plan component', workspace: 'planning', skills: ['writing-plans'] },
|
|
460
|
+
{ pattern: 'implement component', workspace: 'src', skills: ['test-driven-development'] },
|
|
461
|
+
{ pattern: 'write docs', workspace: 'docs', skills: ['vault-capture'] },
|
|
462
|
+
],
|
|
463
|
+
security: [
|
|
464
|
+
{ pattern: 'threat model', workspace: 'planning', skills: ['writing-plans'] },
|
|
465
|
+
{ pattern: 'implement policy', workspace: 'src', skills: ['test-driven-development'] },
|
|
466
|
+
{ pattern: 'write runbook', workspace: 'docs', skills: ['vault-capture'] },
|
|
467
|
+
],
|
|
468
|
+
};
|
|
469
|
+
|
|
238
470
|
// ─── Main Scaffolder ──────────────────────────────────────────────────
|
|
239
471
|
|
|
240
472
|
/**
|
|
@@ -324,6 +556,13 @@ export function scaffoldFileTree(input: AgentYamlInput, outputDir: string): File
|
|
|
324
556
|
'AGENTS.md',
|
|
325
557
|
'instructions/_engine.md',
|
|
326
558
|
'',
|
|
559
|
+
'# OS',
|
|
560
|
+
'.DS_Store',
|
|
561
|
+
'',
|
|
562
|
+
'# Editor / IDE state',
|
|
563
|
+
'.obsidian/',
|
|
564
|
+
'.opencode/',
|
|
565
|
+
'',
|
|
327
566
|
].join('\n'),
|
|
328
567
|
filesCreated,
|
|
329
568
|
);
|
|
@@ -332,6 +571,24 @@ export function scaffoldFileTree(input: AgentYamlInput, outputDir: string): File
|
|
|
332
571
|
writeFile(agentDir, 'instructions/_engine.md', getEngineRulesContent(), filesCreated);
|
|
333
572
|
|
|
334
573
|
// ─── 6. Write user instruction files ────────────────────────
|
|
574
|
+
// Generate user.md — user-editable file with priority placement in CLAUDE.md
|
|
575
|
+
const userMdContent = [
|
|
576
|
+
'# Your Custom Rules',
|
|
577
|
+
'',
|
|
578
|
+
'Add your agent-specific rules, constraints, and preferences here.',
|
|
579
|
+
'This file gets priority placement in CLAUDE.md — it appears before engine rules.',
|
|
580
|
+
'',
|
|
581
|
+
'## Examples of what to put here:',
|
|
582
|
+
'- Project-specific conventions',
|
|
583
|
+
'- Communication preferences',
|
|
584
|
+
'- Domain expertise to emphasize',
|
|
585
|
+
'- Things to always/never do',
|
|
586
|
+
'',
|
|
587
|
+
'Delete these instructions and replace with your own content.',
|
|
588
|
+
'',
|
|
589
|
+
].join('\n');
|
|
590
|
+
writeFile(agentDir, 'instructions/user.md', userMdContent, filesCreated);
|
|
591
|
+
|
|
335
592
|
// Generate domain-specific instruction file if agent has specialized domains
|
|
336
593
|
if (config.domains.length > 0) {
|
|
337
594
|
const domainLines = [
|
|
@@ -347,6 +604,15 @@ export function scaffoldFileTree(input: AgentYamlInput, outputDir: string): File
|
|
|
347
604
|
writeFile(agentDir, 'instructions/domain.md', domainLines.join('\n'), filesCreated);
|
|
348
605
|
}
|
|
349
606
|
|
|
607
|
+
// ─── 6b. Write example instruction files ─────────────────────
|
|
608
|
+
writeFile(agentDir, 'instructions/conventions.md', INSTRUCTIONS_CONVENTIONS, filesCreated);
|
|
609
|
+
writeFile(
|
|
610
|
+
agentDir,
|
|
611
|
+
'instructions/getting-started.md',
|
|
612
|
+
INSTRUCTIONS_GETTING_STARTED,
|
|
613
|
+
filesCreated,
|
|
614
|
+
);
|
|
615
|
+
|
|
350
616
|
// ─── 7. Write workflows ─────────────────────────────────────
|
|
351
617
|
for (const wf of BUILTIN_WORKFLOWS) {
|
|
352
618
|
writeFile(agentDir, `workflows/${wf.name}/prompt.md`, wf.prompt, filesCreated);
|
|
@@ -355,7 +621,11 @@ export function scaffoldFileTree(input: AgentYamlInput, outputDir: string): File
|
|
|
355
621
|
}
|
|
356
622
|
|
|
357
623
|
// ─── 8. Copy bundled skills (with placeholder substitution) ─
|
|
358
|
-
const
|
|
624
|
+
const resolvedSkills = resolveSkillsFilter(config.skillsFilter);
|
|
625
|
+
const skills = generateSkills({
|
|
626
|
+
id: config.id,
|
|
627
|
+
skills: resolvedSkills ?? undefined,
|
|
628
|
+
} as AgentConfig);
|
|
359
629
|
for (const [relativePath, content] of skills) {
|
|
360
630
|
mkdirSync(join(agentDir, dirname(relativePath)), { recursive: true });
|
|
361
631
|
writeFile(agentDir, relativePath, content, filesCreated);
|
|
@@ -381,7 +651,33 @@ export function scaffoldFileTree(input: AgentYamlInput, outputDir: string): File
|
|
|
381
651
|
totalSeeded += starterEntries.length;
|
|
382
652
|
}
|
|
383
653
|
|
|
384
|
-
// ───
|
|
654
|
+
// ─── 9b. Create workspace directories with CONTEXT.md ──────
|
|
655
|
+
// Resolve workspaces: use explicit config or seed from domains
|
|
656
|
+
const resolvedWorkspaces = resolveWorkspaces(config);
|
|
657
|
+
if (resolvedWorkspaces.length > 0) {
|
|
658
|
+
for (const ws of resolvedWorkspaces) {
|
|
659
|
+
const wsDir = join(agentDir, 'workspaces', ws.id);
|
|
660
|
+
mkdirSync(wsDir, { recursive: true });
|
|
661
|
+
const contextContent = [
|
|
662
|
+
`# ${ws.name}`,
|
|
663
|
+
'',
|
|
664
|
+
ws.description,
|
|
665
|
+
'',
|
|
666
|
+
'## Instructions',
|
|
667
|
+
'',
|
|
668
|
+
`<!-- Add workspace-specific instructions here for the "${ws.name}" context. -->`,
|
|
669
|
+
'',
|
|
670
|
+
].join('\n');
|
|
671
|
+
writeFile(
|
|
672
|
+
agentDir,
|
|
673
|
+
`workspaces/${ws.id}/${ws.contextFile ?? 'CONTEXT.md'}`,
|
|
674
|
+
contextContent,
|
|
675
|
+
filesCreated,
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// ─── 10. Generate CLAUDE.md ──────────────────────────────────
|
|
385
681
|
const { content: claudeMd } = composeClaudeMd(agentDir);
|
|
386
682
|
writeFile(agentDir, 'CLAUDE.md', claudeMd, filesCreated);
|
|
387
683
|
|
|
@@ -476,6 +772,34 @@ function buildAgentYaml(config: AgentYaml): Record<string, unknown> {
|
|
|
476
772
|
setup.model = config.setup.model;
|
|
477
773
|
if (Object.keys(setup).length > 0) yaml.setup = setup;
|
|
478
774
|
|
|
775
|
+
// Skills filter — only include if not the default ('essential')
|
|
776
|
+
if (config.skillsFilter && config.skillsFilter !== 'essential') {
|
|
777
|
+
yaml.skillsFilter = config.skillsFilter;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Workspaces
|
|
781
|
+
const resolvedWs = resolveWorkspaces(config);
|
|
782
|
+
if (resolvedWs.length > 0) {
|
|
783
|
+
yaml.workspaces = resolvedWs.map((ws) =>
|
|
784
|
+
Object.assign(
|
|
785
|
+
{ id: ws.id, name: ws.name, description: ws.description },
|
|
786
|
+
ws.contextFile !== `CONTEXT.md` ? { contextFile: ws.contextFile } : {},
|
|
787
|
+
),
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Routing
|
|
792
|
+
const resolvedRouting = resolveRouting(config);
|
|
793
|
+
if (resolvedRouting.length > 0) {
|
|
794
|
+
yaml.routing = resolvedRouting.map((r) =>
|
|
795
|
+
Object.assign(
|
|
796
|
+
{ pattern: r.pattern, workspace: r.workspace },
|
|
797
|
+
r.context.length > 0 ? { context: r.context } : {},
|
|
798
|
+
r.skills.length > 0 ? { skills: r.skills } : {},
|
|
799
|
+
),
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
|
|
479
803
|
// Packs
|
|
480
804
|
if (config.packs && config.packs.length > 0) {
|
|
481
805
|
yaml.packs = config.packs;
|
|
@@ -484,6 +808,76 @@ function buildAgentYaml(config: AgentYaml): Record<string, unknown> {
|
|
|
484
808
|
return yaml;
|
|
485
809
|
}
|
|
486
810
|
|
|
811
|
+
// ─── Workspace & Routing Helpers ─────────────────────────────────────
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Resolve workspaces: use explicit config or seed from domains.
|
|
815
|
+
* Deduplicates by workspace id.
|
|
816
|
+
*/
|
|
817
|
+
function resolveWorkspaces(
|
|
818
|
+
config: AgentYaml,
|
|
819
|
+
): { id: string; name: string; description: string; contextFile: string }[] {
|
|
820
|
+
// If explicitly defined, use those
|
|
821
|
+
if (config.workspaces && config.workspaces.length > 0) {
|
|
822
|
+
return config.workspaces.map((ws) => ({
|
|
823
|
+
id: ws.id,
|
|
824
|
+
name: ws.name,
|
|
825
|
+
description: ws.description,
|
|
826
|
+
contextFile: ws.contextFile ?? 'CONTEXT.md',
|
|
827
|
+
}));
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// Otherwise, seed from domains
|
|
831
|
+
const seen = new Set<string>();
|
|
832
|
+
const workspaces: { id: string; name: string; description: string; contextFile: string }[] = [];
|
|
833
|
+
|
|
834
|
+
for (const domain of config.domains) {
|
|
835
|
+
const seeds = DOMAIN_WORKSPACE_SEEDS[domain];
|
|
836
|
+
if (!seeds) continue;
|
|
837
|
+
for (const seed of seeds) {
|
|
838
|
+
if (seen.has(seed.id)) continue;
|
|
839
|
+
seen.add(seed.id);
|
|
840
|
+
workspaces.push({ ...seed, contextFile: 'CONTEXT.md' });
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return workspaces;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Resolve routing entries: use explicit config or seed from domains.
|
|
849
|
+
* Deduplicates by pattern string.
|
|
850
|
+
*/
|
|
851
|
+
function resolveRouting(
|
|
852
|
+
config: AgentYaml,
|
|
853
|
+
): { pattern: string; workspace: string; context: string[]; skills: string[] }[] {
|
|
854
|
+
// If explicitly defined, use those
|
|
855
|
+
if (config.routing && config.routing.length > 0) {
|
|
856
|
+
return config.routing.map((r) => ({
|
|
857
|
+
pattern: r.pattern,
|
|
858
|
+
workspace: r.workspace,
|
|
859
|
+
context: r.context ?? [],
|
|
860
|
+
skills: r.skills ?? [],
|
|
861
|
+
}));
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Otherwise, seed from domains
|
|
865
|
+
const seen = new Set<string>();
|
|
866
|
+
const routes: { pattern: string; workspace: string; context: string[]; skills: string[] }[] = [];
|
|
867
|
+
|
|
868
|
+
for (const domain of config.domains) {
|
|
869
|
+
const seeds = DOMAIN_ROUTING_SEEDS[domain];
|
|
870
|
+
if (!seeds) continue;
|
|
871
|
+
for (const seed of seeds) {
|
|
872
|
+
if (seen.has(seed.pattern)) continue;
|
|
873
|
+
seen.add(seed.pattern);
|
|
874
|
+
routes.push({ ...seed, context: [] });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return routes;
|
|
879
|
+
}
|
|
880
|
+
|
|
487
881
|
// ─── Starter Pack Helpers ────────────────────────────────────────────
|
|
488
882
|
|
|
489
883
|
/** Domain aliases — map agent domains to starter pack directories. */
|
package/src/scaffolder.ts
CHANGED
|
@@ -163,6 +163,13 @@ export function previewScaffold(config: AgentConfig): ScaffoldPreview {
|
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
if (opencodeSetup && config.hookPacks?.length) {
|
|
167
|
+
files.push({
|
|
168
|
+
path: '.opencode/plugins/',
|
|
169
|
+
description: `OpenCode enforcement plugin (${config.hookPacks.join(', ')})`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
166
173
|
if (config.telegram) {
|
|
167
174
|
files.push(
|
|
168
175
|
{
|
|
@@ -334,6 +341,10 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
|
|
|
334
341
|
dirs.push('.claude');
|
|
335
342
|
}
|
|
336
343
|
|
|
344
|
+
if (opencodeSetup && config.hookPacks?.length) {
|
|
345
|
+
dirs.push('.opencode/plugins');
|
|
346
|
+
}
|
|
347
|
+
|
|
337
348
|
for (const dir of dirs) {
|
|
338
349
|
mkdirSync(join(agentDir, dir), { recursive: true });
|
|
339
350
|
}
|
|
@@ -570,6 +581,12 @@ export function scaffold(config: AgentConfig): ScaffoldResult {
|
|
|
570
581
|
summaryLines.push(`${config.hookPacks.length} hook pack(s) bundled in .claude/`);
|
|
571
582
|
}
|
|
572
583
|
|
|
584
|
+
if (opencodeSetup && config.hookPacks?.length) {
|
|
585
|
+
summaryLines.push(
|
|
586
|
+
`${config.hookPacks.length} hook pack(s) bundled as OpenCode plugin in .opencode/plugins/`,
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
573
590
|
for (const registration of mcpRegistrations) {
|
|
574
591
|
if (registration.result.registered) {
|
|
575
592
|
summaryLines.push(
|