@brunosps00/dev-workflow 1.0.4 → 1.0.6
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 +71 -23
- package/bin/dev-workflow.js +29 -3
- package/lib/agents.js +410 -0
- package/lib/constants.js +16 -2
- package/lib/doctor.js +213 -0
- package/lib/init.js +96 -8
- package/lib/install-state.js +57 -0
- package/lib/list-installed.js +27 -0
- package/lib/migrate-skills.js +26 -2
- package/lib/profiles.js +44 -0
- 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 +51 -6
- package/scaffold/en/commands/dw-autopilot.md +156 -275
- package/scaffold/en/commands/dw-bugfix.md +12 -2
- package/scaffold/en/commands/dw-context-budget.md +28 -0
- package/scaffold/en/commands/dw-goal.md +136 -0
- package/scaffold/en/commands/dw-harness-audit.md +31 -0
- package/scaffold/en/commands/dw-help.md +22 -10
- package/scaffold/en/commands/dw-intel.md +6 -0
- package/scaffold/en/commands/dw-plan.md +20 -3
- 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 +51 -6
- package/scaffold/pt-br/commands/dw-autopilot.md +159 -278
- package/scaffold/pt-br/commands/dw-bugfix.md +12 -2
- package/scaffold/pt-br/commands/dw-context-budget.md +28 -0
- package/scaffold/pt-br/commands/dw-goal.md +136 -0
- package/scaffold/pt-br/commands/dw-harness-audit.md +31 -0
- package/scaffold/pt-br/commands/dw-help.md +22 -10
- package/scaffold/pt-br/commands/dw-intel.md +6 -0
- package/scaffold/pt-br/commands/dw-plan.md +20 -3
- 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
|
+
};
|
package/lib/constants.js
CHANGED
|
@@ -2,7 +2,7 @@ const COMMANDS = {
|
|
|
2
2
|
en: [
|
|
3
3
|
{ name: 'dw-adr', description: 'Records an architectural decision and the trade-offs accepted, before they get lost.' },
|
|
4
4
|
{ name: 'dw-analyze-project', description: 'Scans the repo to learn its stack and conventions, then writes the rules other commands rely on.' },
|
|
5
|
-
{ name: 'dw-autopilot', description: 'Trigger when user asks to implement, build, create, or add a feature non-trivially.
|
|
5
|
+
{ name: 'dw-autopilot', description: 'Trigger when user asks to implement, build, create, or add a feature non-trivially. First invocation plans and stops; second invocation resumes via dw-goal, then commit and PR.' },
|
|
6
6
|
{ name: 'dw-brainstorm', description: 'Refine an idea against the product\'s existing features. Modes: default ideation, --research (multi-source cited research), --refactor (Fowler code-smell catalog), --onepager, --council.' },
|
|
7
7
|
{ name: 'dw-bugfix', description: 'Trigger when user reports a bug, pastes an error, or describes broken behavior. Triages with three questions, then fixes or routes to PRD.' },
|
|
8
8
|
{ name: 'dw-commit', description: 'Trigger when implementation is complete and pending changes need to be committed. Atomic commits with Conventional Commits messages.' },
|
|
@@ -10,7 +10,10 @@ const COMMANDS = {
|
|
|
10
10
|
{ name: 'dw-find-skills', description: 'Searches the npx skills ecosystem for a skill matching what you need, vets it, and installs it where you choose.' },
|
|
11
11
|
{ name: 'dw-functional-doc', description: 'Maps screens and user flows into a functional doc, validated end-to-end with Playwright.' },
|
|
12
12
|
{ name: 'dw-generate-pr', description: 'Trigger when commits are ready and the branch needs a PR. Pushes branch and opens PR with summary + test plan + hard verify gate.' },
|
|
13
|
+
{ name: 'dw-goal', description: 'Trigger when long-running implementation work needs a durable objective. Portable Codex-style goal contract; used by autopilot after planning.' },
|
|
13
14
|
{ name: 'dw-help', description: 'Lists primary commands and the flows that connect them. Pass --advanced to see internal/hidden commands.' },
|
|
15
|
+
{ name: 'dw-context-budget', description: 'Audits context overhead from commands, skills, agents, instructions, and MCPs; recommends token savings.' },
|
|
16
|
+
{ name: 'dw-harness-audit', description: 'Runs a deterministic health scorecard for dev-workflow installation, agents, MCPs, gates, and context discipline.' },
|
|
14
17
|
{ name: 'dw-install-aws-skills', description: 'Trigger when user asks to install AWS expertise, setup AWS MCP server, or add AWS agent skills. Opt-in: clones curated skills from aws/agent-toolkit-for-aws into .agents/skills/aws/ and registers the unified AWS MCP Server (stdio via mcp-proxy-for-aws). Requires uv, aws cli, and AWS credentials.' },
|
|
15
18
|
{ name: 'dw-install-azure-skills', description: 'Trigger when user asks to install Azure expertise, setup Microsoft docs MCP, or add Azure agent skills. Opt-in: clones curated skills from MicrosoftDocs/Agent-Skills into .agents/skills/azure/ and registers the Microsoft Learn MCP server.' },
|
|
16
19
|
{ name: 'dw-intel', description: 'Codebase intelligence: query mode (default) answers questions citing .dw/intel/ + .dw/rules/; --build mode (re)builds the index.' },
|
|
@@ -23,12 +26,16 @@ const COMMANDS = {
|
|
|
23
26
|
{ name: 'dw-review', description: 'Trigger when user asks to review code, check quality, or verify PR readiness. Default runs L2 (PRD coverage) + L3 (code quality). Flags --coverage-only / --code-only / --bugfix <slug>.' },
|
|
24
27
|
{ name: 'dw-run', description: 'Trigger when user wants to execute tasks. Default runs all pending in dependency order; \'run <task-id>\' runs one; \'run --resume\' continues an interrupted plan.' },
|
|
25
28
|
{ name: 'dw-secure-audit', description: 'Unified security audit: OWASP + Trivy SCA/secret/IaC + lockfile + supply-chain compromise check. Hidden; auto-invoked by /dw-review and /dw-generate-pr.' },
|
|
29
|
+
{ name: 'dw-skill-health', description: 'Audits installed skills and agents for bloat, duplication, missing references, and stale or unused modules.' },
|
|
30
|
+
{ name: 'dw-subtask-start', description: 'Creates a minimal input packet for a subagent without injecting the parent transcript.' },
|
|
31
|
+
{ name: 'dw-subtask-complete', description: 'Records a structured subagent handoff with result, files, decisions, risks, verification, and blockers.' },
|
|
32
|
+
{ name: 'dw-subtask-resume', description: 'Consumes pending subagent handoffs, summarizes them for the parent, and archives the local packets.' },
|
|
26
33
|
{ name: 'dw-update', description: 'Updates dev-workflow to the latest npm release in-place, with a snapshot you can roll back to.' },
|
|
27
34
|
],
|
|
28
35
|
'pt-br': [
|
|
29
36
|
{ name: 'dw-adr', description: 'Registra uma decisao arquitetural e os trade-offs aceitos, antes que se percam.' },
|
|
30
37
|
{ name: 'dw-analyze-project', description: 'Escaneia o repo para aprender stack e convencoes, e escreve as regras que os outros commands usam.' },
|
|
31
|
-
{ name: 'dw-autopilot', description: 'Trigger quando usuario pede pra implementar, criar ou adicionar uma feature nao-trivial.
|
|
38
|
+
{ name: 'dw-autopilot', description: 'Trigger quando usuario pede pra implementar, criar ou adicionar uma feature nao-trivial. Primeira invocacao planeja e para; segunda retoma via dw-goal, depois commit e PR.' },
|
|
32
39
|
{ name: 'dw-brainstorm', description: 'Refina uma ideia contra features existentes do produto. Modos: ideacao default, --research (research multi-fonte citada), --refactor (catalogo de code smells Fowler), --onepager, --council.' },
|
|
33
40
|
{ name: 'dw-bugfix', description: 'Trigger quando usuario reporta bug, cola erro ou descreve comportamento quebrado. Tria com tres perguntas, depois corrige ou roteia pra PRD.' },
|
|
34
41
|
{ name: 'dw-commit', description: 'Trigger quando implementacao esta completa e ha mudancas pendentes pra commit. Commits atomicos com mensagens Conventional Commits.' },
|
|
@@ -36,7 +43,10 @@ const COMMANDS = {
|
|
|
36
43
|
{ name: 'dw-find-skills', description: 'Busca no ecossistema npx skills uma skill que resolva o que voce precisa, valida e instala onde voce escolher.' },
|
|
37
44
|
{ name: 'dw-functional-doc', description: 'Mapeia telas e fluxos em um dossie funcional, validado E2E com Playwright.' },
|
|
38
45
|
{ name: 'dw-generate-pr', description: 'Trigger quando commits estao prontos e branch precisa de PR. Push da branch e abre PR com summary + test plan + hard verify gate.' },
|
|
46
|
+
{ name: 'dw-goal', description: 'Trigger quando trabalho longo precisa de objetivo duravel. Contrato portavel estilo Codex goal; usado pelo autopilot apos o planejamento.' },
|
|
39
47
|
{ name: 'dw-help', description: 'Lista comandos primarios e fluxos que os conectam. Passe --advanced para ver comandos internos/escondidos.' },
|
|
48
|
+
{ name: 'dw-context-budget', description: 'Audita overhead de contexto em comandos, skills, agentes, instrucoes e MCPs; recomenda economia de tokens.' },
|
|
49
|
+
{ name: 'dw-harness-audit', description: 'Roda um scorecard deterministico da instalacao dev-workflow, agentes, MCPs, gates e disciplina de contexto.' },
|
|
40
50
|
{ name: 'dw-install-aws-skills', description: 'Trigger quando usuario pede pra instalar expertise AWS, configurar MCP da AWS, ou adicionar agent skills AWS. Opt-in: clona skills curadas de aws/agent-toolkit-for-aws para .agents/skills/aws/ e registra o AWS MCP Server unificado (stdio via mcp-proxy-for-aws). Requer uv, aws cli e credenciais AWS.' },
|
|
41
51
|
{ name: 'dw-install-azure-skills', description: 'Trigger quando usuario pede pra instalar expertise Azure, configurar MCP do Microsoft docs, ou adicionar agent skills Azure. Opt-in: clona skills curadas de MicrosoftDocs/Agent-Skills para .agents/skills/azure/ e registra o Microsoft Learn MCP server.' },
|
|
42
52
|
{ name: 'dw-intel', description: 'Inteligencia do codebase: modo query (default) responde citando .dw/intel/ + .dw/rules/; modo --build (re)constroi indice.' },
|
|
@@ -49,6 +59,10 @@ const COMMANDS = {
|
|
|
49
59
|
{ name: 'dw-review', description: 'Trigger quando usuario pede pra revisar codigo, checar qualidade ou validar prontidao pra PR. Default roda L2 (cobertura PRD) + L3 (qualidade). Flags --coverage-only / --code-only / --bugfix <slug>.' },
|
|
50
60
|
{ name: 'dw-run', description: 'Trigger quando usuario quer executar tasks. Default roda todas pendentes em ordem de dependencia; \'run <task-id>\' roda uma; \'run --resume\' continua plan interrompido.' },
|
|
51
61
|
{ name: 'dw-secure-audit', description: 'Audit unificado de seguranca: OWASP + Trivy SCA/secret/IaC + lockfile + supply-chain compromise. Hidden; auto-invocado por /dw-review e /dw-generate-pr.' },
|
|
62
|
+
{ name: 'dw-skill-health', description: 'Audita skills e agentes instalados por bloat, duplicacao, referencias quebradas e modulos sem uso.' },
|
|
63
|
+
{ name: 'dw-subtask-start', description: 'Cria um input packet minimo para subagente sem injetar transcript bruto do parent.' },
|
|
64
|
+
{ name: 'dw-subtask-complete', description: 'Registra handoff estruturado do subagente com resultado, arquivos, decisoes, riscos, verificacao e bloqueios.' },
|
|
65
|
+
{ name: 'dw-subtask-resume', description: 'Consome handoffs pendentes de subagentes, resume para o parent e arquiva os pacotes locais.' },
|
|
52
66
|
{ name: 'dw-update', description: 'Atualiza o dev-workflow para o release mais recente no npm, com snapshot para rollback.' },
|
|
53
67
|
],
|
|
54
68
|
};
|
package/lib/doctor.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { COMMANDS, PLATFORMS, MCP_SERVERS } = require('./constants');
|
|
4
|
+
const {
|
|
5
|
+
listExpectedAgentFiles,
|
|
6
|
+
listLegacyAgentFiles,
|
|
7
|
+
readAgentRegistry,
|
|
8
|
+
validateAgentRegistry,
|
|
9
|
+
AGENT_TARGETS,
|
|
10
|
+
} = require('./agents');
|
|
11
|
+
const { readInstallState, STATE_RELATIVE_PATH } = require('./install-state');
|
|
12
|
+
const { validateSkillRegistry, readSkillRegistry } = require('./skill-registry');
|
|
13
|
+
const removedCommands = require('./removed-commands');
|
|
14
|
+
|
|
15
|
+
function run() {
|
|
16
|
+
const projectRoot = process.cwd();
|
|
17
|
+
const state = readInstallState(projectRoot);
|
|
18
|
+
const issues = [];
|
|
19
|
+
const warnings = [];
|
|
20
|
+
|
|
21
|
+
if (!state) {
|
|
22
|
+
issues.push(`Missing ${STATE_RELATIVE_PATH}. Run \`dev-workflow repair\`.`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const lang = (state && state.lang) || 'en';
|
|
26
|
+
const modules = (state && state.modules) || ['core', 'security', 'frontend'];
|
|
27
|
+
const commands = COMMANDS[lang] || COMMANDS.en;
|
|
28
|
+
|
|
29
|
+
checkCommands(projectRoot, commands, issues);
|
|
30
|
+
checkWrappers(projectRoot, commands, issues);
|
|
31
|
+
checkAgents(projectRoot, modules, issues);
|
|
32
|
+
checkAgentRegistry(issues, warnings);
|
|
33
|
+
checkSubtaskLayout(projectRoot, issues);
|
|
34
|
+
checkSkillRegistry(projectRoot, issues, warnings);
|
|
35
|
+
checkMcp(projectRoot, issues);
|
|
36
|
+
checkLegacyCommands(projectRoot, warnings);
|
|
37
|
+
|
|
38
|
+
console.log('dev-workflow doctor\n');
|
|
39
|
+
if (issues.length === 0 && warnings.length === 0) {
|
|
40
|
+
console.log('PASS: managed commands, wrappers, agents, skills, and MCP config look healthy.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (issues.length) {
|
|
45
|
+
console.log('Issues:');
|
|
46
|
+
for (const issue of issues) console.log(`- ${issue}`);
|
|
47
|
+
console.log();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (warnings.length) {
|
|
51
|
+
console.log('Warnings:');
|
|
52
|
+
for (const warning of warnings) console.log(`- ${warning}`);
|
|
53
|
+
console.log();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log('Run `npx @brunosps00/dev-workflow repair` to reconcile managed files.');
|
|
57
|
+
if (issues.length) process.exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function checkCommands(projectRoot, commands, issues) {
|
|
61
|
+
for (const cmd of commands) {
|
|
62
|
+
const file = path.join(projectRoot, '.dw', 'commands', `${cmd.name}.md`);
|
|
63
|
+
if (!fs.existsSync(file)) issues.push(`Missing command source: ${path.relative(projectRoot, file)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function checkWrappers(projectRoot, commands, issues) {
|
|
68
|
+
for (const [, platform] of Object.entries(PLATFORMS)) {
|
|
69
|
+
for (const cmd of commands) {
|
|
70
|
+
const file = platform.flat
|
|
71
|
+
? path.join(projectRoot, platform.dir, `${cmd.name}.md`)
|
|
72
|
+
: path.join(projectRoot, platform.dir, cmd.name, 'SKILL.md');
|
|
73
|
+
if (!fs.existsSync(file)) issues.push(`Missing wrapper: ${path.relative(projectRoot, file)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function checkAgents(projectRoot, modules, issues) {
|
|
79
|
+
for (const file of listExpectedAgentFiles(projectRoot, modules)) {
|
|
80
|
+
if (!fs.existsSync(file)) issues.push(`Missing agent: ${path.relative(projectRoot, file)}`);
|
|
81
|
+
}
|
|
82
|
+
for (const file of listLegacyAgentFiles(projectRoot, modules)) {
|
|
83
|
+
if (fs.existsSync(file)) issues.push(`Legacy Copilot agent path still exists: ${path.relative(projectRoot, file)}`);
|
|
84
|
+
}
|
|
85
|
+
for (const file of listExpectedAgentFiles(projectRoot, modules)) {
|
|
86
|
+
if (fs.existsSync(file) && file.endsWith('.md') && fs.statSync(file).size > 8 * 1024) {
|
|
87
|
+
issues.push(`Agent prompt exceeds 8KB: ${path.relative(projectRoot, file)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
checkProviderAgentCompatibility(projectRoot, modules, issues);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function checkAgentRegistry(issues, warnings) {
|
|
94
|
+
const registry = readAgentRegistry();
|
|
95
|
+
const validation = validateAgentRegistry(registry);
|
|
96
|
+
issues.push(...validation.issues);
|
|
97
|
+
warnings.push(...validation.warnings);
|
|
98
|
+
const installedRegistryPath = path.join(process.cwd(), '.dw', 'agent-registry.json');
|
|
99
|
+
if (!fs.existsSync(installedRegistryPath)) {
|
|
100
|
+
issues.push(`Missing agent registry: ${path.relative(process.cwd(), installedRegistryPath)}`);
|
|
101
|
+
} else {
|
|
102
|
+
try {
|
|
103
|
+
const installedRegistry = JSON.parse(fs.readFileSync(installedRegistryPath, 'utf-8'));
|
|
104
|
+
if (installedRegistry.schema_version !== '2.0') {
|
|
105
|
+
issues.push('Installed agent registry must use schema_version 2.0.');
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
issues.push(`Malformed agent registry: ${path.relative(process.cwd(), installedRegistryPath)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const agentNames = new Set(registry.agents.map((agent) => agent.name));
|
|
112
|
+
for (const [commandName, agents] of Object.entries(registry.commands || {})) {
|
|
113
|
+
if (!COMMANDS.en.some((cmd) => cmd.name === commandName)) {
|
|
114
|
+
issues.push(`Agent registry references unknown command: ${commandName}`);
|
|
115
|
+
}
|
|
116
|
+
for (const agentName of agents) {
|
|
117
|
+
if (!agentNames.has(agentName)) {
|
|
118
|
+
issues.push(`Agent registry references missing agent: ${agentName}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function checkProviderAgentCompatibility(projectRoot, modules, issues) {
|
|
125
|
+
const expected = listExpectedAgentFiles(projectRoot, modules);
|
|
126
|
+
for (const file of expected) {
|
|
127
|
+
if (!fs.existsSync(file)) continue;
|
|
128
|
+
const rel = path.relative(projectRoot, file);
|
|
129
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
130
|
+
if (rel.startsWith(`${AGENT_TARGETS.copilot.dir}/`)) {
|
|
131
|
+
for (const unsupported of ['permissionMode:', 'maxTurns:', 'model:', 'mode: subagent']) {
|
|
132
|
+
if (content.includes(unsupported)) issues.push(`Copilot agent contains unsupported field ${unsupported} ${rel}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (rel.startsWith(`${AGENT_TARGETS.claude.dir}/`) && !content.includes('permissionMode:')) {
|
|
136
|
+
issues.push(`Claude agent missing permissionMode: ${rel}`);
|
|
137
|
+
}
|
|
138
|
+
if (rel.startsWith(`${AGENT_TARGETS.opencode.dir}/`) && content.includes('\ntools:')) {
|
|
139
|
+
issues.push(`OpenCode agent should use permission instead of tools: ${rel}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function checkSubtaskLayout(projectRoot, issues) {
|
|
145
|
+
const gitignore = path.join(projectRoot, '.dw', 'subtasks', '.gitignore');
|
|
146
|
+
if (!fs.existsSync(gitignore)) issues.push(`Missing subtask gitignore: ${path.relative(projectRoot, gitignore)}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function checkSkillRegistry(projectRoot, issues, warnings) {
|
|
150
|
+
const validation = validateSkillRegistry();
|
|
151
|
+
issues.push(...validation.issues);
|
|
152
|
+
warnings.push(...validation.warnings);
|
|
153
|
+
|
|
154
|
+
const installedRegistryPath = path.join(projectRoot, '.dw', 'skill-registry.json');
|
|
155
|
+
if (!fs.existsSync(installedRegistryPath)) {
|
|
156
|
+
issues.push(`Missing skill registry: ${path.relative(projectRoot, installedRegistryPath)}`);
|
|
157
|
+
} else {
|
|
158
|
+
try {
|
|
159
|
+
const installedRegistry = JSON.parse(fs.readFileSync(installedRegistryPath, 'utf-8'));
|
|
160
|
+
const currentRegistry = readSkillRegistry();
|
|
161
|
+
if (installedRegistry.schema_version !== currentRegistry.schema_version) {
|
|
162
|
+
warnings.push('Installed skill registry schema differs from package registry; run repair.');
|
|
163
|
+
}
|
|
164
|
+
const installedNames = new Set((installedRegistry.skills || []).map((entry) => entry.name));
|
|
165
|
+
for (const entry of currentRegistry.skills) {
|
|
166
|
+
if (entry.bundled && !installedNames.has(entry.name)) {
|
|
167
|
+
warnings.push(`Installed skill registry missing bundled entry: ${entry.name}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
issues.push(`Malformed skill registry: ${path.relative(projectRoot, installedRegistryPath)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const registry = readSkillRegistry();
|
|
176
|
+
for (const entry of registry.skills) {
|
|
177
|
+
if (!entry.bundled) continue;
|
|
178
|
+
const skillPath = path.join(projectRoot, '.agents', 'skills', entry.name, 'SKILL.md');
|
|
179
|
+
if (!fs.existsSync(skillPath)) {
|
|
180
|
+
issues.push(`Missing bundled skill: ${path.relative(projectRoot, skillPath)}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function checkMcp(projectRoot, issues) {
|
|
186
|
+
const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
|
|
187
|
+
if (!fs.existsSync(settingsPath)) {
|
|
188
|
+
issues.push('Missing .claude/settings.json with MCP configuration');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
194
|
+
for (const name of Object.keys(MCP_SERVERS)) {
|
|
195
|
+
if (!settings.mcpServers || !settings.mcpServers[name]) {
|
|
196
|
+
issues.push(`Missing MCP server: ${name}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
issues.push('Malformed .claude/settings.json');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function checkLegacyCommands(projectRoot, warnings) {
|
|
205
|
+
for (const entry of removedCommands) {
|
|
206
|
+
const file = path.join(projectRoot, '.dw', 'commands', `${entry.name}.md`);
|
|
207
|
+
if (fs.existsSync(file)) {
|
|
208
|
+
warnings.push(`Legacy command source still exists: ${path.relative(projectRoot, file)} -> ${entry.replacedBy}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = { run };
|