@brunosps00/dev-workflow 1.0.4 → 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.
Files changed (66) hide show
  1. package/README.md +58 -14
  2. package/bin/dev-workflow.js +29 -3
  3. package/lib/agents.js +410 -0
  4. package/lib/constants.js +12 -0
  5. package/lib/doctor.js +213 -0
  6. package/lib/init.js +96 -8
  7. package/lib/install-state.js +57 -0
  8. package/lib/list-installed.js +27 -0
  9. package/lib/migrate-skills.js +26 -2
  10. package/lib/profiles.js +44 -0
  11. package/lib/skill-registry.js +109 -0
  12. package/lib/subtasks.js +264 -0
  13. package/lib/uninstall.js +21 -12
  14. package/lib/utils.js +42 -1
  15. package/lib/wrappers.js +2 -1
  16. package/package.json +1 -1
  17. package/scaffold/agent-registry.json +272 -0
  18. package/scaffold/agents/core/dw-build-fixer.md +21 -0
  19. package/scaffold/agents/core/dw-code-explorer.md +32 -0
  20. package/scaffold/agents/core/dw-code-reviewer.md +28 -0
  21. package/scaffold/agents/core/dw-planner.md +23 -0
  22. package/scaffold/agents/core/dw-test-author.md +21 -0
  23. package/scaffold/agents/csharp/dw-csharp-reviewer.md +15 -0
  24. package/scaffold/agents/csharp/dw-dotnet-build-fixer.md +15 -0
  25. package/scaffold/agents/frontend/dw-qa-runner.md +21 -0
  26. package/scaffold/agents/python/dw-python-build-fixer.md +15 -0
  27. package/scaffold/agents/python/dw-python-reviewer.md +15 -0
  28. package/scaffold/agents/rust/dw-rust-build-fixer.md +15 -0
  29. package/scaffold/agents/rust/dw-rust-reviewer.md +15 -0
  30. package/scaffold/agents/security/dw-security-reviewer.md +24 -0
  31. package/scaffold/agents/security/dw-silent-failure-hunter.md +22 -0
  32. package/scaffold/agents/typescript/dw-typescript-build-fixer.md +15 -0
  33. package/scaffold/agents/typescript/dw-typescript-reviewer.md +15 -0
  34. package/scaffold/en/agent-instructions.md +44 -1
  35. package/scaffold/en/commands/dw-bugfix.md +9 -0
  36. package/scaffold/en/commands/dw-context-budget.md +28 -0
  37. package/scaffold/en/commands/dw-harness-audit.md +31 -0
  38. package/scaffold/en/commands/dw-help.md +15 -5
  39. package/scaffold/en/commands/dw-intel.md +6 -0
  40. package/scaffold/en/commands/dw-plan.md +11 -0
  41. package/scaffold/en/commands/dw-review.md +12 -0
  42. package/scaffold/en/commands/dw-skill-health.md +29 -0
  43. package/scaffold/en/commands/dw-subtask-complete.md +40 -0
  44. package/scaffold/en/commands/dw-subtask-resume.md +21 -0
  45. package/scaffold/en/commands/dw-subtask-start.md +22 -0
  46. package/scaffold/pt-br/agent-instructions.md +44 -1
  47. package/scaffold/pt-br/commands/dw-bugfix.md +9 -0
  48. package/scaffold/pt-br/commands/dw-context-budget.md +28 -0
  49. package/scaffold/pt-br/commands/dw-harness-audit.md +31 -0
  50. package/scaffold/pt-br/commands/dw-help.md +15 -5
  51. package/scaffold/pt-br/commands/dw-intel.md +6 -0
  52. package/scaffold/pt-br/commands/dw-plan.md +11 -0
  53. package/scaffold/pt-br/commands/dw-review.md +12 -0
  54. package/scaffold/pt-br/commands/dw-skill-health.md +29 -0
  55. package/scaffold/pt-br/commands/dw-subtask-complete.md +40 -0
  56. package/scaffold/pt-br/commands/dw-subtask-resume.md +21 -0
  57. package/scaffold/pt-br/commands/dw-subtask-start.md +22 -0
  58. package/scaffold/skill-registry.json +258 -0
  59. package/scaffold/skills/dw-codebase-intel/SKILL.md +12 -5
  60. package/scaffold/skills/dw-context-budget/SKILL.md +45 -0
  61. package/scaffold/skills/dw-search-first/SKILL.md +31 -0
  62. package/scaffold/skills/dw-silent-failure/SKILL.md +33 -0
  63. package/scaffold/skills/dw-verify/SKILL.md +2 -29
  64. package/scaffold/skills/dw-verify/references/verification-discovery-and-attribution.md +32 -0
  65. package/scaffold/skills/humanizer/SKILL.md +7 -412
  66. package/scaffold/skills/humanizer/references/ai-writing-patterns.md +418 -0
@@ -0,0 +1,109 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { readJson } = require('./utils');
4
+
5
+ const SCAFFOLD_DIR = path.join(__dirname, '..', 'scaffold');
6
+ const REGISTRY_PATH = path.join(SCAFFOLD_DIR, 'skill-registry.json');
7
+ const SKILLS_DIR = path.join(SCAFFOLD_DIR, 'skills');
8
+
9
+ const VALID_KINDS = new Set(['protocol', 'domain-pack', 'recipe-pack', 'asset-pack']);
10
+ const VALID_LOAD_POLICIES = new Set(['always-small', 'lazy-references', 'opt-in-runtime']);
11
+
12
+ function readSkillRegistry() {
13
+ const registry = readJson(REGISTRY_PATH, null);
14
+ if (!registry || !Array.isArray(registry.skills)) {
15
+ throw new Error('Invalid scaffold/skill-registry.json');
16
+ }
17
+ return registry;
18
+ }
19
+
20
+ function listScaffoldSkillNames() {
21
+ if (!fs.existsSync(SKILLS_DIR)) return [];
22
+ return fs
23
+ .readdirSync(SKILLS_DIR, { withFileTypes: true })
24
+ .filter((entry) => entry.isDirectory())
25
+ .map((entry) => entry.name)
26
+ .sort();
27
+ }
28
+
29
+ function validateSkillRegistry() {
30
+ const registry = readSkillRegistry();
31
+ const issues = [];
32
+ const warnings = [];
33
+ const entriesByName = new Map();
34
+ const scaffoldNames = new Set(listScaffoldSkillNames());
35
+
36
+ for (const entry of registry.skills) {
37
+ validateEntry(entry, issues);
38
+ if (entriesByName.has(entry.name)) {
39
+ issues.push(`Skill registry duplicates entry: ${entry.name}`);
40
+ }
41
+ entriesByName.set(entry.name, entry);
42
+
43
+ if (!scaffoldNames.has(entry.name)) {
44
+ issues.push(`Skill registry references missing scaffold skill: ${entry.name}`);
45
+ continue;
46
+ }
47
+
48
+ const skillPath = path.join(SKILLS_DIR, entry.name, 'SKILL.md');
49
+ if (!fs.existsSync(skillPath)) {
50
+ issues.push(`Scaffold skill missing SKILL.md: ${entry.name}`);
51
+ continue;
52
+ }
53
+
54
+ const size = fs.statSync(skillPath).size;
55
+ if (entry.context_limit && size > entry.context_limit) {
56
+ warnings.push(`Skill ${entry.name} SKILL.md is ${size} bytes; registry limit is ${entry.context_limit}`);
57
+ }
58
+
59
+ if (entry.load_policy === 'always-small' && size > 8000) {
60
+ warnings.push(`Skill ${entry.name} is marked always-small but SKILL.md is ${size} bytes`);
61
+ }
62
+ }
63
+
64
+ for (const skillName of scaffoldNames) {
65
+ if (!entriesByName.has(skillName)) {
66
+ issues.push(`Scaffold skill missing from registry: ${skillName}`);
67
+ }
68
+ }
69
+
70
+ return { issues, warnings, registry };
71
+ }
72
+
73
+ function validateEntry(entry, issues) {
74
+ for (const field of ['name', 'kind', 'tier', 'owner', 'trigger', 'expected_output', 'load_policy', 'context_limit']) {
75
+ if (entry[field] === undefined || entry[field] === null || entry[field] === '') {
76
+ issues.push(`Skill registry entry ${entry.name || '(unknown)'} missing field: ${field}`);
77
+ }
78
+ }
79
+
80
+ if (entry.kind && !VALID_KINDS.has(entry.kind)) {
81
+ issues.push(`Skill registry entry ${entry.name} has invalid kind: ${entry.kind}`);
82
+ }
83
+
84
+ if (entry.load_policy && !VALID_LOAD_POLICIES.has(entry.load_policy)) {
85
+ issues.push(`Skill registry entry ${entry.name} has invalid load_policy: ${entry.load_policy}`);
86
+ }
87
+
88
+ if (typeof entry.context_limit !== 'number' || entry.context_limit <= 0) {
89
+ issues.push(`Skill registry entry ${entry.name} must set a positive numeric context_limit`);
90
+ }
91
+ }
92
+
93
+ function listInstalledSkillNames(projectRoot) {
94
+ const installedDir = path.join(projectRoot, '.agents', 'skills');
95
+ if (!fs.existsSync(installedDir)) return [];
96
+ return fs
97
+ .readdirSync(installedDir, { withFileTypes: true })
98
+ .filter((entry) => entry.isDirectory() && fs.existsSync(path.join(installedDir, entry.name, 'SKILL.md')))
99
+ .map((entry) => entry.name)
100
+ .sort();
101
+ }
102
+
103
+ module.exports = {
104
+ REGISTRY_PATH,
105
+ readSkillRegistry,
106
+ validateSkillRegistry,
107
+ listScaffoldSkillNames,
108
+ listInstalledSkillNames,
109
+ };
@@ -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
- // Currently-active bundled skills. If a skill is removed in a future release,
60
- // add it to lib/removed-bundled-skills.js (append-only manifest used by
61
- // migrate-skills.js during `dev-workflow update`); this list is just for
62
- // wholesale uninstall.
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 = { ensureDir, fileExists, copyFile, writeFile, copyDir, upsertDelimitedBlock, log };
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.4",
3
+ "version": "1.0.5",
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"