sdd-toolkit 1.1.0 → 1.6.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 +139 -133
- package/README.pt.md +135 -0
- package/definitions/{dev.coder.yaml → coder.yaml} +71 -59
- package/definitions/feature.yaml +74 -0
- package/definitions/log.yaml +61 -0
- package/definitions/project.yaml +108 -0
- package/definitions/requirements.yaml +71 -0
- package/definitions/review.yaml +88 -0
- package/definitions/sdd.yaml +30 -0
- package/package.json +2 -1
- package/src/commands/view.js +18 -0
- package/src/index.js +240 -112
- package/src/lib/agents.js +21 -21
- package/src/lib/dashboard.js +188 -0
- package/src/lib/docs.js +69 -104
- package/src/lib/i18n.js +65 -0
- package/src/lib/messages.js +234 -0
- package/src/lib/profiles.js +4 -4
- package/src/lib/transformers.js +166 -33
- 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/definitions/dev.auditor.yaml +0 -54
- package/definitions/dev.feature.yaml +0 -108
- package/definitions/dev.log.yaml +0 -51
- package/definitions/dev.milestone.yaml +0 -62
- package/definitions/dev.ops.yaml +0 -39
- package/definitions/dev.project.yaml +0 -91
- package/definitions/dev.requirements.yaml +0 -76
- package/definitions/dev.review.yaml +0 -72
- package/definitions/dev.tasks.yaml +0 -70
package/src/index.js
CHANGED
|
@@ -6,201 +6,329 @@ const path = require('path');
|
|
|
6
6
|
const { intro, outro, multiselect, spinner, note, select, text } = require('@clack/prompts');
|
|
7
7
|
const pc = require('picocolors');
|
|
8
8
|
|
|
9
|
-
//
|
|
9
|
+
// Internal Modules
|
|
10
10
|
const { loadAgents } = require('./lib/agents');
|
|
11
11
|
const { STACK_PROFILES } = require('./lib/profiles');
|
|
12
|
-
const {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const { setLocale, t, getLocale } = require('./lib/i18n');
|
|
13
|
+
const {
|
|
14
|
+
toGeminiTOML,
|
|
15
|
+
toRooConfig,
|
|
16
|
+
toKiloMarkdown,
|
|
16
17
|
toCopilotInstructions,
|
|
17
18
|
toCursorMDC,
|
|
18
19
|
toWindsurfRules,
|
|
20
|
+
toClaudeCommand,
|
|
19
21
|
toPlainSystemPrompt,
|
|
20
|
-
toTraeRules
|
|
22
|
+
toTraeRules,
|
|
23
|
+
toOpenCodeAgent
|
|
21
24
|
} = require('./lib/transformers');
|
|
22
25
|
const { generateWorkflowGuide } = require('./lib/docs');
|
|
26
|
+
const { view } = require('./commands/view');
|
|
23
27
|
|
|
24
28
|
async function main() {
|
|
25
29
|
console.clear();
|
|
26
|
-
intro(pc.bgMagenta(pc.white(' UNIVERSAL SPEC CLI ')));
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
|
|
31
|
+
const args = process.argv.slice(2);
|
|
32
|
+
|
|
33
|
+
if (args[0] === 'view') {
|
|
34
|
+
await view();
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isUpgrade = args.includes('upgrade') || args.includes('--upgrade');
|
|
39
|
+
|
|
40
|
+
// 0. Language Selection
|
|
41
|
+
if (!isUpgrade) {
|
|
42
|
+
intro(pc.bgMagenta(pc.white(' UNIVERSAL SPEC CLI ')));
|
|
43
|
+
|
|
44
|
+
const lang = await select({
|
|
45
|
+
message: 'Select Language / Selecione o Idioma / Seleccione el Idioma',
|
|
46
|
+
options: [
|
|
47
|
+
{ value: 'en', label: 'English' },
|
|
48
|
+
{ value: 'pt_br', label: 'Português (Brasil)' },
|
|
49
|
+
{ value: 'es', label: 'Español' }
|
|
50
|
+
]
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (typeof lang === 'symbol') {
|
|
54
|
+
outro(pc.yellow('Operation cancelled.'));
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setLocale(lang);
|
|
59
|
+
} else {
|
|
60
|
+
setLocale('en');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (isUpgrade) {
|
|
64
|
+
intro(pc.bgBlue(pc.white(t('INTRO.UPGRADE_TITLE'))));
|
|
65
|
+
|
|
66
|
+
const tools = [];
|
|
67
|
+
if (fs.existsSync(path.join(process.cwd(), '.gemini'))) tools.push('gemini');
|
|
68
|
+
if (fs.existsSync(path.join(process.cwd(), '.roo'))) tools.push('roo');
|
|
69
|
+
if (fs.existsSync(path.join(process.cwd(), '.cline'))) tools.push('cline');
|
|
70
|
+
if (fs.existsSync(path.join(process.cwd(), '.cursor'))) tools.push('cursor');
|
|
71
|
+
if (fs.existsSync(path.join(process.cwd(), '.windsurf'))) tools.push('windsurf');
|
|
72
|
+
if (fs.existsSync(path.join(process.cwd(), '.claude'))) tools.push('claude');
|
|
73
|
+
if (fs.existsSync(path.join(process.cwd(), '.trae'))) tools.push('trae');
|
|
74
|
+
if (fs.existsSync(path.join(process.cwd(), '.kilo'))) tools.push('kilo');
|
|
75
|
+
if (fs.existsSync(path.join(process.cwd(), '.github'))) tools.push('copilot');
|
|
76
|
+
if (fs.existsSync(path.join(process.cwd(), '.opencode'))) tools.push('opencode');
|
|
77
|
+
if (fs.existsSync(path.join(process.cwd(), 'prompts'))) tools.push('web');
|
|
78
|
+
|
|
79
|
+
if (tools.length === 0) {
|
|
80
|
+
note(t('UPGRADE.NO_CONFIG'), t('UPGRADE.NO_CONFIG_TITLE'));
|
|
81
|
+
} else {
|
|
82
|
+
note(t('UPGRADE.DETECTED_TOOLS', tools.join(', ')), t('UPGRADE.DETECTED_TITLE'));
|
|
83
|
+
await processAgentsInstallation(tools, { locale: getLocale() });
|
|
84
|
+
outro(pc.green(t('UPGRADE.SUCCESS')));
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
32
87
|
}
|
|
33
88
|
|
|
34
|
-
//
|
|
89
|
+
// 1. Automatic Scaffold
|
|
90
|
+
const s = spinner();
|
|
91
|
+
s.start(t('SCAFFOLD.LOADING'));
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const stats = generateWorkflowGuide(process.cwd());
|
|
95
|
+
if (stats.created > 0) {
|
|
96
|
+
s.stop(`${t('SCAFFOLD.SUCCESS')} (${stats.created} new, ${stats.verified} verified)`);
|
|
97
|
+
} else {
|
|
98
|
+
s.stop(t('SCAFFOLD.ALREADY_EXISTS'));
|
|
99
|
+
}
|
|
100
|
+
} catch (e) {
|
|
101
|
+
s.stop(pc.red(t('SCAFFOLD.ERROR')));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 2. Feature 5: Stack Selection
|
|
35
105
|
const stackOptions = Object.entries(STACK_PROFILES).map(([key, profile]) => ({
|
|
36
106
|
value: key,
|
|
37
107
|
label: profile.label
|
|
38
108
|
}));
|
|
39
109
|
|
|
40
110
|
const stackProfile = await select({
|
|
41
|
-
message: '
|
|
111
|
+
message: t('SETUP.STACK_SELECT'),
|
|
42
112
|
options: stackOptions,
|
|
43
113
|
initialValue: 'generic'
|
|
44
114
|
});
|
|
45
115
|
|
|
46
|
-
if (typeof stackProfile === 'symbol') {
|
|
116
|
+
if (typeof stackProfile === 'symbol') {
|
|
117
|
+
outro(pc.yellow(t('GENERAL.CANCELLED')));
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
47
120
|
|
|
48
|
-
// 3. Feature 3:
|
|
121
|
+
// 3. Feature 3: Global Rules
|
|
49
122
|
const globalRules = await text({
|
|
50
|
-
message: '
|
|
51
|
-
placeholder: '
|
|
123
|
+
message: t('SETUP.GLOBAL_RULES'),
|
|
124
|
+
placeholder: t('SETUP.GLOBAL_RULES_HINT'),
|
|
52
125
|
required: false
|
|
53
126
|
});
|
|
54
127
|
|
|
55
|
-
if (typeof globalRules === 'symbol') {
|
|
128
|
+
if (typeof globalRules === 'symbol') {
|
|
129
|
+
outro(pc.yellow(t('GENERAL.CANCELLED')));
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
56
132
|
|
|
57
|
-
// 4.
|
|
133
|
+
// 4. Tool Selection
|
|
58
134
|
const tools = await multiselect({
|
|
59
|
-
message: '
|
|
135
|
+
message: t('SETUP.TOOL_SELECT'),
|
|
60
136
|
options: [
|
|
61
|
-
{ value: 'gemini', label: '
|
|
62
|
-
{ value: 'roo', label: '
|
|
63
|
-
{ value: 'cline', label: '
|
|
64
|
-
{ value: 'cursor', label: '
|
|
65
|
-
{ value: 'windsurf', label: '
|
|
66
|
-
{ value: '
|
|
67
|
-
{ value: '
|
|
68
|
-
{ value: '
|
|
69
|
-
{ value: '
|
|
70
|
-
{ value: '
|
|
137
|
+
{ value: 'gemini', label: t('TOOLS.GEMINI'), hint: '.gemini/commands/dev' },
|
|
138
|
+
{ value: 'roo', label: t('TOOLS.ROO'), hint: '.roo/ & custom_modes.json' },
|
|
139
|
+
{ value: 'cline', label: t('TOOLS.CLINE'), hint: '.cline/ & custom_modes.json' },
|
|
140
|
+
{ value: 'cursor', label: t('TOOLS.CURSOR'), hint: '.cursor/rules/*.mdc' },
|
|
141
|
+
{ value: 'windsurf', label: t('TOOLS.WINDSURF'), hint: '.windsurf/workflows/*.md' },
|
|
142
|
+
{ value: 'claude', label: 'Claude Code', hint: '.claude/commands/openspec/*.md' },
|
|
143
|
+
{ value: 'trae', label: t('TOOLS.TRAE'), hint: '.trae/instructions.md' },
|
|
144
|
+
{ value: 'kilo', label: t('TOOLS.KILO'), hint: '.kilo/prompts/*.md' },
|
|
145
|
+
{ value: 'copilot', label: t('TOOLS.COPILOT'), hint: '.github/copilot-instructions.md' },
|
|
146
|
+
{ value: 'web', label: t('TOOLS.WEB'), hint: 'prompts/*.txt' },
|
|
147
|
+
{ value: 'opencode', label: t('TOOLS.OPENCODE'), hint: '.opencode/*.md' }
|
|
71
148
|
],
|
|
72
149
|
required: true,
|
|
73
|
-
hint: '
|
|
150
|
+
hint: t('SETUP.TOOL_HINT')
|
|
74
151
|
});
|
|
75
152
|
|
|
153
|
+
if (typeof tools === 'symbol') {
|
|
154
|
+
outro(pc.yellow(t('GENERAL.CANCELLED')));
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
|
|
76
158
|
if (!tools || tools.length === 0) {
|
|
77
|
-
outro('
|
|
159
|
+
outro(t('SETUP.NO_TOOLS'));
|
|
78
160
|
process.exit(0);
|
|
79
161
|
}
|
|
80
162
|
|
|
81
|
-
await processAgentsInstallation(tools, { stackProfile, globalRules });
|
|
163
|
+
await processAgentsInstallation(tools, { stackProfile, globalRules, locale: getLocale() });
|
|
82
164
|
|
|
83
|
-
outro(pc.green('
|
|
165
|
+
outro(pc.green(t('SETUP.SUCCESS')));
|
|
84
166
|
}
|
|
85
167
|
|
|
86
168
|
async function processAgentsInstallation(tools, options) {
|
|
87
169
|
const s = spinner();
|
|
88
|
-
s.start('
|
|
170
|
+
s.start(t('INSTALL.LOADING'));
|
|
89
171
|
|
|
90
172
|
try {
|
|
91
173
|
const validAgents = await loadAgents(options);
|
|
92
174
|
|
|
93
175
|
if (validAgents.length === 0) {
|
|
94
|
-
s.stop('
|
|
176
|
+
s.stop(t('INSTALL.NO_AGENTS'));
|
|
95
177
|
return;
|
|
96
178
|
}
|
|
97
179
|
|
|
98
|
-
s.message(
|
|
180
|
+
s.message(t('INSTALL.INSTALLING', tools.join(', ')));
|
|
99
181
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// Instalação Específica por Ferramenta
|
|
104
|
-
if (tool === 'gemini') {
|
|
182
|
+
const toolHandlers = {
|
|
183
|
+
gemini: async (validAgents, options) => {
|
|
105
184
|
const targetDir = path.join(process.cwd(), '.gemini', 'commands', 'dev');
|
|
106
185
|
await fsp.mkdir(targetDir, { recursive: true });
|
|
107
186
|
|
|
108
|
-
await Promise.all(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
187
|
+
await Promise.all(
|
|
188
|
+
validAgents.map((agent) => {
|
|
189
|
+
const toml = toGeminiTOML(agent, options);
|
|
190
|
+
const fileName = `${agent.originalName}.toml`;
|
|
191
|
+
return fsp.writeFile(path.join(targetDir, fileName), toml);
|
|
192
|
+
})
|
|
193
|
+
);
|
|
194
|
+
},
|
|
195
|
+
roo: async (validAgents, options) => {
|
|
196
|
+
const targetDir = path.join(process.cwd(), '.roo');
|
|
117
197
|
await fsp.mkdir(targetDir, { recursive: true });
|
|
118
198
|
|
|
119
|
-
await Promise.all(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
199
|
+
await Promise.all(
|
|
200
|
+
validAgents.map((agent) => {
|
|
201
|
+
const md = toKiloMarkdown(agent, options);
|
|
202
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
|
|
203
|
+
})
|
|
204
|
+
);
|
|
123
205
|
|
|
124
|
-
const modes = validAgents.map(agent => toRooConfig(agent, agent.slug));
|
|
206
|
+
const modes = validAgents.map((agent) => toRooConfig(agent, agent.slug, options));
|
|
125
207
|
const jsonContent = JSON.stringify({ customModes: modes }, null, 2);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
208
|
+
await fsp.writeFile(path.join(process.cwd(), 'roo_custom_modes.json'), jsonContent);
|
|
209
|
+
},
|
|
210
|
+
cline: async (validAgents, options) => {
|
|
211
|
+
const targetDir = path.join(process.cwd(), '.cline');
|
|
212
|
+
await fsp.mkdir(targetDir, { recursive: true });
|
|
213
|
+
|
|
214
|
+
await Promise.all(
|
|
215
|
+
validAgents.map((agent) => {
|
|
216
|
+
const md = toKiloMarkdown(agent, options);
|
|
217
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const modes = validAgents.map((agent) => toRooConfig(agent, agent.slug, options));
|
|
222
|
+
const jsonContent = JSON.stringify({ customModes: modes }, null, 2);
|
|
223
|
+
await fsp.writeFile(path.join(process.cwd(), 'cline_custom_modes.json'), jsonContent);
|
|
224
|
+
},
|
|
225
|
+
windsurf: async (validAgents, options) => {
|
|
226
|
+
const targetDir = path.join(process.cwd(), '.windsurf', 'workflows');
|
|
227
|
+
await fsp.mkdir(targetDir, { recursive: true });
|
|
228
|
+
|
|
229
|
+
await Promise.all(
|
|
230
|
+
validAgents.map((agent) => {
|
|
231
|
+
const md = toWindsurfRules(agent, options);
|
|
232
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
},
|
|
236
|
+
claude: async (validAgents, options) => {
|
|
237
|
+
const targetDir = path.join(process.cwd(), '.claude', 'commands', 'openspec');
|
|
238
|
+
await fsp.mkdir(targetDir, { recursive: true });
|
|
239
|
+
|
|
240
|
+
await Promise.all(
|
|
241
|
+
validAgents.map((agent) => {
|
|
242
|
+
const md = toClaudeCommand(agent, options);
|
|
243
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
},
|
|
247
|
+
cursor: async (validAgents, options) => {
|
|
248
|
+
const rulesDir = path.join(process.cwd(), '.cursor', 'rules');
|
|
249
|
+
await fsp.mkdir(rulesDir, { recursive: true });
|
|
250
|
+
|
|
251
|
+
await Promise.all(
|
|
252
|
+
validAgents.map((agent) => {
|
|
253
|
+
const mdc = toCursorMDC(agent, options);
|
|
254
|
+
return fsp.writeFile(path.join(rulesDir, `${agent.slug}.mdc`), mdc);
|
|
255
|
+
})
|
|
256
|
+
);
|
|
257
|
+
},
|
|
258
|
+
kilo: async (validAgents, options) => {
|
|
130
259
|
const targetDir = path.join(process.cwd(), '.kilo', 'prompts');
|
|
131
260
|
await fsp.mkdir(targetDir, { recursive: true });
|
|
132
261
|
|
|
133
|
-
await Promise.all(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
262
|
+
await Promise.all(
|
|
263
|
+
validAgents.map((agent) => {
|
|
264
|
+
const md = toKiloMarkdown(agent, options);
|
|
265
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
|
|
266
|
+
})
|
|
267
|
+
);
|
|
268
|
+
},
|
|
269
|
+
copilot: async (validAgents, options) => {
|
|
139
270
|
const githubDir = path.join(process.cwd(), '.github');
|
|
140
271
|
const agentsDir = path.join(githubDir, 'agents');
|
|
141
272
|
await fsp.mkdir(agentsDir, { recursive: true });
|
|
142
273
|
|
|
143
|
-
await Promise.all(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
274
|
+
await Promise.all(
|
|
275
|
+
validAgents.map((agent) => {
|
|
276
|
+
const md = toCopilotInstructions(agent, options);
|
|
277
|
+
return fsp.writeFile(path.join(agentsDir, `${agent.slug}.md`), md);
|
|
278
|
+
})
|
|
279
|
+
);
|
|
147
280
|
|
|
148
|
-
const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
|
|
149
|
-
const mainInstructions = toCopilotInstructions(mainAgent);
|
|
281
|
+
const mainAgent = validAgents.find((a) => a.slug.includes('coder')) || validAgents[0];
|
|
282
|
+
const mainInstructions = toCopilotInstructions(mainAgent, options);
|
|
150
283
|
await fsp.writeFile(path.join(githubDir, 'copilot-instructions.md'), mainInstructions);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const rulesDir = path.join(process.cwd(), '.cursor', 'rules');
|
|
154
|
-
await fsp.mkdir(rulesDir, { recursive: true });
|
|
155
|
-
|
|
156
|
-
await Promise.all(validAgents.map(agent => {
|
|
157
|
-
const mdc = toCursorMDC(agent);
|
|
158
|
-
return fsp.writeFile(path.join(rulesDir, `${agent.slug}.mdc`), mdc);
|
|
159
|
-
}));
|
|
160
|
-
}
|
|
161
|
-
else if (tool === 'windsurf') {
|
|
162
|
-
const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
|
|
163
|
-
const rules = toWindsurfRules(mainAgent);
|
|
164
|
-
await fsp.writeFile(path.join(process.cwd(), '.windsurfrules'), rules);
|
|
165
|
-
}
|
|
166
|
-
else if (tool === 'trae') {
|
|
284
|
+
},
|
|
285
|
+
trae: async (validAgents, options) => {
|
|
167
286
|
const traeDir = path.join(process.cwd(), '.trae');
|
|
168
287
|
await fsp.mkdir(traeDir, { recursive: true });
|
|
169
|
-
|
|
170
|
-
const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
|
|
171
|
-
const rules = toTraeRules(mainAgent);
|
|
288
|
+
|
|
289
|
+
const mainAgent = validAgents.find((a) => a.slug.includes('coder')) || validAgents[0];
|
|
290
|
+
const rules = toTraeRules(mainAgent, options);
|
|
172
291
|
await fsp.writeFile(path.join(traeDir, 'instructions.md'), rules);
|
|
173
|
-
}
|
|
174
|
-
|
|
292
|
+
},
|
|
293
|
+
web: async (validAgents, options) => {
|
|
175
294
|
const targetDir = path.join(process.cwd(), 'prompts');
|
|
176
295
|
await fsp.mkdir(targetDir, { recursive: true });
|
|
177
296
|
|
|
178
|
-
await Promise.all(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
297
|
+
await Promise.all(
|
|
298
|
+
validAgents.map((agent) => {
|
|
299
|
+
const txt = toPlainSystemPrompt(agent, options);
|
|
300
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.txt`), txt);
|
|
301
|
+
})
|
|
302
|
+
);
|
|
303
|
+
},
|
|
304
|
+
opencode: async (validAgents, options) => {
|
|
305
|
+
const targetDir = path.join(process.cwd(), '.opencode', 'agent');
|
|
185
306
|
await fsp.mkdir(targetDir, { recursive: true });
|
|
186
307
|
|
|
187
|
-
await Promise.all(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
308
|
+
await Promise.all(
|
|
309
|
+
validAgents.map((agent) => {
|
|
310
|
+
const md = toOpenCodeAgent(agent, options);
|
|
311
|
+
return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
|
|
312
|
+
})
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
for (const tool of tools) {
|
|
318
|
+
const handler = toolHandlers[tool];
|
|
319
|
+
if (handler) {
|
|
320
|
+
await handler(validAgents, options);
|
|
191
321
|
}
|
|
192
322
|
}
|
|
193
|
-
|
|
194
|
-
s.stop('
|
|
195
|
-
|
|
196
|
-
// Feedback consolidado
|
|
323
|
+
|
|
324
|
+
s.stop(t('INSTALL.FINISHED'));
|
|
325
|
+
|
|
197
326
|
if (tools.includes('roo') || tools.includes('cline')) {
|
|
198
|
-
note('
|
|
327
|
+
note(t('INSTALL.ROO_WARNING'), t('INSTALL.ROO_WARNING_TITLE'));
|
|
199
328
|
}
|
|
200
|
-
|
|
201
329
|
} catch (e) {
|
|
202
|
-
s.stop('
|
|
203
|
-
|
|
330
|
+
s.stop(pc.red(`${t('INSTALL.FAILED')}: ${e.message}`));
|
|
331
|
+
process.exit(1);
|
|
204
332
|
}
|
|
205
333
|
}
|
|
206
334
|
|
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(), '.sdd', 'agents');
|
|
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
|
|