@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/init.js
CHANGED
|
@@ -1,38 +1,55 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const { COMMANDS } = require('./constants');
|
|
3
|
-
const { ensureDir, copyDir, writeFile, upsertDelimitedBlock, log } = require('./utils');
|
|
3
|
+
const { ensureDir, copyDir, writeFile, upsertDelimitedBlock, log, listFilesRecursive } = require('./utils');
|
|
4
4
|
const { selectLanguage } = require('./prompts');
|
|
5
5
|
const { generateWrappers } = require('./wrappers');
|
|
6
6
|
const { installMCPs } = require('./mcp');
|
|
7
|
+
const { generateAgents } = require('./agents');
|
|
7
8
|
const { migrate: migrateGsd } = require('./migrate-gsd');
|
|
8
9
|
const { migrate: migrateSkills } = require('./migrate-skills');
|
|
10
|
+
const { hasExistingInstall, readInstallState, writeInstallState, listManagedFiles } = require('./install-state');
|
|
11
|
+
const { resolveInstallSelection } = require('./profiles');
|
|
12
|
+
const { ensureSubtaskLayout } = require('./subtasks');
|
|
9
13
|
|
|
10
14
|
const SCAFFOLD_DIR = path.join(__dirname, '..', 'scaffold');
|
|
11
15
|
|
|
12
|
-
async function run({ force = false, lang = null, mode = 'init' }) {
|
|
16
|
+
async function run({ force = false, lang = null, mode = 'init', profile = null, modules = null }) {
|
|
13
17
|
const projectRoot = process.cwd();
|
|
14
18
|
const isUpdate = mode === 'update';
|
|
19
|
+
const isRepair = mode === 'repair';
|
|
20
|
+
const existingInstall = hasExistingInstall(projectRoot);
|
|
21
|
+
const priorState = readInstallState(projectRoot);
|
|
15
22
|
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
const
|
|
23
|
+
// Update/repair and init over an existing dev-workflow install reconcile managed
|
|
24
|
+
// files to the current scaffold. Fresh init keeps normal non-destructive behavior.
|
|
25
|
+
const shouldReconcile = isUpdate || isRepair || (mode === 'init' && existingInstall);
|
|
26
|
+
const managedForce = shouldReconcile ? true : force;
|
|
19
27
|
|
|
20
|
-
console.log(`\n dev-workflow ${isUpdate ? 'update' : 'init'}`);
|
|
28
|
+
console.log(`\n dev-workflow ${isRepair ? 'repair' : (isUpdate ? 'update' : 'init')}`);
|
|
21
29
|
console.log(` ${'='.repeat(40)}\n`);
|
|
30
|
+
if (mode === 'init' && existingInstall) {
|
|
31
|
+
console.log(' Existing dev-workflow install detected; reconciling managed files.\n');
|
|
32
|
+
}
|
|
22
33
|
|
|
23
34
|
// 0. GSD migration (idempotent; only runs if legacy GSD artifacts are detected)
|
|
24
35
|
// 0.1. Skill migration: removed bundled skills + orphan dw-* wrappers (idempotent)
|
|
25
|
-
if (
|
|
36
|
+
if (shouldReconcile) {
|
|
26
37
|
migrateGsd(projectRoot);
|
|
27
38
|
migrateSkills(projectRoot);
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
// 1. Select language
|
|
31
|
-
const selectedLang = await selectLanguage(lang);
|
|
42
|
+
const selectedLang = await selectLanguage(lang || (priorState && priorState.lang));
|
|
43
|
+
const selection = resolveInstallSelection({
|
|
44
|
+
profile: profile || (priorState && priorState.profile),
|
|
45
|
+
modules: modules || (priorState && Array.isArray(priorState.modules) ? priorState.modules.join(',') : null),
|
|
46
|
+
});
|
|
32
47
|
console.log(`\n Language: ${selectedLang}\n`);
|
|
48
|
+
console.log(` Profile: ${selection.profile} (${selection.modules.join(', ')})\n`);
|
|
33
49
|
|
|
34
50
|
const commands = COMMANDS[selectedLang];
|
|
35
51
|
const langDir = path.join(SCAFFOLD_DIR, selectedLang);
|
|
52
|
+
const touchedFiles = [];
|
|
36
53
|
|
|
37
54
|
// Track totals
|
|
38
55
|
let totalCreated = 0;
|
|
@@ -49,6 +66,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
49
66
|
totalCreated += cmdResults.created;
|
|
50
67
|
totalSkipped += cmdResults.skipped;
|
|
51
68
|
totalOverwritten += cmdResults.overwritten;
|
|
69
|
+
touchedFiles.push(...listFilesRecursive(path.join(projectRoot, '.dw', 'commands')));
|
|
52
70
|
console.log(` ${cmdResults.created} created, ${cmdResults.skipped} skipped, ${cmdResults.overwritten} overwritten\n`);
|
|
53
71
|
|
|
54
72
|
// 3. Copy templates (override-aware: .dw/templates/overrides/<name> wins over scaffold core)
|
|
@@ -64,6 +82,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
64
82
|
totalCreated += tplResults.created;
|
|
65
83
|
totalSkipped += tplResults.skipped;
|
|
66
84
|
totalOverwritten += tplResults.overwritten;
|
|
85
|
+
touchedFiles.push(...listFilesRecursive(templatesDir));
|
|
67
86
|
console.log(` ${tplResults.created} created, ${tplResults.skipped} skipped, ${tplResults.overwritten} overwritten\n`);
|
|
68
87
|
|
|
69
88
|
// 3.1. Seed .dw/templates/overrides/ with README on first init (never overwritten)
|
|
@@ -78,6 +97,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
78
97
|
);
|
|
79
98
|
if (ovrStatus === 'created') totalCreated++;
|
|
80
99
|
else totalSkipped++;
|
|
100
|
+
touchedFiles.push(overridesReadmeDest);
|
|
81
101
|
}
|
|
82
102
|
|
|
83
103
|
// 3.5. Copy references (language-specific)
|
|
@@ -92,6 +112,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
92
112
|
totalCreated += refsResults.created;
|
|
93
113
|
totalSkipped += refsResults.skipped;
|
|
94
114
|
totalOverwritten += refsResults.overwritten;
|
|
115
|
+
touchedFiles.push(...listFilesRecursive(path.join(projectRoot, '.dw', 'references')));
|
|
95
116
|
console.log(` ${refsResults.created} created, ${refsResults.skipped} skipped, ${refsResults.overwritten} overwritten\n`);
|
|
96
117
|
}
|
|
97
118
|
|
|
@@ -107,9 +128,46 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
107
128
|
totalCreated += scriptsResults.created;
|
|
108
129
|
totalSkipped += scriptsResults.skipped;
|
|
109
130
|
totalOverwritten += scriptsResults.overwritten;
|
|
131
|
+
touchedFiles.push(...listFilesRecursive(path.join(projectRoot, '.dw', 'scripts')));
|
|
110
132
|
console.log(` ${scriptsResults.created} created, ${scriptsResults.skipped} skipped, ${scriptsResults.overwritten} overwritten\n`);
|
|
111
133
|
}
|
|
112
134
|
|
|
135
|
+
// 3.7. Copy skill registry for agent-facing skill health audits.
|
|
136
|
+
const skillRegistrySrc = path.join(SCAFFOLD_DIR, 'skill-registry.json');
|
|
137
|
+
if (require('fs').existsSync(skillRegistrySrc)) {
|
|
138
|
+
console.log(' Skill registry:');
|
|
139
|
+
const skillRegistryDest = path.join(projectRoot, '.dw', 'skill-registry.json');
|
|
140
|
+
const registryStatus = writeFile(
|
|
141
|
+
skillRegistryDest,
|
|
142
|
+
require('fs').readFileSync(skillRegistrySrc, 'utf-8'),
|
|
143
|
+
managedForce
|
|
144
|
+
);
|
|
145
|
+
log(registryStatus, skillRegistryDest);
|
|
146
|
+
if (registryStatus === 'created') totalCreated++;
|
|
147
|
+
else if (registryStatus === 'overwritten') totalOverwritten++;
|
|
148
|
+
else totalSkipped++;
|
|
149
|
+
touchedFiles.push(skillRegistryDest);
|
|
150
|
+
console.log();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 3.8. Copy agent registry for dispatch, budgets, and provider health audits.
|
|
154
|
+
const agentRegistrySrc = path.join(SCAFFOLD_DIR, 'agent-registry.json');
|
|
155
|
+
if (require('fs').existsSync(agentRegistrySrc)) {
|
|
156
|
+
console.log(' Agent registry:');
|
|
157
|
+
const agentRegistryDest = path.join(projectRoot, '.dw', 'agent-registry.json');
|
|
158
|
+
const registryStatus = writeFile(
|
|
159
|
+
agentRegistryDest,
|
|
160
|
+
require('fs').readFileSync(agentRegistrySrc, 'utf-8'),
|
|
161
|
+
managedForce
|
|
162
|
+
);
|
|
163
|
+
log(registryStatus, agentRegistryDest);
|
|
164
|
+
if (registryStatus === 'created') totalCreated++;
|
|
165
|
+
else if (registryStatus === 'overwritten') totalOverwritten++;
|
|
166
|
+
else totalSkipped++;
|
|
167
|
+
touchedFiles.push(agentRegistryDest);
|
|
168
|
+
console.log();
|
|
169
|
+
}
|
|
170
|
+
|
|
113
171
|
// 4. Create .dw/rules/ with README
|
|
114
172
|
if (!isUpdate) {
|
|
115
173
|
console.log(' Rules:');
|
|
@@ -124,6 +182,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
124
182
|
log(status, rulesReadmeDest);
|
|
125
183
|
if (status === 'created') totalCreated++;
|
|
126
184
|
else totalSkipped++;
|
|
185
|
+
touchedFiles.push(rulesReadmeDest);
|
|
127
186
|
console.log();
|
|
128
187
|
}
|
|
129
188
|
|
|
@@ -146,6 +205,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
146
205
|
log(stateStatus, stateDestPath);
|
|
147
206
|
if (stateStatus === 'created') totalCreated++;
|
|
148
207
|
else totalSkipped++;
|
|
208
|
+
touchedFiles.push(stateDestPath);
|
|
149
209
|
}
|
|
150
210
|
console.log();
|
|
151
211
|
}
|
|
@@ -158,8 +218,13 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
158
218
|
const gitkeepStatus = writeFile(gitkeepPath, '', false);
|
|
159
219
|
if (gitkeepStatus === 'created') totalCreated++;
|
|
160
220
|
else totalSkipped++;
|
|
221
|
+
touchedFiles.push(gitkeepPath);
|
|
161
222
|
}
|
|
162
223
|
|
|
224
|
+
// 5.3. Create local ephemeral subtask handoff directories.
|
|
225
|
+
ensureSubtaskLayout(projectRoot);
|
|
226
|
+
touchedFiles.push(path.join(projectRoot, '.dw', 'subtasks', '.gitignore'));
|
|
227
|
+
|
|
163
228
|
// 5.5. Copy bundled skills to .agents/skills/
|
|
164
229
|
const skillsSrcDir = path.join(SCAFFOLD_DIR, 'skills');
|
|
165
230
|
if (require('fs').existsSync(skillsSrcDir)) {
|
|
@@ -172,9 +237,19 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
172
237
|
totalCreated += skillsResults.created;
|
|
173
238
|
totalSkipped += skillsResults.skipped;
|
|
174
239
|
totalOverwritten += skillsResults.overwritten;
|
|
240
|
+
touchedFiles.push(...listFilesRecursive(path.join(projectRoot, '.agents', 'skills')));
|
|
175
241
|
console.log(` ${skillsResults.created} created, ${skillsResults.skipped} skipped, ${skillsResults.overwritten} overwritten\n`);
|
|
176
242
|
}
|
|
177
243
|
|
|
244
|
+
// 5.6. Generate platform-native/fallback agents from the selected modules.
|
|
245
|
+
console.log(' Agents:');
|
|
246
|
+
const agentResults = generateAgents(projectRoot, selection.modules, managedForce);
|
|
247
|
+
totalCreated += agentResults.created;
|
|
248
|
+
totalSkipped += agentResults.skipped;
|
|
249
|
+
totalOverwritten += agentResults.overwritten;
|
|
250
|
+
touchedFiles.push(...agentResults.files);
|
|
251
|
+
console.log(` ${agentResults.created} created, ${agentResults.skipped} skipped, ${agentResults.overwritten} overwritten\n`);
|
|
252
|
+
|
|
178
253
|
// 5.7. Install CLAUDE.md + AGENTS.md with auto-trigger decision tree
|
|
179
254
|
// Both files receive the same content (Claude Code reads CLAUDE.md; Codex/Copilot/OpenCode
|
|
180
255
|
// converge on AGENTS.md). Merge-aware: user edits outside the dev-workflow markers are preserved.
|
|
@@ -191,6 +266,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
191
266
|
if (status === 'created') totalCreated++;
|
|
192
267
|
else if (status === 'updated') totalOverwritten++;
|
|
193
268
|
else totalSkipped++;
|
|
269
|
+
touchedFiles.push(target);
|
|
194
270
|
}
|
|
195
271
|
console.log();
|
|
196
272
|
}
|
|
@@ -201,6 +277,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
201
277
|
const opcStatus = writeFile(opencodePackageJson, opencodeContent, false);
|
|
202
278
|
if (opcStatus === 'created') totalCreated++;
|
|
203
279
|
else totalSkipped++;
|
|
280
|
+
touchedFiles.push(opencodePackageJson);
|
|
204
281
|
|
|
205
282
|
// 6.5. Clean up legacy .codex/skills/ (now served by .agents/skills/)
|
|
206
283
|
const legacyCodexDir = path.join(projectRoot, '.codex', 'skills');
|
|
@@ -216,6 +293,7 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
216
293
|
totalCreated += wrapperResults.created;
|
|
217
294
|
totalSkipped += wrapperResults.skipped;
|
|
218
295
|
totalOverwritten += wrapperResults.overwritten;
|
|
296
|
+
touchedFiles.push(...wrapperResults.files);
|
|
219
297
|
console.log();
|
|
220
298
|
|
|
221
299
|
// 9. Install MCPs (always, including updates)
|
|
@@ -223,6 +301,16 @@ async function run({ force = false, lang = null, mode = 'init' }) {
|
|
|
223
301
|
const mcpResults = installMCPs(projectRoot);
|
|
224
302
|
console.log(` ${mcpResults.added} configured, ${mcpResults.skipped} already present\n`);
|
|
225
303
|
|
|
304
|
+
// 9.5. Persist install state for diagnostics and repair.
|
|
305
|
+
const installState = writeInstallState(projectRoot, {
|
|
306
|
+
lang: selectedLang,
|
|
307
|
+
profile: selection.profile,
|
|
308
|
+
modules: selection.modules,
|
|
309
|
+
platforms: ['claude', 'agents', 'opencode', 'copilot'],
|
|
310
|
+
managedFiles: listManagedFiles(projectRoot, touchedFiles),
|
|
311
|
+
});
|
|
312
|
+
console.log(` Install state: ${path.relative(projectRoot, path.join(projectRoot, '.dw', 'install-state.json'))} (${installState.managed_files.length} files tracked)\n`);
|
|
313
|
+
|
|
226
314
|
// 10. Summary
|
|
227
315
|
console.log(` ${'='.repeat(40)}`);
|
|
228
316
|
console.log(` Done! ${totalCreated} created, ${totalSkipped} skipped, ${totalOverwritten} overwritten`);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const pkg = require('../package.json');
|
|
4
|
+
const { readJson, writeJson } = require('./utils');
|
|
5
|
+
|
|
6
|
+
const STATE_RELATIVE_PATH = path.join('.dw', 'install-state.json');
|
|
7
|
+
|
|
8
|
+
function statePath(projectRoot) {
|
|
9
|
+
return path.join(projectRoot, STATE_RELATIVE_PATH);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readInstallState(projectRoot) {
|
|
13
|
+
return readJson(statePath(projectRoot), null);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function hasExistingInstall(projectRoot) {
|
|
17
|
+
return Boolean(
|
|
18
|
+
fs.existsSync(path.join(projectRoot, '.dw', 'commands')) ||
|
|
19
|
+
fs.existsSync(path.join(projectRoot, 'CLAUDE.md')) ||
|
|
20
|
+
fs.existsSync(path.join(projectRoot, 'AGENTS.md')) ||
|
|
21
|
+
readInstallState(projectRoot)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeInstallState(projectRoot, state) {
|
|
26
|
+
const existing = readInstallState(projectRoot);
|
|
27
|
+
const now = new Date().toISOString();
|
|
28
|
+
const next = {
|
|
29
|
+
schema_version: '1.0',
|
|
30
|
+
package: pkg.name,
|
|
31
|
+
version: pkg.version,
|
|
32
|
+
lang: state.lang,
|
|
33
|
+
profile: state.profile,
|
|
34
|
+
modules: state.modules || [],
|
|
35
|
+
platforms: state.platforms || [],
|
|
36
|
+
managed_files: state.managedFiles || [],
|
|
37
|
+
installed_at: existing && existing.installed_at ? existing.installed_at : now,
|
|
38
|
+
updated_at: now,
|
|
39
|
+
};
|
|
40
|
+
writeJson(statePath(projectRoot), next);
|
|
41
|
+
return next;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function listManagedFiles(projectRoot, relativeFiles) {
|
|
45
|
+
return relativeFiles
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.map((filePath) => path.relative(projectRoot, filePath))
|
|
48
|
+
.sort();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
STATE_RELATIVE_PATH,
|
|
53
|
+
hasExistingInstall,
|
|
54
|
+
readInstallState,
|
|
55
|
+
writeInstallState,
|
|
56
|
+
listManagedFiles,
|
|
57
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { readInstallState, STATE_RELATIVE_PATH } = require('./install-state');
|
|
3
|
+
|
|
4
|
+
function run() {
|
|
5
|
+
const projectRoot = process.cwd();
|
|
6
|
+
const state = readInstallState(projectRoot);
|
|
7
|
+
|
|
8
|
+
if (!state) {
|
|
9
|
+
console.log(`No dev-workflow install state found at ${STATE_RELATIVE_PATH}.`);
|
|
10
|
+
console.log('Run `npx @brunosps00/dev-workflow init` or `update` to create it.');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log('dev-workflow install\n');
|
|
15
|
+
console.log(`Root: ${projectRoot}`);
|
|
16
|
+
console.log(`Package: ${state.package || '@brunosps00/dev-workflow'}@${state.version || '(unknown)'}`);
|
|
17
|
+
console.log(`Language: ${state.lang || '(unknown)'}`);
|
|
18
|
+
console.log(`Profile: ${state.profile || '(unknown)'}`);
|
|
19
|
+
console.log(`Modules: ${(state.modules || []).join(', ') || '(none)'}`);
|
|
20
|
+
console.log(`Platforms: ${(state.platforms || []).join(', ') || '(none)'}`);
|
|
21
|
+
console.log(`Managed files tracked: ${(state.managed_files || []).length}`);
|
|
22
|
+
console.log(`Installed at: ${state.installed_at || '(unknown)'}`);
|
|
23
|
+
console.log(`Updated at: ${state.updated_at || '(unknown)'}`);
|
|
24
|
+
console.log(`State: ${path.join(projectRoot, STATE_RELATIVE_PATH)}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = { run };
|
package/lib/migrate-skills.js
CHANGED
|
@@ -28,8 +28,9 @@ const removedCommands = require('./removed-commands');
|
|
|
28
28
|
function migrate(projectRoot) {
|
|
29
29
|
const removed = scanRemovedBundledSkills(projectRoot);
|
|
30
30
|
const orphans = scanOrphanCommandWrappers(projectRoot);
|
|
31
|
+
const commandSources = scanRemovedCommandSources(projectRoot);
|
|
31
32
|
|
|
32
|
-
if (removed.length === 0 && orphans.length === 0) {
|
|
33
|
+
if (removed.length === 0 && orphans.length === 0 && commandSources.length === 0) {
|
|
33
34
|
return null; // silent — nothing to clean
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -56,8 +57,16 @@ function migrate(projectRoot) {
|
|
|
56
57
|
rmRecursive(entry.path);
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
for (const entry of commandSources) {
|
|
61
|
+
const rel = path.relative(projectRoot, entry.path);
|
|
62
|
+
console.log(
|
|
63
|
+
` \x1b[33m-\x1b[0m [legacy] Removing command source ${rel} (replaced in v${entry.removedIn} by \`${entry.replacedBy}\`)`
|
|
64
|
+
);
|
|
65
|
+
rmRecursive(entry.path);
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
console.log();
|
|
60
|
-
return { removed: removed.length, orphans: orphans.length };
|
|
69
|
+
return { removed: removed.length, orphans: orphans.length, commandSources: commandSources.length };
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
function scanRemovedBundledSkills(projectRoot) {
|
|
@@ -128,6 +137,21 @@ function scanOrphanCommandWrappers(projectRoot) {
|
|
|
128
137
|
return orphans;
|
|
129
138
|
}
|
|
130
139
|
|
|
140
|
+
function scanRemovedCommandSources(projectRoot) {
|
|
141
|
+
const matches = [];
|
|
142
|
+
const commandsDir = path.join(projectRoot, '.dw', 'commands');
|
|
143
|
+
if (!fs.existsSync(commandsDir)) return matches;
|
|
144
|
+
|
|
145
|
+
for (const entry of removedCommands) {
|
|
146
|
+
const target = path.join(commandsDir, `${entry.name}.md`);
|
|
147
|
+
if (fs.existsSync(target)) {
|
|
148
|
+
matches.push({ ...entry, path: target });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return matches;
|
|
153
|
+
}
|
|
154
|
+
|
|
131
155
|
function rmRecursive(target) {
|
|
132
156
|
if (!fs.existsSync(target)) return;
|
|
133
157
|
fs.rmSync(target, { recursive: true, force: true });
|
package/lib/profiles.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const PROFILES = {
|
|
2
|
+
core: {
|
|
3
|
+
description: 'Core workflow agents only.',
|
|
4
|
+
modules: ['core'],
|
|
5
|
+
},
|
|
6
|
+
standard: {
|
|
7
|
+
description: 'Core workflow plus security and frontend QA agents.',
|
|
8
|
+
modules: ['core', 'security', 'frontend'],
|
|
9
|
+
},
|
|
10
|
+
full: {
|
|
11
|
+
description: 'All bundled dev-workflow agents.',
|
|
12
|
+
modules: ['core', 'security', 'frontend', 'typescript', 'python', 'csharp', 'rust'],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const DEFAULT_PROFILE = 'standard';
|
|
17
|
+
|
|
18
|
+
function parseCsv(value) {
|
|
19
|
+
if (!value || value === true) return [];
|
|
20
|
+
return String(value)
|
|
21
|
+
.split(',')
|
|
22
|
+
.map((item) => item.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveInstallSelection({ profile, modules }) {
|
|
27
|
+
const selectedProfile = profile || DEFAULT_PROFILE;
|
|
28
|
+
if (!PROFILES[selectedProfile]) {
|
|
29
|
+
throw new Error(`Unknown profile: ${selectedProfile}. Valid profiles: ${Object.keys(PROFILES).join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const explicitModules = parseCsv(modules);
|
|
33
|
+
const selectedModules = new Set(PROFILES[selectedProfile].modules);
|
|
34
|
+
for (const moduleName of explicitModules) {
|
|
35
|
+
selectedModules.add(moduleName);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
profile: selectedProfile,
|
|
40
|
+
modules: Array.from(selectedModules).sort(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { DEFAULT_PROFILE, PROFILES, resolveInstallSelection };
|
|
@@ -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
|
+
};
|