@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/subtasks.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { ensureDir, writeFile } = require('./utils');
|
|
4
|
+
const { readAgentRegistry } = require('./agents');
|
|
5
|
+
|
|
6
|
+
const SUBTASKS_RELATIVE_DIR = path.join('.dw', 'subtasks');
|
|
7
|
+
|
|
8
|
+
function run(argv = []) {
|
|
9
|
+
const action = argv[0];
|
|
10
|
+
const flags = parseFlags(argv.slice(1));
|
|
11
|
+
const projectRoot = process.cwd();
|
|
12
|
+
|
|
13
|
+
switch (action) {
|
|
14
|
+
case 'create':
|
|
15
|
+
createSubtask(projectRoot, flags);
|
|
16
|
+
break;
|
|
17
|
+
case 'complete':
|
|
18
|
+
completeSubtask(projectRoot, flags);
|
|
19
|
+
break;
|
|
20
|
+
case 'consume':
|
|
21
|
+
consumeSubtasks(projectRoot);
|
|
22
|
+
break;
|
|
23
|
+
case 'list':
|
|
24
|
+
listSubtasks(projectRoot);
|
|
25
|
+
break;
|
|
26
|
+
case undefined:
|
|
27
|
+
case 'help':
|
|
28
|
+
case '--help':
|
|
29
|
+
case '-h':
|
|
30
|
+
printHelp();
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(`Unknown subtask action: ${action}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createSubtask(projectRoot, flags) {
|
|
38
|
+
const agent = required(flags, 'agent');
|
|
39
|
+
const goal = required(flags, 'goal');
|
|
40
|
+
validateAgent(agent);
|
|
41
|
+
ensureSubtaskLayout(projectRoot);
|
|
42
|
+
|
|
43
|
+
const now = new Date();
|
|
44
|
+
const stamp = now.toISOString().replace(/[-:]/g, '').replace(/\..+$/, 'Z');
|
|
45
|
+
const slug = `${stamp}-${slugify(goal)}`;
|
|
46
|
+
const dir = path.join(projectRoot, SUBTASKS_RELATIVE_DIR, 'pending', slug);
|
|
47
|
+
ensureDir(dir);
|
|
48
|
+
|
|
49
|
+
const task = [
|
|
50
|
+
`# Subtask: ${goal}`,
|
|
51
|
+
'',
|
|
52
|
+
`- Slug: \`${slug}\``,
|
|
53
|
+
`- Agent: \`${agent}\``,
|
|
54
|
+
`- Created: ${now.toISOString()}`,
|
|
55
|
+
'',
|
|
56
|
+
'## Objective',
|
|
57
|
+
goal,
|
|
58
|
+
'',
|
|
59
|
+
'## Allowed Files And Sources',
|
|
60
|
+
flags.files || 'Parent must fill this before dispatch if the boundary is not obvious.',
|
|
61
|
+
'',
|
|
62
|
+
'## Constraints',
|
|
63
|
+
flags.constraints || 'Use project rules. Do not request or paste the full parent transcript.',
|
|
64
|
+
'',
|
|
65
|
+
'## Expected Output',
|
|
66
|
+
flags.output || 'Structured handoff with result, files read, files changed, decisions, risks, verification, next steps, and blockers.',
|
|
67
|
+
'',
|
|
68
|
+
'## Context Budget',
|
|
69
|
+
flags.budget || 'Use the agent registry default. Return only summarized logs and relevant paths.',
|
|
70
|
+
'',
|
|
71
|
+
'## Stop Condition',
|
|
72
|
+
flags.stop || 'Stop when the expected output packet is complete or the scope exceeds the packet.',
|
|
73
|
+
'',
|
|
74
|
+
'## Handoff Command',
|
|
75
|
+
'',
|
|
76
|
+
`When complete, write a handoff markdown file and run:`,
|
|
77
|
+
'',
|
|
78
|
+
'```bash',
|
|
79
|
+
`npx @brunosps00/dev-workflow subtask complete --slug=${slug} --file=<handoff.md>`,
|
|
80
|
+
'```',
|
|
81
|
+
'',
|
|
82
|
+
].join('\n');
|
|
83
|
+
|
|
84
|
+
writeFile(path.join(dir, 'TASK.md'), task, true);
|
|
85
|
+
console.log(path.relative(projectRoot, path.join(dir, 'TASK.md')));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function completeSubtask(projectRoot, flags) {
|
|
89
|
+
const slug = required(flags, 'slug');
|
|
90
|
+
const handoffFile = required(flags, 'file');
|
|
91
|
+
const taskDir = resolvePendingSubtask(projectRoot, slug);
|
|
92
|
+
const sourcePath = path.resolve(projectRoot, handoffFile);
|
|
93
|
+
if (!fs.existsSync(sourcePath)) throw new Error(`Handoff file not found: ${handoffFile}`);
|
|
94
|
+
|
|
95
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
96
|
+
validateHandoff(content);
|
|
97
|
+
fs.writeFileSync(path.join(taskDir, 'HANDOFF.md'), content.endsWith('\n') ? content : `${content}\n`, 'utf-8');
|
|
98
|
+
console.log(path.relative(projectRoot, path.join(taskDir, 'HANDOFF.md')));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function consumeSubtasks(projectRoot) {
|
|
102
|
+
ensureSubtaskLayout(projectRoot);
|
|
103
|
+
const pendingDir = path.join(projectRoot, SUBTASKS_RELATIVE_DIR, 'pending');
|
|
104
|
+
const entries = fs.readdirSync(pendingDir, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
105
|
+
const ready = entries
|
|
106
|
+
.map((entry) => path.join(pendingDir, entry.name))
|
|
107
|
+
.filter((dir) => fs.existsSync(path.join(dir, 'HANDOFF.md')));
|
|
108
|
+
|
|
109
|
+
if (!ready.length) {
|
|
110
|
+
console.log('No completed subtasks to consume.');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
115
|
+
const archiveDateDir = path.join(projectRoot, SUBTASKS_RELATIVE_DIR, 'archive', date);
|
|
116
|
+
ensureDir(archiveDateDir);
|
|
117
|
+
|
|
118
|
+
console.log('Subtask handoffs consumed:\n');
|
|
119
|
+
for (const dir of ready) {
|
|
120
|
+
const handoff = fs.readFileSync(path.join(dir, 'HANDOFF.md'), 'utf-8');
|
|
121
|
+
const summary = summarizeHandoff(handoff);
|
|
122
|
+
console.log(`- ${path.basename(dir)}: ${summary}`);
|
|
123
|
+
fs.renameSync(dir, path.join(archiveDateDir, path.basename(dir)));
|
|
124
|
+
}
|
|
125
|
+
console.log('\nArchive updated. Promote durable lessons manually into .dw/STATE.md, .dw/rules/, or .dw/intel/ when they should survive beyond this workstream.');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function listSubtasks(projectRoot) {
|
|
129
|
+
ensureSubtaskLayout(projectRoot);
|
|
130
|
+
const pendingDir = path.join(projectRoot, SUBTASKS_RELATIVE_DIR, 'pending');
|
|
131
|
+
const entries = fs.readdirSync(pendingDir, { withFileTypes: true }).filter((entry) => entry.isDirectory());
|
|
132
|
+
if (!entries.length) {
|
|
133
|
+
console.log('No pending subtasks.');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
const dir = path.join(pendingDir, entry.name);
|
|
138
|
+
const state = fs.existsSync(path.join(dir, 'HANDOFF.md')) ? 'complete' : 'pending';
|
|
139
|
+
console.log(`${entry.name}\t${state}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function ensureSubtaskLayout(projectRoot) {
|
|
144
|
+
const root = path.join(projectRoot, SUBTASKS_RELATIVE_DIR);
|
|
145
|
+
ensureDir(path.join(root, 'pending'));
|
|
146
|
+
ensureDir(path.join(root, 'archive'));
|
|
147
|
+
const gitignore = ['pending/', 'archive/', '', '!/.gitignore', ''].join('\n');
|
|
148
|
+
writeFile(path.join(root, '.gitignore'), gitignore, false);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function validateAgent(agentName) {
|
|
152
|
+
const registry = readAgentRegistry();
|
|
153
|
+
if (!registry.agents.some((agent) => agent.name === agentName)) {
|
|
154
|
+
throw new Error(`Unknown agent: ${agentName}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function validateHandoff(content) {
|
|
159
|
+
const requiredHeadings = [
|
|
160
|
+
'Objective',
|
|
161
|
+
'Result',
|
|
162
|
+
'Files Read',
|
|
163
|
+
'Files Changed',
|
|
164
|
+
'Decisions',
|
|
165
|
+
'Risks',
|
|
166
|
+
'Verification',
|
|
167
|
+
'Next Steps',
|
|
168
|
+
'Blocked Or Not Done',
|
|
169
|
+
];
|
|
170
|
+
const missing = requiredHeadings.filter((heading) => !new RegExp(`^##\\s+${escapeRegExp(heading)}\\b`, 'mi').test(content));
|
|
171
|
+
if (missing.length) {
|
|
172
|
+
throw new Error(`Handoff is missing required headings: ${missing.join(', ')}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function summarizeHandoff(content) {
|
|
177
|
+
const result = extractSection(content, 'Result') || firstNonEmptyLine(content) || 'handoff recorded';
|
|
178
|
+
const verification = extractSection(content, 'Verification');
|
|
179
|
+
const next = extractSection(content, 'Next Steps');
|
|
180
|
+
const parts = [compact(result)];
|
|
181
|
+
if (verification) parts.push(`verification: ${compact(verification)}`);
|
|
182
|
+
if (next) parts.push(`next: ${compact(next)}`);
|
|
183
|
+
return parts.join(' | ');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function extractSection(content, heading) {
|
|
187
|
+
const match = content.match(new RegExp(`^##\\s+${escapeRegExp(heading)}\\b\\s*\\n([\\s\\S]*?)(?=^##\\s+|\\s*$)`, 'mi'));
|
|
188
|
+
return match ? match[1].trim() : '';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function firstNonEmptyLine(content) {
|
|
192
|
+
return content.split('\n').map((line) => line.trim()).find(Boolean) || '';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function compact(value) {
|
|
196
|
+
return value.replace(/\s+/g, ' ').slice(0, 220);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function resolvePendingSubtask(projectRoot, slug) {
|
|
200
|
+
const pendingDir = path.join(projectRoot, SUBTASKS_RELATIVE_DIR, 'pending');
|
|
201
|
+
if (!fs.existsSync(pendingDir)) throw new Error('No pending subtasks directory. Run subtask create first.');
|
|
202
|
+
const direct = path.join(pendingDir, slug);
|
|
203
|
+
if (fs.existsSync(direct)) return direct;
|
|
204
|
+
const matches = fs.readdirSync(pendingDir).filter((entry) => entry === slug || entry.endsWith(`-${slug}`));
|
|
205
|
+
if (matches.length === 1) return path.join(pendingDir, matches[0]);
|
|
206
|
+
if (matches.length > 1) throw new Error(`Ambiguous slug: ${slug}`);
|
|
207
|
+
throw new Error(`Pending subtask not found: ${slug}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function parseFlags(args) {
|
|
211
|
+
const flags = {};
|
|
212
|
+
for (let index = 0; index < args.length; index++) {
|
|
213
|
+
const arg = args[index];
|
|
214
|
+
if (!arg.startsWith('--')) continue;
|
|
215
|
+
const raw = arg.slice(2);
|
|
216
|
+
if (raw.includes('=')) {
|
|
217
|
+
const [key, ...rest] = raw.split('=');
|
|
218
|
+
flags[key] = rest.join('=');
|
|
219
|
+
} else {
|
|
220
|
+
const next = args[index + 1];
|
|
221
|
+
flags[raw] = next && !next.startsWith('--') ? next : true;
|
|
222
|
+
if (flags[raw] === next) index++;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return flags;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function required(flags, name) {
|
|
229
|
+
if (!flags[name] || flags[name] === true) throw new Error(`Missing required flag: --${name}`);
|
|
230
|
+
return String(flags[name]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function slugify(value) {
|
|
234
|
+
return String(value)
|
|
235
|
+
.toLowerCase()
|
|
236
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
237
|
+
.replace(/^-+|-+$/g, '')
|
|
238
|
+
.slice(0, 60) || 'subtask';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function escapeRegExp(value) {
|
|
242
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function printHelp() {
|
|
246
|
+
console.log(`
|
|
247
|
+
dev-workflow subtask
|
|
248
|
+
|
|
249
|
+
Usage:
|
|
250
|
+
npx @brunosps00/dev-workflow subtask create --agent=<name> --goal="<goal>"
|
|
251
|
+
npx @brunosps00/dev-workflow subtask complete --slug=<slug> --file=<handoff.md>
|
|
252
|
+
npx @brunosps00/dev-workflow subtask consume
|
|
253
|
+
npx @brunosps00/dev-workflow subtask list
|
|
254
|
+
`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = {
|
|
258
|
+
run,
|
|
259
|
+
ensureSubtaskLayout,
|
|
260
|
+
createSubtask,
|
|
261
|
+
completeSubtask,
|
|
262
|
+
consumeSubtasks,
|
|
263
|
+
listSubtasks,
|
|
264
|
+
};
|
package/lib/uninstall.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { COMMANDS, PLATFORMS, MCP_SERVERS } = require('./constants');
|
|
4
|
+
const { AGENT_TARGETS, LEGACY_AGENT_TARGETS, readAgentRegistry } = require('./agents');
|
|
4
5
|
|
|
5
6
|
function run() {
|
|
6
7
|
const projectRoot = process.cwd();
|
|
@@ -37,6 +38,7 @@ function run() {
|
|
|
37
38
|
removeDir(path.join(projectRoot, '.dw', 'templates'), '.dw/templates/');
|
|
38
39
|
removeDir(path.join(projectRoot, '.dw', 'references'), '.dw/references/');
|
|
39
40
|
removeDir(path.join(projectRoot, '.dw', 'scripts'), '.dw/scripts/');
|
|
41
|
+
removeFile(path.join(projectRoot, '.dw', 'agent-registry.json'));
|
|
40
42
|
console.log();
|
|
41
43
|
|
|
42
44
|
// 2. Remove platform wrappers (only dw-* skills, not user skills)
|
|
@@ -56,23 +58,29 @@ function run() {
|
|
|
56
58
|
|
|
57
59
|
// 3. Remove bundled skills from .agents/skills/
|
|
58
60
|
console.log(' Bundled skills:');
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const bundledSkills = [
|
|
64
|
-
'dw-ui-discipline',
|
|
65
|
-
'dw-testing-discipline',
|
|
66
|
-
'humanizer',
|
|
67
|
-
'remotion-best-practices',
|
|
68
|
-
'security-review',
|
|
69
|
-
'vercel-react-best-practices',
|
|
70
|
-
];
|
|
61
|
+
const skillsDir = path.join(__dirname, '..', 'scaffold', 'skills');
|
|
62
|
+
const bundledSkills = fs.existsSync(skillsDir)
|
|
63
|
+
? fs.readdirSync(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name)
|
|
64
|
+
: [];
|
|
71
65
|
for (const skill of bundledSkills) {
|
|
72
66
|
removeDir(path.join(projectRoot, '.agents', 'skills', skill));
|
|
73
67
|
}
|
|
74
68
|
console.log();
|
|
75
69
|
|
|
70
|
+
// 3.5. Remove generated dev-workflow agents from platform folders.
|
|
71
|
+
console.log(' Agents:');
|
|
72
|
+
const registry = readAgentRegistry();
|
|
73
|
+
for (const agent of registry.agents) {
|
|
74
|
+
for (const target of Object.values(AGENT_TARGETS)) {
|
|
75
|
+
removeFile(path.join(projectRoot, target.dir, target.fileName(agent.name)));
|
|
76
|
+
}
|
|
77
|
+
for (const target of Object.values(LEGACY_AGENT_TARGETS)) {
|
|
78
|
+
removeFile(path.join(projectRoot, target.dir, target.fileName(agent.name)));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
removeFile(path.join(projectRoot, '.agents', 'agents', 'README.md'));
|
|
82
|
+
console.log();
|
|
83
|
+
|
|
76
84
|
// 4. Remove MCP servers from .claude/settings.json
|
|
77
85
|
console.log(' MCP Servers:');
|
|
78
86
|
const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
|
|
@@ -97,6 +105,7 @@ function run() {
|
|
|
97
105
|
|
|
98
106
|
// 5. Remove .opencode/package.json
|
|
99
107
|
removeFile(path.join(projectRoot, '.opencode', 'package.json'));
|
|
108
|
+
removeFile(path.join(projectRoot, '.dw', 'install-state.json'));
|
|
100
109
|
|
|
101
110
|
// 6. Remove legacy .codex/skills/
|
|
102
111
|
removeDir(path.join(projectRoot, '.codex', 'skills'));
|
package/lib/utils.js
CHANGED
|
@@ -60,6 +60,36 @@ function copyDir(srcDir, destDir, force = false, overridesDir = null) {
|
|
|
60
60
|
return results;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function listFilesRecursive(dirPath) {
|
|
64
|
+
const files = [];
|
|
65
|
+
if (!fs.existsSync(dirPath)) return files;
|
|
66
|
+
|
|
67
|
+
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
68
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
69
|
+
if (entry.isDirectory()) {
|
|
70
|
+
files.push(...listFilesRecursive(fullPath));
|
|
71
|
+
} else if (entry.isFile()) {
|
|
72
|
+
files.push(fullPath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return files;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readJson(filePath, fallback = null) {
|
|
80
|
+
if (!fs.existsSync(filePath)) return fallback;
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
83
|
+
} catch {
|
|
84
|
+
return fallback;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function writeJson(filePath, value) {
|
|
89
|
+
ensureDir(path.dirname(filePath));
|
|
90
|
+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf-8');
|
|
91
|
+
}
|
|
92
|
+
|
|
63
93
|
/**
|
|
64
94
|
* Upsert a delimited block inside an existing file.
|
|
65
95
|
*
|
|
@@ -113,4 +143,15 @@ function log(status, filePath) {
|
|
|
113
143
|
console.log(` ${icon} ${relative} [${status}]`);
|
|
114
144
|
}
|
|
115
145
|
|
|
116
|
-
module.exports = {
|
|
146
|
+
module.exports = {
|
|
147
|
+
ensureDir,
|
|
148
|
+
fileExists,
|
|
149
|
+
copyFile,
|
|
150
|
+
writeFile,
|
|
151
|
+
copyDir,
|
|
152
|
+
listFilesRecursive,
|
|
153
|
+
readJson,
|
|
154
|
+
writeJson,
|
|
155
|
+
upsertDelimitedBlock,
|
|
156
|
+
log,
|
|
157
|
+
};
|
package/lib/wrappers.js
CHANGED
|
@@ -3,7 +3,7 @@ const { PLATFORMS } = require('./constants');
|
|
|
3
3
|
const { writeFile, log } = require('./utils');
|
|
4
4
|
|
|
5
5
|
function generateWrappers(projectRoot, commands, force = false) {
|
|
6
|
-
const results = { created: 0, skipped: 0, overwritten: 0 };
|
|
6
|
+
const results = { created: 0, skipped: 0, overwritten: 0, files: [] };
|
|
7
7
|
|
|
8
8
|
for (const [platformName, platform] of Object.entries(PLATFORMS)) {
|
|
9
9
|
for (const cmd of commands) {
|
|
@@ -18,6 +18,7 @@ function generateWrappers(projectRoot, commands, force = false) {
|
|
|
18
18
|
const content = platform.wrapperTemplate(cmd.name, cmd.description);
|
|
19
19
|
const status = writeFile(skillFile, content, force);
|
|
20
20
|
results[status]++;
|
|
21
|
+
results.files.push(skillFile);
|
|
21
22
|
log(status, skillFile);
|
|
22
23
|
}
|
|
23
24
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brunosps00/dev-workflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "AI-driven development workflow commands for any project. Scaffolds a complete PRD-to-PR pipeline with multi-platform AI assistant support.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"dev-workflow": "./bin/dev-workflow.js"
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "2.0",
|
|
3
|
+
"agents": [
|
|
4
|
+
{
|
|
5
|
+
"name": "dw-code-explorer",
|
|
6
|
+
"module": "core",
|
|
7
|
+
"mode": "read-only",
|
|
8
|
+
"description": "Trace entry points, flows, dependencies, and existing patterns before planning or fixing code.",
|
|
9
|
+
"context_mode": "fresh",
|
|
10
|
+
"tool_policy": "read-only",
|
|
11
|
+
"max_turns": 8,
|
|
12
|
+
"input_budget_words": 900,
|
|
13
|
+
"output_budget_words": 900,
|
|
14
|
+
"parallel_safe": true,
|
|
15
|
+
"handoff_required": true,
|
|
16
|
+
"skill_policy": "lazy",
|
|
17
|
+
"recommended_for": ["unfamiliar code", "planning research", "wide grep or dependency tracing"],
|
|
18
|
+
"avoid_when": ["small direct edit", "needs full parent conversation", "implementation is already obvious"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "dw-planner",
|
|
22
|
+
"module": "core",
|
|
23
|
+
"mode": "read-only",
|
|
24
|
+
"description": "Turn requirements into implementation slices, dependencies, risks, and verification criteria.",
|
|
25
|
+
"context_mode": "fresh",
|
|
26
|
+
"tool_policy": "read-only",
|
|
27
|
+
"max_turns": 8,
|
|
28
|
+
"input_budget_words": 900,
|
|
29
|
+
"output_budget_words": 900,
|
|
30
|
+
"parallel_safe": true,
|
|
31
|
+
"handoff_required": true,
|
|
32
|
+
"skill_policy": "lazy",
|
|
33
|
+
"recommended_for": ["planning research", "task decomposition", "risk mapping"],
|
|
34
|
+
"avoid_when": ["small direct edit", "requirements need user clarification", "implementation and planning are tightly coupled"]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "dw-build-fixer",
|
|
38
|
+
"module": "core",
|
|
39
|
+
"mode": "write",
|
|
40
|
+
"description": "Fix build, typecheck, and lint failures with minimal diffs.",
|
|
41
|
+
"context_mode": "fresh",
|
|
42
|
+
"tool_policy": "write-scoped",
|
|
43
|
+
"max_turns": 12,
|
|
44
|
+
"input_budget_words": 1200,
|
|
45
|
+
"output_budget_words": 1200,
|
|
46
|
+
"parallel_safe": false,
|
|
47
|
+
"handoff_required": true,
|
|
48
|
+
"skill_policy": "lazy",
|
|
49
|
+
"recommended_for": ["failing build command", "typecheck failure", "lint failure after a known command"],
|
|
50
|
+
"avoid_when": ["no failing command exists", "fix needs architecture decision", "write scope is unclear"]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "dw-code-reviewer",
|
|
54
|
+
"module": "core",
|
|
55
|
+
"mode": "read-only",
|
|
56
|
+
"description": "Review changed code for correctness, maintainability, and defensible risks.",
|
|
57
|
+
"context_mode": "fresh",
|
|
58
|
+
"tool_policy": "read-only",
|
|
59
|
+
"max_turns": 8,
|
|
60
|
+
"input_budget_words": 900,
|
|
61
|
+
"output_budget_words": 900,
|
|
62
|
+
"parallel_safe": true,
|
|
63
|
+
"handoff_required": true,
|
|
64
|
+
"skill_policy": "lazy",
|
|
65
|
+
"recommended_for": ["independent code review", "PR readiness", "changed files with clear diff"],
|
|
66
|
+
"avoid_when": ["implementation is still moving", "no files or diff are provided", "style-only preference review"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "dw-test-author",
|
|
70
|
+
"module": "core",
|
|
71
|
+
"mode": "write",
|
|
72
|
+
"description": "Add focused tests and regression coverage using project conventions.",
|
|
73
|
+
"context_mode": "fresh",
|
|
74
|
+
"tool_policy": "write-scoped",
|
|
75
|
+
"max_turns": 12,
|
|
76
|
+
"input_budget_words": 1200,
|
|
77
|
+
"output_budget_words": 1200,
|
|
78
|
+
"parallel_safe": false,
|
|
79
|
+
"handoff_required": true,
|
|
80
|
+
"skill_policy": "lazy",
|
|
81
|
+
"recommended_for": ["focused regression coverage", "clear invariant to test", "known nearby test pattern"],
|
|
82
|
+
"avoid_when": ["behavior is unspecified", "test suite architecture needs redesign", "write scope is unclear"]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "dw-security-reviewer",
|
|
86
|
+
"module": "security",
|
|
87
|
+
"mode": "read-only",
|
|
88
|
+
"description": "Review sensitive surfaces such as auth, secrets, uploads, SQL, SSRF, and XSS.",
|
|
89
|
+
"context_mode": "fresh",
|
|
90
|
+
"tool_policy": "read-only",
|
|
91
|
+
"max_turns": 8,
|
|
92
|
+
"input_budget_words": 900,
|
|
93
|
+
"output_budget_words": 900,
|
|
94
|
+
"parallel_safe": true,
|
|
95
|
+
"handoff_required": true,
|
|
96
|
+
"skill_policy": "lazy",
|
|
97
|
+
"recommended_for": ["auth or authorization changes", "input boundary review", "supply-chain or secret risk"],
|
|
98
|
+
"avoid_when": ["purely cosmetic diff", "no sensitive surface is involved", "requires exploit research outside repo scope"]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "dw-silent-failure-hunter",
|
|
102
|
+
"module": "security",
|
|
103
|
+
"mode": "read-only",
|
|
104
|
+
"description": "Find swallowed errors, dangerous fallbacks, lost stack traces, and missing propagation.",
|
|
105
|
+
"context_mode": "fresh",
|
|
106
|
+
"tool_policy": "read-only",
|
|
107
|
+
"max_turns": 8,
|
|
108
|
+
"input_budget_words": 900,
|
|
109
|
+
"output_budget_words": 900,
|
|
110
|
+
"parallel_safe": true,
|
|
111
|
+
"handoff_required": true,
|
|
112
|
+
"skill_policy": "lazy",
|
|
113
|
+
"recommended_for": ["error handling review", "async detachment review", "fallback or retry changes"],
|
|
114
|
+
"avoid_when": ["no error path changed", "requires broad audit beyond input packet", "implementation is still in progress"]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "dw-qa-runner",
|
|
118
|
+
"module": "frontend",
|
|
119
|
+
"mode": "write-qa",
|
|
120
|
+
"description": "Create and run UI/API QA scripts, evidence, and retest logs under QA folders.",
|
|
121
|
+
"context_mode": "fresh",
|
|
122
|
+
"tool_policy": "qa-write",
|
|
123
|
+
"max_turns": 16,
|
|
124
|
+
"input_budget_words": 1200,
|
|
125
|
+
"output_budget_words": 1500,
|
|
126
|
+
"parallel_safe": false,
|
|
127
|
+
"handoff_required": true,
|
|
128
|
+
"skill_policy": "lazy",
|
|
129
|
+
"recommended_for": ["behavior-level QA", "Playwright or API flow validation", "retest evidence collection"],
|
|
130
|
+
"avoid_when": ["unit-only change", "environment cannot run", "bug fixing was not assigned"]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "dw-typescript-reviewer",
|
|
134
|
+
"module": "typescript",
|
|
135
|
+
"mode": "read-only",
|
|
136
|
+
"description": "Review TypeScript and JavaScript changes for type safety, async correctness, and web or Node risks.",
|
|
137
|
+
"context_mode": "fresh",
|
|
138
|
+
"tool_policy": "read-only",
|
|
139
|
+
"max_turns": 8,
|
|
140
|
+
"input_budget_words": 900,
|
|
141
|
+
"output_budget_words": 900,
|
|
142
|
+
"parallel_safe": true,
|
|
143
|
+
"handoff_required": true,
|
|
144
|
+
"skill_policy": "lazy",
|
|
145
|
+
"recommended_for": ["TypeScript or JavaScript diff review", "React or Node boundary changes", "type safety review"],
|
|
146
|
+
"avoid_when": ["global audit without paths", "non-TypeScript diff", "style-only review"]
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"name": "dw-typescript-build-fixer",
|
|
150
|
+
"module": "typescript",
|
|
151
|
+
"mode": "write",
|
|
152
|
+
"description": "Fix TypeScript and JavaScript build/type errors with minimal changes.",
|
|
153
|
+
"context_mode": "fresh",
|
|
154
|
+
"tool_policy": "write-scoped",
|
|
155
|
+
"max_turns": 12,
|
|
156
|
+
"input_budget_words": 1200,
|
|
157
|
+
"output_budget_words": 1200,
|
|
158
|
+
"parallel_safe": false,
|
|
159
|
+
"handoff_required": true,
|
|
160
|
+
"skill_policy": "lazy",
|
|
161
|
+
"recommended_for": ["TypeScript typecheck failure", "JavaScript build failure", "failing lint command"],
|
|
162
|
+
"avoid_when": ["no failing command exists", "fix requires API redesign", "strictness would need to be weakened"]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "dw-python-reviewer",
|
|
166
|
+
"module": "python",
|
|
167
|
+
"mode": "read-only",
|
|
168
|
+
"description": "Review Python changes for correctness, typing, async behavior, and framework risks.",
|
|
169
|
+
"context_mode": "fresh",
|
|
170
|
+
"tool_policy": "read-only",
|
|
171
|
+
"max_turns": 8,
|
|
172
|
+
"input_budget_words": 900,
|
|
173
|
+
"output_budget_words": 900,
|
|
174
|
+
"parallel_safe": true,
|
|
175
|
+
"handoff_required": true,
|
|
176
|
+
"skill_policy": "lazy",
|
|
177
|
+
"recommended_for": ["Python diff review", "async or exception handling review", "framework boundary review"],
|
|
178
|
+
"avoid_when": ["global audit without paths", "non-Python diff", "style-only review"]
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"name": "dw-python-build-fixer",
|
|
182
|
+
"module": "python",
|
|
183
|
+
"mode": "write",
|
|
184
|
+
"description": "Fix Python test, import, typing, and packaging failures with minimal changes.",
|
|
185
|
+
"context_mode": "fresh",
|
|
186
|
+
"tool_policy": "write-scoped",
|
|
187
|
+
"max_turns": 12,
|
|
188
|
+
"input_budget_words": 1200,
|
|
189
|
+
"output_budget_words": 1200,
|
|
190
|
+
"parallel_safe": false,
|
|
191
|
+
"handoff_required": true,
|
|
192
|
+
"skill_policy": "lazy",
|
|
193
|
+
"recommended_for": ["pytest failure", "ruff or mypy failure", "import or packaging failure"],
|
|
194
|
+
"avoid_when": ["no failing command exists", "behavior change is not proven", "dependency decision is required"]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"name": "dw-csharp-reviewer",
|
|
198
|
+
"module": "csharp",
|
|
199
|
+
"mode": "read-only",
|
|
200
|
+
"description": "Review C#/.NET code for API, async, dependency injection, and data-access risks.",
|
|
201
|
+
"context_mode": "fresh",
|
|
202
|
+
"tool_policy": "read-only",
|
|
203
|
+
"max_turns": 8,
|
|
204
|
+
"input_budget_words": 900,
|
|
205
|
+
"output_budget_words": 900,
|
|
206
|
+
"parallel_safe": true,
|
|
207
|
+
"handoff_required": true,
|
|
208
|
+
"skill_policy": "lazy",
|
|
209
|
+
"recommended_for": ["C# or .NET diff review", "DI or async review", "EF/data access review"],
|
|
210
|
+
"avoid_when": ["global audit without paths", "non-.NET diff", "style-only review"]
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "dw-dotnet-build-fixer",
|
|
214
|
+
"module": "csharp",
|
|
215
|
+
"mode": "write",
|
|
216
|
+
"description": "Fix .NET build, test, restore, and analyzer failures with minimal changes.",
|
|
217
|
+
"context_mode": "fresh",
|
|
218
|
+
"tool_policy": "write-scoped",
|
|
219
|
+
"max_turns": 12,
|
|
220
|
+
"input_budget_words": 1200,
|
|
221
|
+
"output_budget_words": 1200,
|
|
222
|
+
"parallel_safe": false,
|
|
223
|
+
"handoff_required": true,
|
|
224
|
+
"skill_policy": "lazy",
|
|
225
|
+
"recommended_for": ["dotnet restore failure", "dotnet build or test failure", "analyzer failure"],
|
|
226
|
+
"avoid_when": ["no failing command exists", "package or framework decision is required", "project structure redesign is needed"]
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"name": "dw-rust-reviewer",
|
|
230
|
+
"module": "rust",
|
|
231
|
+
"mode": "read-only",
|
|
232
|
+
"description": "Review Rust changes for ownership, error handling, concurrency, unsafe, and API design risks.",
|
|
233
|
+
"context_mode": "fresh",
|
|
234
|
+
"tool_policy": "read-only",
|
|
235
|
+
"max_turns": 8,
|
|
236
|
+
"input_budget_words": 900,
|
|
237
|
+
"output_budget_words": 900,
|
|
238
|
+
"parallel_safe": true,
|
|
239
|
+
"handoff_required": true,
|
|
240
|
+
"skill_policy": "lazy",
|
|
241
|
+
"recommended_for": ["Rust diff review", "unsafe or concurrency review", "public API review"],
|
|
242
|
+
"avoid_when": ["global audit without paths", "non-Rust diff", "style-only review"]
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"name": "dw-rust-build-fixer",
|
|
246
|
+
"module": "rust",
|
|
247
|
+
"mode": "write",
|
|
248
|
+
"description": "Fix Rust cargo check/test/clippy failures with minimal changes.",
|
|
249
|
+
"context_mode": "fresh",
|
|
250
|
+
"tool_policy": "write-scoped",
|
|
251
|
+
"max_turns": 12,
|
|
252
|
+
"input_budget_words": 1200,
|
|
253
|
+
"output_budget_words": 1200,
|
|
254
|
+
"parallel_safe": false,
|
|
255
|
+
"handoff_required": true,
|
|
256
|
+
"skill_policy": "lazy",
|
|
257
|
+
"recommended_for": ["cargo check failure", "cargo test failure", "cargo clippy failure"],
|
|
258
|
+
"avoid_when": ["no failing command exists", "public API decision is needed", "warning suppression would be broad"]
|
|
259
|
+
}
|
|
260
|
+
],
|
|
261
|
+
"commands": {
|
|
262
|
+
"dw-plan": ["dw-code-explorer", "dw-planner"],
|
|
263
|
+
"dw-run": ["dw-build-fixer", "dw-test-author"],
|
|
264
|
+
"dw-bugfix": ["dw-code-explorer", "dw-build-fixer", "dw-test-author", "dw-silent-failure-hunter"],
|
|
265
|
+
"dw-review": ["dw-code-reviewer", "dw-security-reviewer", "dw-silent-failure-hunter"],
|
|
266
|
+
"dw-qa": ["dw-qa-runner", "dw-test-author"],
|
|
267
|
+
"dw-intel": ["dw-code-explorer"],
|
|
268
|
+
"dw-subtask-start": ["dw-code-explorer", "dw-planner", "dw-build-fixer", "dw-test-author"],
|
|
269
|
+
"dw-subtask-complete": [],
|
|
270
|
+
"dw-subtask-resume": []
|
|
271
|
+
}
|
|
272
|
+
}
|