@brunosps00/dev-workflow 1.0.2 → 1.0.5
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 +74 -20
- package/bin/dev-workflow.js +53 -10
- package/lib/agents.js +410 -0
- package/lib/aws-categories.js +80 -0
- package/lib/azure-categories.js +168 -0
- package/lib/constants.js +16 -0
- package/lib/doctor.js +213 -0
- package/lib/init.js +96 -8
- package/lib/install-aws-skills.js +345 -0
- package/lib/install-azure-skills.js +231 -0
- package/lib/install-state.js +57 -0
- package/lib/list-installed.js +27 -0
- package/lib/mcp.js +32 -21
- package/lib/migrate-skills.js +26 -2
- package/lib/profiles.js +44 -0
- package/lib/prompts.js +38 -1
- package/lib/skill-registry.js +109 -0
- package/lib/subtasks.js +264 -0
- package/lib/uninstall.js +21 -12
- package/lib/utils.js +42 -1
- package/lib/wrappers.js +2 -1
- package/package.json +1 -1
- package/scaffold/agent-registry.json +272 -0
- package/scaffold/agents/core/dw-build-fixer.md +21 -0
- package/scaffold/agents/core/dw-code-explorer.md +32 -0
- package/scaffold/agents/core/dw-code-reviewer.md +28 -0
- package/scaffold/agents/core/dw-planner.md +23 -0
- package/scaffold/agents/core/dw-test-author.md +21 -0
- package/scaffold/agents/csharp/dw-csharp-reviewer.md +15 -0
- package/scaffold/agents/csharp/dw-dotnet-build-fixer.md +15 -0
- package/scaffold/agents/frontend/dw-qa-runner.md +21 -0
- package/scaffold/agents/python/dw-python-build-fixer.md +15 -0
- package/scaffold/agents/python/dw-python-reviewer.md +15 -0
- package/scaffold/agents/rust/dw-rust-build-fixer.md +15 -0
- package/scaffold/agents/rust/dw-rust-reviewer.md +15 -0
- package/scaffold/agents/security/dw-security-reviewer.md +24 -0
- package/scaffold/agents/security/dw-silent-failure-hunter.md +22 -0
- package/scaffold/agents/typescript/dw-typescript-build-fixer.md +15 -0
- package/scaffold/agents/typescript/dw-typescript-reviewer.md +15 -0
- package/scaffold/en/agent-instructions.md +46 -1
- package/scaffold/en/commands/dw-bugfix.md +9 -0
- package/scaffold/en/commands/dw-context-budget.md +28 -0
- package/scaffold/en/commands/dw-harness-audit.md +31 -0
- package/scaffold/en/commands/dw-help.md +15 -5
- package/scaffold/en/commands/dw-install-aws-skills.md +166 -0
- package/scaffold/en/commands/dw-install-azure-skills.md +138 -0
- package/scaffold/en/commands/dw-intel.md +6 -0
- package/scaffold/en/commands/dw-plan.md +11 -0
- package/scaffold/en/commands/dw-review.md +12 -0
- package/scaffold/en/commands/dw-skill-health.md +29 -0
- package/scaffold/en/commands/dw-subtask-complete.md +40 -0
- package/scaffold/en/commands/dw-subtask-resume.md +21 -0
- package/scaffold/en/commands/dw-subtask-start.md +22 -0
- package/scaffold/pt-br/agent-instructions.md +46 -1
- package/scaffold/pt-br/commands/dw-bugfix.md +9 -0
- package/scaffold/pt-br/commands/dw-context-budget.md +28 -0
- package/scaffold/pt-br/commands/dw-harness-audit.md +31 -0
- package/scaffold/pt-br/commands/dw-help.md +15 -5
- package/scaffold/pt-br/commands/dw-install-aws-skills.md +166 -0
- package/scaffold/pt-br/commands/dw-install-azure-skills.md +138 -0
- package/scaffold/pt-br/commands/dw-intel.md +6 -0
- package/scaffold/pt-br/commands/dw-plan.md +11 -0
- package/scaffold/pt-br/commands/dw-review.md +12 -0
- package/scaffold/pt-br/commands/dw-skill-health.md +29 -0
- package/scaffold/pt-br/commands/dw-subtask-complete.md +40 -0
- package/scaffold/pt-br/commands/dw-subtask-resume.md +21 -0
- package/scaffold/pt-br/commands/dw-subtask-start.md +22 -0
- package/scaffold/skill-registry.json +258 -0
- package/scaffold/skills/dw-codebase-intel/SKILL.md +12 -5
- package/scaffold/skills/dw-context-budget/SKILL.md +45 -0
- package/scaffold/skills/dw-search-first/SKILL.md +31 -0
- package/scaffold/skills/dw-silent-failure/SKILL.md +33 -0
- package/scaffold/skills/dw-verify/SKILL.md +2 -29
- package/scaffold/skills/dw-verify/references/verification-discovery-and-attribution.md +32 -0
- package/scaffold/skills/humanizer/SKILL.md +7 -412
- package/scaffold/skills/humanizer/references/ai-writing-patterns.md +418 -0
package/lib/agents.js
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { ensureDir, readJson, writeFile, log } = require('./utils');
|
|
4
|
+
|
|
5
|
+
const SCAFFOLD_DIR = path.join(__dirname, '..', 'scaffold');
|
|
6
|
+
const REGISTRY_PATH = path.join(SCAFFOLD_DIR, 'agent-registry.json');
|
|
7
|
+
|
|
8
|
+
const AGENT_TARGETS = {
|
|
9
|
+
claude: {
|
|
10
|
+
dir: '.claude/agents',
|
|
11
|
+
fileName: (name) => `${name}.md`,
|
|
12
|
+
render: renderClaudeAgent,
|
|
13
|
+
},
|
|
14
|
+
opencode: {
|
|
15
|
+
dir: '.opencode/agent',
|
|
16
|
+
fileName: (name) => `${name}.md`,
|
|
17
|
+
render: renderOpenCodeAgent,
|
|
18
|
+
},
|
|
19
|
+
agents: {
|
|
20
|
+
dir: '.agents/agents',
|
|
21
|
+
fileName: (name) => `${name}.md`,
|
|
22
|
+
render: renderFallbackAgent,
|
|
23
|
+
},
|
|
24
|
+
copilot: {
|
|
25
|
+
dir: '.github/agents',
|
|
26
|
+
fileName: (name) => `${name}.agent.md`,
|
|
27
|
+
render: renderCopilotAgent,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const LEGACY_AGENT_TARGETS = {
|
|
32
|
+
copilot: {
|
|
33
|
+
dir: '.github/copilot/agents',
|
|
34
|
+
fileName: (name) => `${name}.agent.md`,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const REQUIRED_AGENT_FIELDS = [
|
|
39
|
+
'name',
|
|
40
|
+
'module',
|
|
41
|
+
'description',
|
|
42
|
+
'mode',
|
|
43
|
+
'context_mode',
|
|
44
|
+
'tool_policy',
|
|
45
|
+
'max_turns',
|
|
46
|
+
'input_budget_words',
|
|
47
|
+
'output_budget_words',
|
|
48
|
+
'parallel_safe',
|
|
49
|
+
'handoff_required',
|
|
50
|
+
'skill_policy',
|
|
51
|
+
'recommended_for',
|
|
52
|
+
'avoid_when',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
function readAgentRegistry() {
|
|
56
|
+
const registry = readJson(REGISTRY_PATH, null);
|
|
57
|
+
if (!registry || !Array.isArray(registry.agents)) {
|
|
58
|
+
throw new Error('Invalid scaffold/agent-registry.json');
|
|
59
|
+
}
|
|
60
|
+
return registry;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function validateAgentRegistry(registry = readAgentRegistry()) {
|
|
64
|
+
const issues = [];
|
|
65
|
+
const warnings = [];
|
|
66
|
+
if (registry.schema_version !== '2.0') {
|
|
67
|
+
issues.push('Agent registry must use schema_version 2.0.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const names = new Set();
|
|
71
|
+
for (const agent of registry.agents || []) {
|
|
72
|
+
for (const field of REQUIRED_AGENT_FIELDS) {
|
|
73
|
+
if (agent[field] === undefined || agent[field] === null || agent[field] === '') {
|
|
74
|
+
issues.push(`Agent ${agent.name || '<unnamed>'} is missing required field: ${field}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (names.has(agent.name)) issues.push(`Duplicate agent name: ${agent.name}`);
|
|
78
|
+
names.add(agent.name);
|
|
79
|
+
|
|
80
|
+
if (!Array.isArray(agent.recommended_for)) {
|
|
81
|
+
issues.push(`Agent ${agent.name} recommended_for must be an array.`);
|
|
82
|
+
}
|
|
83
|
+
if (!Array.isArray(agent.avoid_when)) {
|
|
84
|
+
issues.push(`Agent ${agent.name} avoid_when must be an array.`);
|
|
85
|
+
}
|
|
86
|
+
if (agent.mode === 'read-only' && agent.tool_policy !== 'read-only') {
|
|
87
|
+
issues.push(`Read-only agent ${agent.name} must use tool_policy read-only.`);
|
|
88
|
+
}
|
|
89
|
+
if (agent.output_budget_words > 1500) {
|
|
90
|
+
warnings.push(`Agent ${agent.name} output budget is above 1500 words.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const [commandName, agents] of Object.entries(registry.commands || {})) {
|
|
95
|
+
if (!Array.isArray(agents)) {
|
|
96
|
+
issues.push(`Agent registry command ${commandName} must map to an array.`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
for (const agentName of agents) {
|
|
100
|
+
if (!names.has(agentName)) issues.push(`Agent registry references missing agent: ${agentName}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { issues, warnings };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function selectAgents(modules) {
|
|
108
|
+
const registry = readAgentRegistry();
|
|
109
|
+
const validation = validateAgentRegistry(registry);
|
|
110
|
+
if (validation.issues.length) {
|
|
111
|
+
throw new Error(`Invalid scaffold/agent-registry.json:\n- ${validation.issues.join('\n- ')}`);
|
|
112
|
+
}
|
|
113
|
+
const selectedModules = new Set(modules || []);
|
|
114
|
+
return registry.agents.filter((agent) => selectedModules.has(agent.module));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function generateAgents(projectRoot, modules, force = false) {
|
|
118
|
+
const results = { created: 0, skipped: 0, overwritten: 0, files: [] };
|
|
119
|
+
const agents = selectAgents(modules);
|
|
120
|
+
|
|
121
|
+
if (force) removeLegacyAgentTargets(projectRoot, agents);
|
|
122
|
+
|
|
123
|
+
for (const agent of agents) {
|
|
124
|
+
const source = readAgentSource(agent);
|
|
125
|
+
for (const target of Object.values(AGENT_TARGETS)) {
|
|
126
|
+
const dest = path.join(projectRoot, target.dir, target.fileName(agent.name));
|
|
127
|
+
const status = writeFile(dest, target.render(agent, source), force);
|
|
128
|
+
results[status]++;
|
|
129
|
+
results.files.push(dest);
|
|
130
|
+
log(status, dest);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
writeAgentIndex(projectRoot, agents, force);
|
|
135
|
+
const indexPath = path.join(projectRoot, '.agents', 'agents', 'README.md');
|
|
136
|
+
results.files.push(indexPath);
|
|
137
|
+
|
|
138
|
+
return results;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readAgentSource(agent) {
|
|
142
|
+
const src = path.join(SCAFFOLD_DIR, 'agents', agent.module, `${agent.name}.md`);
|
|
143
|
+
if (!fs.existsSync(src)) {
|
|
144
|
+
throw new Error(`Agent source missing: ${path.relative(process.cwd(), src)}`);
|
|
145
|
+
}
|
|
146
|
+
const raw = fs.readFileSync(src, 'utf-8');
|
|
147
|
+
const parsed = parseMarkdownFrontmatter(raw);
|
|
148
|
+
return {
|
|
149
|
+
path: src,
|
|
150
|
+
metadata: parsed.metadata,
|
|
151
|
+
body: parsed.body.trim(),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseMarkdownFrontmatter(raw) {
|
|
156
|
+
if (!raw.startsWith('---\n')) return { metadata: {}, body: raw };
|
|
157
|
+
const end = raw.indexOf('\n---', 4);
|
|
158
|
+
if (end === -1) return { metadata: {}, body: raw };
|
|
159
|
+
const metadata = {};
|
|
160
|
+
const frontmatter = raw.slice(4, end).trim();
|
|
161
|
+
for (const line of frontmatter.split('\n')) {
|
|
162
|
+
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
163
|
+
if (!match) continue;
|
|
164
|
+
metadata[match[1]] = match[2].replace(/^["']|["']$/g, '');
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
metadata,
|
|
168
|
+
body: raw.slice(end + 4).replace(/^\s+/, ''),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function renderClaudeAgent(agent, source) {
|
|
173
|
+
const tools = claudeToolsFor(agent, source);
|
|
174
|
+
const lines = [
|
|
175
|
+
'---',
|
|
176
|
+
`name: ${agent.name}`,
|
|
177
|
+
`description: "${yamlString(agent.description)}"`,
|
|
178
|
+
`tools: ${tools.join(', ')}`,
|
|
179
|
+
`model: ${source.metadata.model || 'sonnet'}`,
|
|
180
|
+
`permissionMode: ${agent.tool_policy === 'read-only' ? 'plan' : 'default'}`,
|
|
181
|
+
`maxTurns: ${agent.max_turns}`,
|
|
182
|
+
'---',
|
|
183
|
+
'',
|
|
184
|
+
renderContract(agent, 'Claude Code project subagent'),
|
|
185
|
+
'',
|
|
186
|
+
source.body,
|
|
187
|
+
'',
|
|
188
|
+
];
|
|
189
|
+
return lines.join('\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function renderOpenCodeAgent(agent, source) {
|
|
193
|
+
const canWrite = agent.tool_policy !== 'read-only';
|
|
194
|
+
const lines = [
|
|
195
|
+
'---',
|
|
196
|
+
'mode: subagent',
|
|
197
|
+
`description: "${yamlString(agent.description)}"`,
|
|
198
|
+
`steps: ${agent.max_turns}`,
|
|
199
|
+
'permission:',
|
|
200
|
+
` edit: ${canWrite ? 'allow' : 'deny'}`,
|
|
201
|
+
` write: ${canWrite ? 'allow' : 'deny'}`,
|
|
202
|
+
` bash: ${agent.tool_policy === 'read-only' ? 'deny' : 'allow'}`,
|
|
203
|
+
'---',
|
|
204
|
+
'',
|
|
205
|
+
renderContract(agent, 'OpenCode project subagent'),
|
|
206
|
+
'',
|
|
207
|
+
source.body,
|
|
208
|
+
'',
|
|
209
|
+
];
|
|
210
|
+
return lines.join('\n');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function renderCopilotAgent(agent, source) {
|
|
214
|
+
const tools = copilotToolsFor(agent);
|
|
215
|
+
const lines = [
|
|
216
|
+
'---',
|
|
217
|
+
`name: ${agent.name}`,
|
|
218
|
+
`description: "${yamlString(agent.description)}"`,
|
|
219
|
+
`tools: ${tools.join(', ')}`,
|
|
220
|
+
'---',
|
|
221
|
+
'',
|
|
222
|
+
renderContract(agent, 'GitHub Copilot custom agent'),
|
|
223
|
+
'',
|
|
224
|
+
source.body,
|
|
225
|
+
'',
|
|
226
|
+
];
|
|
227
|
+
return lines.join('\n');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function renderFallbackAgent(agent, source) {
|
|
231
|
+
const lines = [
|
|
232
|
+
'---',
|
|
233
|
+
`name: ${agent.name}`,
|
|
234
|
+
`description: "${yamlString(agent.description)}"`,
|
|
235
|
+
`mode: ${agent.mode}`,
|
|
236
|
+
`context_mode: ${agent.context_mode}`,
|
|
237
|
+
`tool_policy: ${agent.tool_policy}`,
|
|
238
|
+
`max_turns: ${agent.max_turns}`,
|
|
239
|
+
`input_budget_words: ${agent.input_budget_words}`,
|
|
240
|
+
`output_budget_words: ${agent.output_budget_words}`,
|
|
241
|
+
`parallel_safe: ${agent.parallel_safe}`,
|
|
242
|
+
'---',
|
|
243
|
+
'',
|
|
244
|
+
renderContract(agent, 'Fallback prompt profile'),
|
|
245
|
+
'',
|
|
246
|
+
source.body,
|
|
247
|
+
'',
|
|
248
|
+
];
|
|
249
|
+
return lines.join('\n');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function renderContract(agent, targetLabel) {
|
|
253
|
+
return [
|
|
254
|
+
`# ${agent.name}`,
|
|
255
|
+
'',
|
|
256
|
+
`Provider target: ${targetLabel}.`,
|
|
257
|
+
'',
|
|
258
|
+
'## Dispatch Contract',
|
|
259
|
+
'',
|
|
260
|
+
`- Context mode: \`${agent.context_mode}\`.`,
|
|
261
|
+
`- Tool policy: \`${agent.tool_policy}\`.`,
|
|
262
|
+
`- Max turns: ${agent.max_turns}.`,
|
|
263
|
+
`- Input budget: ${agent.input_budget_words} words.`,
|
|
264
|
+
`- Output budget: ${agent.output_budget_words} words.`,
|
|
265
|
+
`- Parallel safe: ${agent.parallel_safe ? 'yes' : 'no'}.`,
|
|
266
|
+
`- Handoff required: ${agent.handoff_required ? 'yes' : 'no'}.`,
|
|
267
|
+
`- Skill policy: \`${agent.skill_policy}\`; do not preload large skills.`,
|
|
268
|
+
'',
|
|
269
|
+
'## Use When',
|
|
270
|
+
...agent.recommended_for.map((item) => `- ${item}.`),
|
|
271
|
+
'',
|
|
272
|
+
'## Do Not Use When',
|
|
273
|
+
...agent.avoid_when.map((item) => `- ${item}.`),
|
|
274
|
+
'',
|
|
275
|
+
'## Input Packet Expected',
|
|
276
|
+
'',
|
|
277
|
+
'- Objective and stop condition.',
|
|
278
|
+
'- Allowed files, folders, commands, and write boundaries.',
|
|
279
|
+
'- Relevant constraints from `.dw/rules/`, `.dw/intel/`, or the parent session.',
|
|
280
|
+
'- Expected return packet shape and verification command, when applicable.',
|
|
281
|
+
'',
|
|
282
|
+
'## Context Rules',
|
|
283
|
+
'',
|
|
284
|
+
'- Do not request or paste the full parent transcript.',
|
|
285
|
+
'- Read only the files needed for the objective.',
|
|
286
|
+
'- Summarize logs; include only failing lines, paths, commands, and decisions.',
|
|
287
|
+
'',
|
|
288
|
+
'## Tool/Write Boundaries',
|
|
289
|
+
'',
|
|
290
|
+
toolBoundary(agent),
|
|
291
|
+
'',
|
|
292
|
+
'## Return Packet',
|
|
293
|
+
'',
|
|
294
|
+
'- Result: pass, findings, changed files, or blocked.',
|
|
295
|
+
'- Files read and files changed.',
|
|
296
|
+
'- Decisions made and risks that remain.',
|
|
297
|
+
'- Verification run, including command and outcome.',
|
|
298
|
+
'- Recommended next step for the parent session.',
|
|
299
|
+
'',
|
|
300
|
+
'## Stop Conditions',
|
|
301
|
+
'',
|
|
302
|
+
'- Stop when the requested packet is complete.',
|
|
303
|
+
'- Stop if required context exceeds the input budget.',
|
|
304
|
+
'- Stop if the task needs broader architecture, product, or permission decisions.',
|
|
305
|
+
].join('\n');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function toolBoundary(agent) {
|
|
309
|
+
if (agent.tool_policy === 'read-only') {
|
|
310
|
+
return '- Read-only. Do not edit, write, delete, or format files.';
|
|
311
|
+
}
|
|
312
|
+
if (agent.tool_policy === 'qa-write') {
|
|
313
|
+
return '- Write only QA artifacts, test scripts, traces, screenshots, and reports unless the parent explicitly assigns a fix.';
|
|
314
|
+
}
|
|
315
|
+
if (agent.tool_policy === 'shell-limited') {
|
|
316
|
+
return '- Shell commands are allowed only for inspection and targeted verification. Do not edit files unless explicitly assigned.';
|
|
317
|
+
}
|
|
318
|
+
return '- Edits are allowed only inside the files or module boundaries named in the input packet.';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function claudeToolsFor(agent, source) {
|
|
322
|
+
if (source.metadata.tools) {
|
|
323
|
+
return source.metadata.tools.split(',').map((tool) => tool.trim()).filter(Boolean);
|
|
324
|
+
}
|
|
325
|
+
return agent.tool_policy === 'read-only'
|
|
326
|
+
? ['Read', 'Grep', 'Glob']
|
|
327
|
+
: ['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob'];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function copilotToolsFor(agent) {
|
|
331
|
+
return agent.tool_policy === 'read-only'
|
|
332
|
+
? ['read', 'grep']
|
|
333
|
+
: ['read', 'edit', 'terminal'];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function yamlString(value) {
|
|
337
|
+
return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function writeAgentIndex(projectRoot, agents, force) {
|
|
341
|
+
const lines = [
|
|
342
|
+
'# dev-workflow Agents',
|
|
343
|
+
'',
|
|
344
|
+
'These files are fallback prompt profiles for harnesses that do not expose native project subagents.',
|
|
345
|
+
'Claude Code uses `.claude/agents/`; OpenCode uses `.opencode/agent/`; Copilot receives `.github/agents/*.agent.md`.',
|
|
346
|
+
'Codex should treat this directory as delegable profiles when subagents are available, or as a manual prompt pack otherwise.',
|
|
347
|
+
'',
|
|
348
|
+
'| Agent | Module | Mode | Context | Output Budget | Purpose |',
|
|
349
|
+
'|---|---|---|---|---:|---|',
|
|
350
|
+
];
|
|
351
|
+
|
|
352
|
+
for (const agent of agents) {
|
|
353
|
+
lines.push(`| \`${agent.name}\` | \`${agent.module}\` | \`${agent.mode}\` | \`${agent.context_mode}\` | ${agent.output_budget_words} words | ${agent.description} |`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const content = lines.join('\n') + '\n';
|
|
357
|
+
writeFile(path.join(projectRoot, '.agents', 'agents', 'README.md'), content, force);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function listExpectedAgentFiles(projectRoot, modules) {
|
|
361
|
+
const files = [];
|
|
362
|
+
for (const agent of selectAgents(modules)) {
|
|
363
|
+
for (const target of Object.values(AGENT_TARGETS)) {
|
|
364
|
+
files.push(path.join(projectRoot, target.dir, target.fileName(agent.name)));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
files.push(path.join(projectRoot, '.agents', 'agents', 'README.md'));
|
|
368
|
+
return files;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function listLegacyAgentFiles(projectRoot, modules) {
|
|
372
|
+
const files = [];
|
|
373
|
+
for (const agent of selectAgents(modules)) {
|
|
374
|
+
for (const target of Object.values(LEGACY_AGENT_TARGETS)) {
|
|
375
|
+
files.push(path.join(projectRoot, target.dir, target.fileName(agent.name)));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return files;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function ensureAgentDirs(projectRoot) {
|
|
382
|
+
for (const target of Object.values(AGENT_TARGETS)) {
|
|
383
|
+
ensureDir(path.join(projectRoot, target.dir));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function removeLegacyAgentTargets(projectRoot, agents) {
|
|
388
|
+
for (const target of Object.values(LEGACY_AGENT_TARGETS)) {
|
|
389
|
+
for (const agent of agents) {
|
|
390
|
+
const file = path.join(projectRoot, target.dir, target.fileName(agent.name));
|
|
391
|
+
if (fs.existsSync(file)) fs.rmSync(file);
|
|
392
|
+
}
|
|
393
|
+
const dir = path.join(projectRoot, target.dir);
|
|
394
|
+
if (fs.existsSync(dir) && fs.readdirSync(dir).length === 0) {
|
|
395
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
module.exports = {
|
|
401
|
+
AGENT_TARGETS,
|
|
402
|
+
LEGACY_AGENT_TARGETS,
|
|
403
|
+
readAgentRegistry,
|
|
404
|
+
validateAgentRegistry,
|
|
405
|
+
selectAgents,
|
|
406
|
+
generateAgents,
|
|
407
|
+
listExpectedAgentFiles,
|
|
408
|
+
listLegacyAgentFiles,
|
|
409
|
+
ensureAgentDirs,
|
|
410
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Upstream-native categories for the AWS skills repo (aws/agent-toolkit-for-aws,
|
|
2
|
+
// Apache 2.0). Microsoft's Azure repo is service-oriented and required us to invent
|
|
3
|
+
// 10 human categories; AWS organizes skills task-oriented under skills/core-skills/
|
|
4
|
+
// (13 horizontal) and skills/specialized-skills/<category>/ (9 verticals × N tasks).
|
|
5
|
+
// This file mirrors that structure 1:1 — no artificial mapping.
|
|
6
|
+
//
|
|
7
|
+
// Update this file when the upstream adds, renames, or removes top-level directories.
|
|
8
|
+
|
|
9
|
+
const CATEGORIES = {
|
|
10
|
+
All: {
|
|
11
|
+
description: 'Every skill in the upstream repo (core + specialized).',
|
|
12
|
+
dirs: '__ALL__',
|
|
13
|
+
},
|
|
14
|
+
Core: {
|
|
15
|
+
description:
|
|
16
|
+
'All 13 horizontal core skills (Bedrock, Amplify, Billing, CDK, CloudFormation, Containers, IAM, Messaging, Observability, SDK JS/Python/Swift, Serverless).',
|
|
17
|
+
dirs: ['core-skills/*'],
|
|
18
|
+
},
|
|
19
|
+
Analytics: {
|
|
20
|
+
description: 'Athena, Glue, Kinesis, EMR, Redshift, OpenSearch, QuickSight, etc.',
|
|
21
|
+
dirs: ['specialized-skills/analytics-skills/*'],
|
|
22
|
+
},
|
|
23
|
+
Database: {
|
|
24
|
+
description: 'DynamoDB, RDS/Aurora, DocumentDB, ElastiCache, Neptune, etc.',
|
|
25
|
+
dirs: ['specialized-skills/database-skills/*'],
|
|
26
|
+
},
|
|
27
|
+
'EC2 / Compute': {
|
|
28
|
+
description: 'EC2 launch, AMI, instance profiles, image builder, etc.',
|
|
29
|
+
dirs: ['specialized-skills/ec2-skills/*'],
|
|
30
|
+
},
|
|
31
|
+
'Migration & Modernization': {
|
|
32
|
+
description: 'DMS, MGN, App2Container, modernization patterns.',
|
|
33
|
+
dirs: ['specialized-skills/migration-and-modernization-skills/*'],
|
|
34
|
+
},
|
|
35
|
+
'Networking & Content Delivery': {
|
|
36
|
+
description: 'VPC, Route 53, CloudFront, API Gateway, ELB, Transit Gateway, etc.',
|
|
37
|
+
dirs: ['specialized-skills/networking-and-content-delivery-skills/*'],
|
|
38
|
+
},
|
|
39
|
+
Operations: {
|
|
40
|
+
description: 'CloudWatch, Systems Manager, OpsWorks, Config, Trusted Advisor.',
|
|
41
|
+
dirs: ['specialized-skills/operations-skills/*'],
|
|
42
|
+
},
|
|
43
|
+
'Security & Identity': {
|
|
44
|
+
description: 'IAM patterns, KMS, Secrets Manager, GuardDuty, WAF, Security Hub.',
|
|
45
|
+
dirs: ['specialized-skills/security-and-identity-skills/*'],
|
|
46
|
+
},
|
|
47
|
+
Serverless: {
|
|
48
|
+
description: 'Lambda, Step Functions, EventBridge, SAM patterns.',
|
|
49
|
+
dirs: ['specialized-skills/serverless-skills/*'],
|
|
50
|
+
},
|
|
51
|
+
Storage: {
|
|
52
|
+
description: 'S3, EBS, EFS, FSx, Storage Gateway, data lake tables.',
|
|
53
|
+
dirs: ['specialized-skills/storage-skills/*'],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function listCategories() {
|
|
58
|
+
return Object.keys(CATEGORIES);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Resolve selected category names → list of upstream relative paths to scan for skills.
|
|
62
|
+
// Each entry is a directory pattern under `skills/`. The caller globs each pattern.
|
|
63
|
+
// Special-cases "All" → returns the sentinel string '__ALL__' meaning every dir
|
|
64
|
+
// under skills/core-skills/ and skills/specialized-skills/<*>/.
|
|
65
|
+
function resolveDirs(selectedCategories) {
|
|
66
|
+
if (selectedCategories.includes('All')) {
|
|
67
|
+
return '__ALL__';
|
|
68
|
+
}
|
|
69
|
+
const dirs = new Set();
|
|
70
|
+
for (const cat of selectedCategories) {
|
|
71
|
+
const entry = CATEGORIES[cat];
|
|
72
|
+
if (!entry || !Array.isArray(entry.dirs)) continue;
|
|
73
|
+
for (const d of entry.dirs) {
|
|
74
|
+
dirs.add(d);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return Array.from(dirs);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { CATEGORIES, listCategories, resolveDirs };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Curated categories mapping human-readable groups to upstream Azure skill slugs
|
|
2
|
+
// from MicrosoftDocs/Agent-Skills (CC-BY-4.0). Match is prefix-based: a slug in the
|
|
3
|
+
// list matches any directory whose name starts with that slug. "all" is a sentinel
|
|
4
|
+
// meaning every directory under skills/.
|
|
5
|
+
//
|
|
6
|
+
// This file is the single source of truth. When Microsoft adds, renames, or removes
|
|
7
|
+
// services, update the prefixes here — install-azure-skills.js does not embed slugs.
|
|
8
|
+
|
|
9
|
+
const CATEGORIES = {
|
|
10
|
+
All: { description: 'Every skill in the upstream repo (skills/ and non-overlapping products/).' },
|
|
11
|
+
Compute: {
|
|
12
|
+
description: 'AKS, App Service, Container Apps/Instances/Registry, Functions, VMs, Batch.',
|
|
13
|
+
prefixes: [
|
|
14
|
+
'azure-aks',
|
|
15
|
+
'azure-app-service',
|
|
16
|
+
'azure-container-apps',
|
|
17
|
+
'azure-container-instances',
|
|
18
|
+
'azure-container-registry',
|
|
19
|
+
'azure-functions',
|
|
20
|
+
'azure-virtual-machines',
|
|
21
|
+
'azure-vmware-solution',
|
|
22
|
+
'azure-batch',
|
|
23
|
+
'azure-spring-apps',
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
'Data & Storage': {
|
|
27
|
+
description: 'Blob, Cosmos DB, SQL, MySQL, PostgreSQL, Cache for Redis, Data Lake, Files.',
|
|
28
|
+
prefixes: [
|
|
29
|
+
'azure-blob-storage',
|
|
30
|
+
'azure-cosmos-db',
|
|
31
|
+
'azure-sql',
|
|
32
|
+
'azure-database-for-mysql',
|
|
33
|
+
'azure-database-for-postgresql',
|
|
34
|
+
'azure-cache-redis',
|
|
35
|
+
'azure-data-lake',
|
|
36
|
+
'azure-files',
|
|
37
|
+
'azure-storage',
|
|
38
|
+
'azure-table-storage',
|
|
39
|
+
'azure-queue-storage',
|
|
40
|
+
'azure-managed-disk',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
'AI & ML': {
|
|
44
|
+
description: 'OpenAI, AI Foundry, AI Vision, AI Search, Machine Learning, Anomaly Detector, Bot Service.',
|
|
45
|
+
prefixes: [
|
|
46
|
+
'azure-openai',
|
|
47
|
+
'azure-ai-foundry',
|
|
48
|
+
'azure-ai-vision',
|
|
49
|
+
'azure-ai-search',
|
|
50
|
+
'azure-ai-services',
|
|
51
|
+
'azure-machine-learning',
|
|
52
|
+
'azure-anomaly-detector',
|
|
53
|
+
'azure-bot-service',
|
|
54
|
+
'azure-cognitive',
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
Networking: {
|
|
58
|
+
description: 'Application Gateway, Front Door, Load Balancer, VPN Gateway, ExpressRoute, Bastion, DNS, CDN.',
|
|
59
|
+
prefixes: [
|
|
60
|
+
'azure-application-gateway',
|
|
61
|
+
'azure-front-door',
|
|
62
|
+
'azure-load-balancer',
|
|
63
|
+
'azure-vpn-gateway',
|
|
64
|
+
'azure-expressroute',
|
|
65
|
+
'azure-bastion',
|
|
66
|
+
'azure-dns',
|
|
67
|
+
'azure-cdn',
|
|
68
|
+
'azure-virtual-network',
|
|
69
|
+
'azure-virtual-wan',
|
|
70
|
+
'azure-private-link',
|
|
71
|
+
'azure-network-watcher',
|
|
72
|
+
'azure-firewall',
|
|
73
|
+
'azure-traffic-manager',
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
'Identity & Security': {
|
|
77
|
+
description: 'Entra ID (Active Directory), Key Vault, Attestation, Defender, Sentinel.',
|
|
78
|
+
prefixes: [
|
|
79
|
+
'azure-active-directory',
|
|
80
|
+
'azure-entra',
|
|
81
|
+
'azure-key-vault',
|
|
82
|
+
'azure-attestation',
|
|
83
|
+
'azure-defender',
|
|
84
|
+
'azure-sentinel',
|
|
85
|
+
'azure-security-center',
|
|
86
|
+
'azure-artifact-signing',
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
DevOps: {
|
|
90
|
+
description: 'Azure DevOps (Boards, Pipelines, Artifacts, Repos), Azure DevOps Server, GitHub integration.',
|
|
91
|
+
prefixes: [
|
|
92
|
+
'azure-boards',
|
|
93
|
+
'azure-pipelines',
|
|
94
|
+
'azure-artifacts',
|
|
95
|
+
'azure-repos',
|
|
96
|
+
'azure-devops',
|
|
97
|
+
'azure-test-plans',
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
Observability: {
|
|
101
|
+
description: 'Monitor, Application Insights, Log Analytics.',
|
|
102
|
+
prefixes: [
|
|
103
|
+
'azure-monitor',
|
|
104
|
+
'azure-application-insights',
|
|
105
|
+
'azure-log-analytics',
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
Integration: {
|
|
109
|
+
description: 'Logic Apps, Service Bus, Event Grid, Event Hubs, API Management, API Center.',
|
|
110
|
+
prefixes: [
|
|
111
|
+
'azure-logic-apps',
|
|
112
|
+
'azure-service-bus',
|
|
113
|
+
'azure-event-grid',
|
|
114
|
+
'azure-event-hubs',
|
|
115
|
+
'azure-api-management',
|
|
116
|
+
'azure-api-center',
|
|
117
|
+
'azure-business-process-tracking',
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
Architecture: {
|
|
121
|
+
description: 'Architecture guidance, Advisor, Blueprints, Well-Architected.',
|
|
122
|
+
prefixes: [
|
|
123
|
+
'azure-architecture',
|
|
124
|
+
'azure-advisor',
|
|
125
|
+
'azure-blueprints',
|
|
126
|
+
'azure-well-architected',
|
|
127
|
+
'azure-policy',
|
|
128
|
+
'azure-resource-graph',
|
|
129
|
+
'azure-resource-manager',
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Return the list of category names in the canonical display order.
|
|
135
|
+
function listCategories() {
|
|
136
|
+
return Object.keys(CATEGORIES);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Resolve selected category names → flat set of slug prefixes to match against
|
|
140
|
+
// upstream skill directory names. Special-cases "All".
|
|
141
|
+
function resolvePrefixes(selectedCategories) {
|
|
142
|
+
if (selectedCategories.includes('All')) {
|
|
143
|
+
return null; // null = match every directory
|
|
144
|
+
}
|
|
145
|
+
const prefixes = new Set();
|
|
146
|
+
for (const cat of selectedCategories) {
|
|
147
|
+
const entry = CATEGORIES[cat];
|
|
148
|
+
if (!entry || !entry.prefixes) continue;
|
|
149
|
+
for (const prefix of entry.prefixes) {
|
|
150
|
+
prefixes.add(prefix);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return Array.from(prefixes);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// True when a directory name should be installed given the resolved prefix set.
|
|
157
|
+
// null prefix set means install everything.
|
|
158
|
+
function matchesPrefixes(dirName, prefixes) {
|
|
159
|
+
if (prefixes === null) return true;
|
|
160
|
+
for (const prefix of prefixes) {
|
|
161
|
+
if (dirName === prefix || dirName.startsWith(`${prefix}-`)) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = { CATEGORIES, listCategories, resolvePrefixes, matchesPrefixes };
|