sdd-toolkit 1.1.0 → 1.5.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/README.md +59 -37
- package/definitions/dev.auditor.yaml +61 -49
- package/definitions/dev.coder.yaml +78 -59
- package/definitions/dev.feature.yaml +105 -108
- package/definitions/dev.log.yaml +90 -51
- package/definitions/dev.milestone.yaml +75 -62
- package/definitions/dev.ops.yaml +51 -39
- package/definitions/dev.project.yaml +106 -91
- package/definitions/dev.requirements.yaml +91 -76
- package/definitions/dev.review.yaml +61 -72
- package/definitions/dev.tasks.yaml +84 -70
- package/package.json +2 -1
- package/src/index.js +284 -207
- package/src/lib/agents.js +21 -21
- package/src/lib/docs.js +68 -104
- package/src/lib/i18n.js +65 -0
- package/src/lib/messages.js +186 -0
- package/src/lib/profiles.js +4 -4
- package/src/lib/transformers.js +246 -199
- package/src/scripts/archive.js +56 -0
- package/src/scripts/reset.js +19 -0
- package/src/scripts/status.js +58 -0
- package/templates/guidelines.md +9 -0
- package/templates/milestones.md +14 -0
- package/templates/project.md +28 -0
- package/templates/requirements.md +15 -0
- package/templates/task.md +11 -0
package/src/index.js
CHANGED
|
@@ -1,207 +1,284 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const fsp = require('fs/promises');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const { intro, outro, multiselect, spinner, note, select, text } = require('@clack/prompts');
|
|
7
|
-
const pc = require('picocolors');
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
const { loadAgents } = require('./lib/agents');
|
|
11
|
-
const { STACK_PROFILES } = require('./lib/profiles');
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
await
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const fsp = require('fs/promises');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { intro, outro, multiselect, spinner, note, select, text } = require('@clack/prompts');
|
|
7
|
+
const pc = require('picocolors');
|
|
8
|
+
|
|
9
|
+
// Internal Modules
|
|
10
|
+
const { loadAgents } = require('./lib/agents');
|
|
11
|
+
const { STACK_PROFILES } = require('./lib/profiles');
|
|
12
|
+
const { setLocale, t, getLocale } = require('./lib/i18n');
|
|
13
|
+
const {
|
|
14
|
+
toGeminiTOML,
|
|
15
|
+
toRooConfig,
|
|
16
|
+
toKiloMarkdown,
|
|
17
|
+
toCopilotInstructions,
|
|
18
|
+
toCursorMDC,
|
|
19
|
+
toWindsurfRules,
|
|
20
|
+
toPlainSystemPrompt,
|
|
21
|
+
toTraeRules
|
|
22
|
+
} = require('./lib/transformers');
|
|
23
|
+
const { generateWorkflowGuide } = require('./lib/docs');
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
console.clear();
|
|
27
|
+
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const isUpgrade = args.includes('upgrade') || args.includes('--upgrade');
|
|
30
|
+
|
|
31
|
+
// 0. Language Selection (Always first unless forced upgrade silent mode - but upgrade has interaction)
|
|
32
|
+
// We show this before Upgrade title to ensure upgrade messages are localized too if possible
|
|
33
|
+
// But typically flags like --lang would be better. For now, interactive.
|
|
34
|
+
|
|
35
|
+
if (!isUpgrade) {
|
|
36
|
+
intro(pc.bgMagenta(pc.white(' UNIVERSAL SPEC CLI ')));
|
|
37
|
+
|
|
38
|
+
const lang = await select({
|
|
39
|
+
message: 'Select Language / Selecione o Idioma / Seleccione el Idioma',
|
|
40
|
+
options: [
|
|
41
|
+
{ value: 'en', label: 'English' },
|
|
42
|
+
{ value: 'pt_br', label: 'Português (Brasil)' },
|
|
43
|
+
{ value: 'es', label: 'Español' }
|
|
44
|
+
]
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (typeof lang === 'symbol') {
|
|
48
|
+
outro(pc.yellow('Operation cancelled.'));
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setLocale(lang);
|
|
53
|
+
} else {
|
|
54
|
+
// Default EN for upgrade for now
|
|
55
|
+
setLocale('en');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (isUpgrade) {
|
|
59
|
+
intro(pc.bgBlue(pc.white(t('INTRO.UPGRADE_TITLE'))));
|
|
60
|
+
|
|
61
|
+
// Detecção de Ferramentas Existentes
|
|
62
|
+
const tools = [];
|
|
63
|
+
if (fs.existsSync(path.join(process.cwd(), '.gemini'))) tools.push('gemini');
|
|
64
|
+
if (fs.existsSync(path.join(process.cwd(), '.roo'))) tools.push('roo');
|
|
65
|
+
if (fs.existsSync(path.join(process.cwd(), '.cline'))) tools.push('cline');
|
|
66
|
+
if (fs.existsSync(path.join(process.cwd(), '.cursor'))) tools.push('cursor');
|
|
67
|
+
if (fs.existsSync(path.join(process.cwd(), '.windsurfrules'))) tools.push('windsurf');
|
|
68
|
+
if (fs.existsSync(path.join(process.cwd(), '.trae'))) tools.push('trae');
|
|
69
|
+
if (fs.existsSync(path.join(process.cwd(), '.kilo'))) tools.push('kilo');
|
|
70
|
+
if (fs.existsSync(path.join(process.cwd(), '.github'))) tools.push('copilot'); // Assume copilot se .github existir
|
|
71
|
+
if (fs.existsSync(path.join(process.cwd(), '.opencode'))) tools.push('opencode');
|
|
72
|
+
if (fs.existsSync(path.join(process.cwd(), 'prompts'))) tools.push('web');
|
|
73
|
+
|
|
74
|
+
if (tools.length === 0) {
|
|
75
|
+
note(t('UPGRADE.NO_CONFIG'), t('UPGRADE.NO_CONFIG_TITLE'));
|
|
76
|
+
} else {
|
|
77
|
+
note(t('UPGRADE.DETECTED_TOOLS', tools.join(', ')), t('UPGRADE.DETECTED_TITLE'));
|
|
78
|
+
await processAgentsInstallation(tools, { locale: getLocale() });
|
|
79
|
+
outro(pc.green(t('UPGRADE.SUCCESS')));
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 1. Automatic Scaffold
|
|
85
|
+
const s = spinner();
|
|
86
|
+
s.start(t('SCAFFOLD.LOADING'));
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const stats = generateWorkflowGuide(process.cwd());
|
|
90
|
+
if (stats.created > 0) {
|
|
91
|
+
s.stop(`${t('SCAFFOLD.SUCCESS')} (${stats.created} new, ${stats.verified} verified)`);
|
|
92
|
+
} else {
|
|
93
|
+
s.stop(t('SCAFFOLD.ALREADY_EXISTS'));
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
s.stop(pc.red(t('SCAFFOLD.ERROR')));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 2. Feature 5: Stack Selection (Profile)
|
|
100
|
+
const stackOptions = Object.entries(STACK_PROFILES).map(([key, profile]) => ({
|
|
101
|
+
value: key,
|
|
102
|
+
label: profile.label
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
const stackProfile = await select({
|
|
106
|
+
message: t('SETUP.STACK_SELECT'),
|
|
107
|
+
options: stackOptions,
|
|
108
|
+
initialValue: 'generic'
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (typeof stackProfile === 'symbol') {
|
|
112
|
+
outro(pc.yellow(t('GENERAL.CANCELLED')));
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 3. Feature 3: Global Rules (Optional)
|
|
117
|
+
const globalRules = await text({
|
|
118
|
+
message: t('SETUP.GLOBAL_RULES'),
|
|
119
|
+
placeholder: t('SETUP.GLOBAL_RULES_HINT'),
|
|
120
|
+
required: false
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (typeof globalRules === 'symbol') {
|
|
124
|
+
outro(pc.yellow(t('GENERAL.CANCELLED')));
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 4. Tool Selection (Multiple choice)
|
|
129
|
+
const tools = await multiselect({
|
|
130
|
+
message: t('SETUP.TOOL_SELECT'),
|
|
131
|
+
options: [
|
|
132
|
+
{ value: 'gemini', label: t('TOOLS.GEMINI'), hint: '.gemini/commands/dev' },
|
|
133
|
+
{ value: 'roo', label: t('TOOLS.ROO'), hint: '.roo/ & custom_modes.json' },
|
|
134
|
+
{ value: 'cline', label: t('TOOLS.CLINE'), hint: '.cline/ & custom_modes.json' },
|
|
135
|
+
{ value: 'cursor', label: t('TOOLS.CURSOR'), hint: '.cursor/rules/*.mdc' },
|
|
136
|
+
{ value: 'windsurf', label: t('TOOLS.WINDSURF'), hint: '.windsurfrules' },
|
|
137
|
+
{ value: 'trae', label: t('TOOLS.TRAE'), hint: '.trae/instructions.md' },
|
|
138
|
+
{ value: 'kilo', label: t('TOOLS.KILO'), hint: '.kilo/prompts/*.md' },
|
|
139
|
+
{ value: 'copilot', label: t('TOOLS.COPILOT'), hint: '.github/copilot-instructions.md' },
|
|
140
|
+
{ value: 'web', label: t('TOOLS.WEB'), hint: 'prompts/*.txt' },
|
|
141
|
+
{ value: 'opencode', label: t('TOOLS.OPENCODE'), hint: '.opencode/*.md' },
|
|
142
|
+
],
|
|
143
|
+
required: true,
|
|
144
|
+
hint: t('SETUP.TOOL_HINT')
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (typeof tools === 'symbol') {
|
|
148
|
+
outro(pc.yellow(t('GENERAL.CANCELLED')));
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!tools || tools.length === 0) {
|
|
153
|
+
outro(t('SETUP.NO_TOOLS'));
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Pass locale to installation process
|
|
158
|
+
await processAgentsInstallation(tools, { stackProfile, globalRules, locale: getLocale() });
|
|
159
|
+
|
|
160
|
+
outro(pc.green(t('SETUP.SUCCESS')));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function processAgentsInstallation(tools, options) {
|
|
164
|
+
const s = spinner();
|
|
165
|
+
s.start(t('INSTALL.LOADING'));
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const validAgents = await loadAgents(options);
|
|
169
|
+
|
|
170
|
+
if (validAgents.length === 0) {
|
|
171
|
+
s.stop(t('INSTALL.NO_AGENTS'));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
s.message(t('INSTALL.INSTALLING', tools.join(', ')));
|
|
176
|
+
|
|
177
|
+
// Iterate over each selected tool
|
|
178
|
+
for (const tool of tools) {
|
|
179
|
+
|
|
180
|
+
// Tool-Specific Installation
|
|
181
|
+
if (tool === 'gemini') {
|
|
182
|
+
const targetDir = path.join(process.cwd(), '.gemini', 'commands', 'dev');
|
|
183
|
+
await fsp.mkdir(targetDir, { recursive: true });
|
|
184
|
+
|
|
185
|
+
await Promise.all(validAgents.map(agent => {
|
|
186
|
+
const toml = toGeminiTOML(agent, options);
|
|
187
|
+
const fileName = `${agent.originalName}.toml`;
|
|
188
|
+
return fsp.writeFile(path.join(targetDir, fileName), toml);
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
else if (tool === 'roo' || tool === 'cline') {
|
|
192
|
+
const configDir = tool === 'roo' ? '.roo' : '.cline';
|
|
193
|
+
const targetDir = path.join(process.cwd(), configDir);
|
|
194
|
+
await fsp.mkdir(targetDir, { recursive: true });
|
|
195
|
+
|
|
196
|
+
await Promise.all(validAgents.map(agent => {
|
|
197
|
+
const md = toKiloMarkdown(agent, options);
|
|
198
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
const modes = validAgents.map(agent => toRooConfig(agent, agent.slug, options));
|
|
202
|
+
const jsonContent = JSON.stringify({ customModes: modes }, null, 2);
|
|
203
|
+
const fileName = `${tool}_custom_modes.json`;
|
|
204
|
+
await fsp.writeFile(path.join(process.cwd(), fileName), jsonContent);
|
|
205
|
+
}
|
|
206
|
+
else if (tool === 'kilo') {
|
|
207
|
+
const targetDir = path.join(process.cwd(), '.kilo', 'prompts');
|
|
208
|
+
await fsp.mkdir(targetDir, { recursive: true });
|
|
209
|
+
|
|
210
|
+
await Promise.all(validAgents.map(agent => {
|
|
211
|
+
const md = toKiloMarkdown(agent, options);
|
|
212
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
|
|
213
|
+
}));
|
|
214
|
+
}
|
|
215
|
+
else if (tool === 'copilot') {
|
|
216
|
+
const githubDir = path.join(process.cwd(), '.github');
|
|
217
|
+
const agentsDir = path.join(githubDir, 'agents');
|
|
218
|
+
await fsp.mkdir(agentsDir, { recursive: true });
|
|
219
|
+
|
|
220
|
+
await Promise.all(validAgents.map(agent => {
|
|
221
|
+
const md = toCopilotInstructions(agent, options);
|
|
222
|
+
return fsp.writeFile(path.join(agentsDir, `${agent.slug}.md`), md);
|
|
223
|
+
}));
|
|
224
|
+
|
|
225
|
+
const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
|
|
226
|
+
const mainInstructions = toCopilotInstructions(mainAgent, options);
|
|
227
|
+
await fsp.writeFile(path.join(githubDir, 'copilot-instructions.md'), mainInstructions);
|
|
228
|
+
}
|
|
229
|
+
else if (tool === 'cursor') {
|
|
230
|
+
const rulesDir = path.join(process.cwd(), '.cursor', 'rules');
|
|
231
|
+
await fsp.mkdir(rulesDir, { recursive: true });
|
|
232
|
+
|
|
233
|
+
await Promise.all(validAgents.map(agent => {
|
|
234
|
+
const mdc = toCursorMDC(agent, options);
|
|
235
|
+
return fsp.writeFile(path.join(rulesDir, `${agent.slug}.mdc`), mdc);
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
else if (tool === 'windsurf') {
|
|
239
|
+
const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
|
|
240
|
+
const rules = toWindsurfRules(mainAgent, options);
|
|
241
|
+
await fsp.writeFile(path.join(process.cwd(), '.windsurfrules'), rules);
|
|
242
|
+
}
|
|
243
|
+
else if (tool === 'trae') {
|
|
244
|
+
const traeDir = path.join(process.cwd(), '.trae');
|
|
245
|
+
await fsp.mkdir(traeDir, { recursive: true });
|
|
246
|
+
|
|
247
|
+
const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
|
|
248
|
+
const rules = toTraeRules(mainAgent, options);
|
|
249
|
+
await fsp.writeFile(path.join(traeDir, 'instructions.md'), rules);
|
|
250
|
+
}
|
|
251
|
+
else if (tool === 'web') {
|
|
252
|
+
const targetDir = path.join(process.cwd(), 'prompts');
|
|
253
|
+
await fsp.mkdir(targetDir, { recursive: true });
|
|
254
|
+
|
|
255
|
+
await Promise.all(validAgents.map(agent => {
|
|
256
|
+
const txt = toPlainSystemPrompt(agent, options);
|
|
257
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.txt`), txt);
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
else if (tool === 'opencode') {
|
|
261
|
+
const targetDir = path.join(process.cwd(), '.opencode');
|
|
262
|
+
await fsp.mkdir(targetDir, { recursive: true });
|
|
263
|
+
|
|
264
|
+
await Promise.all(validAgents.map(agent => {
|
|
265
|
+
const md = toKiloMarkdown(agent, options);
|
|
266
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
s.stop(t('INSTALL.FINISHED'));
|
|
272
|
+
|
|
273
|
+
// Consolidated feedback
|
|
274
|
+
if (tools.includes('roo') || tools.includes('cline')) {
|
|
275
|
+
note(t('INSTALL.ROO_WARNING'), t('INSTALL.ROO_WARNING_TITLE'));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
} catch (e) {
|
|
279
|
+
s.stop(pc.red(`${t('INSTALL.FAILED')}: ${e.message}`));
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
main().catch(console.error);
|
package/src/lib/agents.js
CHANGED
|
@@ -6,45 +6,45 @@ const { STACK_PROFILES } = require('./profiles');
|
|
|
6
6
|
const pc = require('picocolors');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Loads and validates all agent definitions from the definitions folder.
|
|
10
|
+
* Supports local override (sdd-toolkit/agents) and rule injection.
|
|
11
11
|
*
|
|
12
12
|
* @param {Object} options
|
|
13
|
-
* @param {string} options.stackProfile -
|
|
14
|
-
* @param {string} options.globalRules -
|
|
15
|
-
* @returns {Promise<Array>}
|
|
13
|
+
* @param {string} options.stackProfile - Stack profile key (e.g., 'frontend-react')
|
|
14
|
+
* @param {string} options.globalRules - Global rules separated by newlines
|
|
15
|
+
* @returns {Promise<Array>} List of validated agents
|
|
16
16
|
*/
|
|
17
17
|
async function loadAgents(options = {}) {
|
|
18
18
|
const definitionsDir = path.join(__dirname, '..', '..', 'definitions');
|
|
19
|
-
const localDefinitionsDir = path.join(process.cwd(), '
|
|
19
|
+
const localDefinitionsDir = path.join(process.cwd(), 'sdd-toolkit', 'agents');
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// Check default directory
|
|
22
22
|
try {
|
|
23
23
|
await fs.access(definitionsDir);
|
|
24
24
|
} catch {
|
|
25
|
-
throw new Error(`
|
|
25
|
+
throw new Error(`Default definitions folder not found: ${definitionsDir}`);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
//
|
|
28
|
+
// Identify default files
|
|
29
29
|
const files = await fs.readdir(definitionsDir);
|
|
30
30
|
const yamlFiles = files.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
31
31
|
|
|
32
|
-
//
|
|
32
|
+
// Check if local override directory exists
|
|
33
33
|
let hasLocalOverrides = false;
|
|
34
34
|
try {
|
|
35
35
|
await fs.access(localDefinitionsDir);
|
|
36
36
|
hasLocalOverrides = true;
|
|
37
37
|
} catch {
|
|
38
|
-
//
|
|
38
|
+
// Ignore if it doesn't exist
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
//
|
|
41
|
+
// Parallel reading and processing
|
|
42
42
|
const results = await Promise.all(yamlFiles.map(async (file) => {
|
|
43
43
|
try {
|
|
44
44
|
let content;
|
|
45
45
|
let source = 'default';
|
|
46
46
|
|
|
47
|
-
// Feature 2:
|
|
47
|
+
// Feature 2: Local Override
|
|
48
48
|
if (hasLocalOverrides) {
|
|
49
49
|
try {
|
|
50
50
|
const localPath = path.join(localDefinitionsDir, file);
|
|
@@ -52,7 +52,7 @@ async function loadAgents(options = {}) {
|
|
|
52
52
|
content = await fs.readFile(localPath, 'utf8');
|
|
53
53
|
source = 'local';
|
|
54
54
|
} catch {
|
|
55
|
-
//
|
|
55
|
+
// Local file doesn't exist, use default
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -67,19 +67,19 @@ async function loadAgents(options = {}) {
|
|
|
67
67
|
return {
|
|
68
68
|
success: false,
|
|
69
69
|
file,
|
|
70
|
-
error: '
|
|
70
|
+
error: 'Schema validation failed',
|
|
71
71
|
details: parsed.error.format()
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const agent = parsed.data;
|
|
76
76
|
|
|
77
|
-
//
|
|
77
|
+
// Normalize slug
|
|
78
78
|
agent.slug = file.replace(/\.ya?ml$/, '').replace(/\./g, '-');
|
|
79
79
|
agent.originalName = file.replace(/\.ya?ml$/, '');
|
|
80
|
-
agent.source = source; //
|
|
80
|
+
agent.source = source; // Useful metadata for logs
|
|
81
81
|
|
|
82
|
-
// Feature 5:
|
|
82
|
+
// Feature 5: Stack Rules Injection
|
|
83
83
|
if (options.stackProfile && STACK_PROFILES[options.stackProfile]) {
|
|
84
84
|
const stackRules = STACK_PROFILES[options.stackProfile].rules;
|
|
85
85
|
if (stackRules && stackRules.length > 0) {
|
|
@@ -87,7 +87,7 @@ async function loadAgents(options = {}) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// Feature 3:
|
|
90
|
+
// Feature 3: Global Rules Injection
|
|
91
91
|
if (options.globalRules && typeof options.globalRules === 'string') {
|
|
92
92
|
const customRules = options.globalRules
|
|
93
93
|
.split('\n')
|
|
@@ -106,7 +106,7 @@ async function loadAgents(options = {}) {
|
|
|
106
106
|
}
|
|
107
107
|
}));
|
|
108
108
|
|
|
109
|
-
//
|
|
109
|
+
// Separate successes and failures
|
|
110
110
|
const validAgents = [];
|
|
111
111
|
const errors = [];
|
|
112
112
|
|
|
@@ -115,7 +115,7 @@ async function loadAgents(options = {}) {
|
|
|
115
115
|
validAgents.push(res.agent);
|
|
116
116
|
} else {
|
|
117
117
|
errors.push(res);
|
|
118
|
-
console.warn(pc.yellow(`⚠️
|
|
118
|
+
console.warn(pc.yellow(`⚠️ Skipping ${res.file}: ${res.error}`));
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
121
|
|