@brunosps00/dev-workflow 2.5.0 → 2.7.0

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 (59) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +101 -0
  3. package/README.md +8 -5
  4. package/lib/doctor.js +63 -11
  5. package/lib/hooks.js +112 -27
  6. package/lib/init.js +27 -11
  7. package/lib/migrate-skills.js +189 -39
  8. package/lib/removed-bundled-skills.js +4 -2
  9. package/lib/skill-registry.js +12 -2
  10. package/lib/uninstall.js +63 -7
  11. package/package.json +1 -1
  12. package/scaffold/en/agent-instructions.md +1 -1
  13. package/scaffold/en/commands/dw-brainstorm.md +2 -2
  14. package/scaffold/en/commands/dw-bugfix.md +1 -1
  15. package/scaffold/en/commands/dw-commit.md +1 -1
  16. package/scaffold/en/commands/dw-dockerize.md +2 -2
  17. package/scaffold/en/commands/dw-functional-doc.md +1 -1
  18. package/scaffold/en/commands/dw-harness-audit.md +1 -1
  19. package/scaffold/en/commands/dw-intel.md +1 -1
  20. package/scaffold/en/commands/dw-learn.md +1 -1
  21. package/scaffold/en/commands/dw-new-project.md +3 -3
  22. package/scaffold/en/commands/dw-plan.md +2 -2
  23. package/scaffold/en/commands/dw-qa.md +6 -5
  24. package/scaffold/en/commands/dw-redesign-ui.md +3 -3
  25. package/scaffold/en/commands/dw-review.md +2 -2
  26. package/scaffold/en/commands/dw-skill-health.md +24 -1
  27. package/scaffold/en/commands/dw-update.md +28 -11
  28. package/scaffold/en/references/invariants.md +7 -2
  29. package/scaffold/en/templates/project-onepager.md +1 -1
  30. package/scaffold/pt-br/agent-instructions.md +1 -1
  31. package/scaffold/pt-br/commands/dw-brainstorm.md +2 -2
  32. package/scaffold/pt-br/commands/dw-bugfix.md +1 -1
  33. package/scaffold/pt-br/commands/dw-commit.md +1 -1
  34. package/scaffold/pt-br/commands/dw-dockerize.md +2 -2
  35. package/scaffold/pt-br/commands/dw-functional-doc.md +1 -1
  36. package/scaffold/pt-br/commands/dw-harness-audit.md +1 -1
  37. package/scaffold/pt-br/commands/dw-intel.md +1 -1
  38. package/scaffold/pt-br/commands/dw-learn.md +1 -1
  39. package/scaffold/pt-br/commands/dw-new-project.md +3 -3
  40. package/scaffold/pt-br/commands/dw-plan.md +2 -2
  41. package/scaffold/pt-br/commands/dw-qa.md +6 -5
  42. package/scaffold/pt-br/commands/dw-redesign-ui.md +3 -3
  43. package/scaffold/pt-br/commands/dw-review.md +2 -2
  44. package/scaffold/pt-br/commands/dw-skill-health.md +24 -1
  45. package/scaffold/pt-br/commands/dw-update.md +28 -11
  46. package/scaffold/pt-br/references/invariants.md +8 -2
  47. package/scaffold/pt-br/templates/project-onepager.md +1 -1
  48. package/scaffold/scripts/hooks/git-guardrails.mjs +3 -2
  49. package/scaffold/scripts/hooks/session-cost.mjs +62 -21
  50. package/scaffold/scripts/hooks/statusline.mjs +14 -11
  51. package/scaffold/skills/docker-compose-recipes/references/compose-composition.md +1 -1
  52. package/scaffold/skills/dw-llm-eval/SKILL.md +3 -1
  53. package/scaffold/skills/dw-llm-eval/references/integration-with-dev-workflow-commands-detail.md +2 -2
  54. package/scaffold/skills/dw-llm-eval/references/reference-dataset.md +57 -3
  55. package/scaffold/skills/dw-ui-discipline/SKILL.md +8 -5
  56. package/scaffold/skills/dw-ui-discipline/references/accessibility-floor.md +20 -8
  57. package/scaffold/skills/dw-ui-discipline/references/content-stress.md +246 -0
  58. package/scaffold/skills/dw-ui-discipline/references/motion.md +205 -35
  59. package/scaffold/skills/dw-ui-discipline/references/responsive.md +44 -2
@@ -3,44 +3,88 @@ const path = require('path');
3
3
  const { COMMANDS } = require('./constants');
4
4
  const removedSkills = require('./removed-bundled-skills');
5
5
  const removedCommands = require('./removed-commands');
6
+ const { BUNDLED_SKILLS_REL, LEGACY_BUNDLED_SKILLS_REL } = require('./skill-registry');
7
+
8
+ const MIGRATED_REL = path.join('.dw', '.migrated');
6
9
 
7
10
  /**
8
11
  * Cleanup-on-update for dev-workflow.
9
12
  *
10
- * Two complementary scans:
13
+ * Nothing is deleted outright: every path this module retires is MOVED to
14
+ * `.dw/.migrated/<timestamp>/<original relative path>`, and the destination is
15
+ * printed. A direct `npx … update` from a terminal takes no snapshot (only the
16
+ * `/dw-update` agent command does), so this move is the only copy those users get.
17
+ *
18
+ * Scans:
11
19
  *
12
20
  * 1. Removed bundled skills (data-driven from `removed-bundled-skills.js`):
13
- * Detects `.agents/skills/<name>/` directories that match the manifest of
14
- * skills removed in past releases. Removes them and notes the replacement.
21
+ * `.dw/skills/<name>/` and legacy `.agents/skills/<name>/` directories named in
22
+ * the manifest of skills removed in past releases. Matched by name only: a
23
+ * same-named skill from elsewhere is moved too, which is why it is a move.
15
24
  *
16
- * 2. Orphan command wrappers (state-comparison vs `constants.js`):
17
- * Lists `.claude/skills/dw-*`, `.agents/skills/dw-*`, and
18
- * `.opencode/commands/dw-*.md` wrappers on disk. Any `dw-*` wrapper whose
19
- * base name is NOT present in the current `COMMANDS[lang]` is removed.
25
+ * 1.5. Relocated bundled skills: releases up to 2.6.0 installed the bundled
26
+ * skills under `.agents/skills/`, where Codex, Copilot and OpenCode list them
27
+ * as model-invocable. They now live in `.dw/skills/`. A legacy copy is retired
28
+ * only when `classifyLegacySkill` says it is ours; otherwise it is kept and
29
+ * reported. Files in an ours-copy that dev-workflow never installed are named
30
+ * in the output so the team can bring them back from the move destination.
20
31
  *
21
- * Only touches names with prefix `dw-` (or names explicitly listed in the
22
- * manifest). Never touches third-party skills/commands.
32
+ * 2. Orphan command wrappers (state-comparison vs `constants.js`):
33
+ * `.claude/skills/dw-*`, `.agents/skills/dw-*` and `.opencode/commands/dw-*.md`
34
+ * whose base name is NOT a current command or bundled skill. `dw-` is this
35
+ * package's namespace; a user skill named `dw-*` is moved, not lost.
23
36
  *
24
37
  * Idempotent: silent when nothing to clean.
25
38
  *
26
- * Run automatically from lib/init.js when mode === 'update'.
39
+ * Run from lib/init.js on update/repair/re-init, AFTER the bundled skills were
40
+ * copied to `.dw/skills/`, so an interrupted update never leaves a project with
41
+ * neither copy.
27
42
  */
28
- function migrate(projectRoot) {
43
+ function migrate(projectRoot, { priorState = null, stamp = timestamp() } = {}) {
44
+ const managed = managedSet(priorState);
29
45
  const removed = scanRemovedBundledSkills(projectRoot);
46
+ const relocated = scanRelocatedBundledSkills(projectRoot, managed);
30
47
  const orphans = scanOrphanCommandWrappers(projectRoot);
31
48
  const commandSources = scanRemovedCommandSources(projectRoot);
32
49
 
33
- if (removed.length === 0 && orphans.length === 0 && commandSources.length === 0) {
50
+ if (
51
+ removed.length === 0 &&
52
+ relocated.length === 0 &&
53
+ orphans.length === 0 &&
54
+ commandSources.length === 0
55
+ ) {
34
56
  return null; // silent — nothing to clean
35
57
  }
36
58
 
37
59
  console.log('\n Skill migration:');
60
+ let moved = 0;
61
+ const retire = (target) => {
62
+ moveToBackup(projectRoot, target, stamp);
63
+ moved += 1;
64
+ };
38
65
 
39
66
  for (const entry of removed) {
40
67
  console.log(
41
- ` \x1b[33m-\x1b[0m [v${entry.removedIn}] Removing legacy skill ${entry.name} (replaced by ${entry.replacedBy})`
68
+ ` \x1b[33m-\x1b[0m [v${entry.removedIn}] Retiring legacy skill ${path.relative(projectRoot, entry.path)} (replaced by ${entry.replacedBy})`
42
69
  );
43
- rmRecursive(entry.path);
70
+ retire(entry.path);
71
+ }
72
+
73
+ let relocatedCount = 0;
74
+ for (const entry of relocated) {
75
+ const rel = path.relative(projectRoot, entry.path);
76
+ if (entry.ours) {
77
+ console.log(` \x1b[33m-\x1b[0m [moved] Retiring ${rel} (bundled skills now live in ${BUNDLED_SKILLS_REL}/)`);
78
+ for (const file of entry.foreign) {
79
+ console.log(` ! ${path.join(rel, file)} was not installed by dev-workflow; it is in the backup below`);
80
+ }
81
+ retire(entry.path);
82
+ relocatedCount += 1;
83
+ } else {
84
+ console.log(
85
+ ` \x1b[36m!\x1b[0m [kept] ${rel} has a bundled skill's name but is not a dev-workflow copy (${entry.reason}); left in place`
86
+ );
87
+ }
44
88
  }
45
89
 
46
90
  // Pre-build a map { commandName -> { removedIn, replacedBy } } for friendly messaging.
@@ -53,58 +97,143 @@ function migrate(projectRoot) {
53
97
  const suffix = known
54
98
  ? `(replaced in v${known.removedIn} by \`${known.replacedBy}\`)`
55
99
  : `(command no longer exists)`;
56
- console.log(` \x1b[33m-\x1b[0m [orphan] Removing wrapper ${rel} ${suffix}`);
57
- rmRecursive(entry.path);
100
+ console.log(` \x1b[33m-\x1b[0m [orphan] Retiring wrapper ${rel} ${suffix}`);
101
+ retire(entry.path);
58
102
  }
59
103
 
60
104
  for (const entry of commandSources) {
61
105
  const rel = path.relative(projectRoot, entry.path);
62
106
  console.log(
63
- ` \x1b[33m-\x1b[0m [legacy] Removing command source ${rel} (replaced in v${entry.removedIn} by \`${entry.replacedBy}\`)`
107
+ ` \x1b[33m-\x1b[0m [legacy] Retiring command source ${rel} (replaced in v${entry.removedIn} by \`${entry.replacedBy}\`)`
64
108
  );
65
- rmRecursive(entry.path);
109
+ retire(entry.path);
66
110
  }
67
111
 
112
+ if (moved > 0) {
113
+ console.log(` Moved ${moved} path(s) to ${path.join(MIGRATED_REL, stamp)}/ — copy back anything you want to keep.`);
114
+ }
68
115
  console.log();
69
- return { removed: removed.length, orphans: orphans.length, commandSources: commandSources.length };
116
+ return {
117
+ removed: removed.length,
118
+ relocated: relocatedCount,
119
+ orphans: orphans.length,
120
+ commandSources: commandSources.length,
121
+ backup: moved > 0 ? path.join(projectRoot, MIGRATED_REL, stamp) : null,
122
+ };
70
123
  }
71
124
 
72
125
  function scanRemovedBundledSkills(projectRoot) {
73
126
  const matches = [];
74
- const agentsSkillsDir = path.join(projectRoot, '.agents', 'skills');
127
+ for (const rel of [BUNDLED_SKILLS_REL, LEGACY_BUNDLED_SKILLS_REL]) {
128
+ const skillsDir = path.join(projectRoot, rel);
129
+ if (!fs.existsSync(skillsDir)) continue;
130
+ for (const entry of removedSkills) {
131
+ const target = path.join(skillsDir, entry.name);
132
+ if (fs.existsSync(target)) {
133
+ matches.push({ ...entry, path: target });
134
+ }
135
+ }
136
+ }
137
+ return matches;
138
+ }
75
139
 
76
- if (!fs.existsSync(agentsSkillsDir)) return matches;
140
+ function scanRelocatedBundledSkills(projectRoot, managed) {
141
+ const matches = [];
142
+ const legacyDir = path.join(projectRoot, LEGACY_BUNDLED_SKILLS_REL);
143
+ if (!fs.existsSync(legacyDir)) return matches;
77
144
 
78
- for (const entry of removedSkills) {
79
- const target = path.join(agentsSkillsDir, entry.name);
80
- if (fs.existsSync(target)) {
81
- matches.push({ ...entry, path: target });
145
+ for (const name of listScaffoldSkillDirs()) {
146
+ const target = path.join(legacyDir, name);
147
+ let stat;
148
+ try {
149
+ stat = fs.statSync(target);
150
+ } catch {
151
+ continue;
82
152
  }
153
+ if (!stat.isDirectory()) continue;
154
+ matches.push({ name, path: target, ...classifyLegacySkill(projectRoot, name, managed) });
83
155
  }
84
156
  return matches;
85
157
  }
86
158
 
159
+ /**
160
+ * Is `.agents/skills/<name>/` the copy a release up to 2.6.0 installed?
161
+ *
162
+ * - Its SKILL.md must carry the Structured Return contract every bundled skill
163
+ * has (`**Status:**` and `**Next Step:**`). Without it: not ours.
164
+ * - When the prior install state tracked files, its SKILL.md must be one of
165
+ * them. A same-named skill installed after the last update is not, even if its
166
+ * SKILL.md happens to use the same two labels.
167
+ * - With no tracked files (installs older than install-state), the contract
168
+ * check alone decides.
169
+ *
170
+ * For an ours-copy, `foreign` lists files dev-workflow neither tracked nor ships
171
+ * (a team's addition, or a file from an older release), relative to the dir.
172
+ */
173
+ function classifyLegacySkill(projectRoot, name, managed = new Set()) {
174
+ const dir = path.join(projectRoot, LEGACY_BUNDLED_SKILLS_REL, name);
175
+ if (!isDevWorkflowSkillCopy(dir)) return { ours: false, reason: 'no Structured Return contract', foreign: [] };
176
+ const relDir = path.join(LEGACY_BUNDLED_SKILLS_REL, name);
177
+ if (managed.size > 0 && !managed.has(path.join(relDir, 'SKILL.md'))) {
178
+ return { ours: false, reason: 'not in the previous install state', foreign: [] };
179
+ }
180
+ const scaffoldDir = path.join(__dirname, '..', 'scaffold', 'skills', name);
181
+ const foreign = listFiles(dir).filter(
182
+ (file) => !managed.has(path.join(relDir, file)) && !fs.existsSync(path.join(scaffoldDir, file))
183
+ );
184
+ return { ours: true, reason: null, foreign };
185
+ }
186
+
187
+ // Every bundled SKILL.md carries the Structured Return contract (enforced by
188
+ // lib/skill-registry.js). Necessary, not sufficient: see classifyLegacySkill.
189
+ function isDevWorkflowSkillCopy(skillDir) {
190
+ let content;
191
+ try {
192
+ content = fs.readFileSync(path.join(skillDir, 'SKILL.md'), 'utf-8');
193
+ } catch {
194
+ return false;
195
+ }
196
+ return content.includes('**Status:**') && content.includes('**Next Step:**');
197
+ }
198
+
199
+ function managedSet(state) {
200
+ return new Set(state && Array.isArray(state.managed_files) ? state.managed_files : []);
201
+ }
202
+
203
+ function listFiles(dir, base = dir) {
204
+ const out = [];
205
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
206
+ const full = path.join(dir, entry.name);
207
+ if (entry.isDirectory()) out.push(...listFiles(full, base));
208
+ else out.push(path.relative(base, full));
209
+ }
210
+ return out;
211
+ }
212
+
213
+ function listScaffoldSkillDirs() {
214
+ const scaffoldSkillsDir = path.join(__dirname, '..', 'scaffold', 'skills');
215
+ if (!fs.existsSync(scaffoldSkillsDir)) return [];
216
+ return fs
217
+ .readdirSync(scaffoldSkillsDir, { withFileTypes: true })
218
+ .filter((entry) => entry.isDirectory())
219
+ .map((entry) => entry.name);
220
+ }
221
+
87
222
  function scanOrphanCommandWrappers(projectRoot) {
88
223
  const orphans = [];
89
224
 
90
225
  // Build the canonical "known names" set:
91
226
  // - Commands from constants.js (en + pt-br union) — these become wrappers.
92
- // - Bundled skills from scaffold/skills/ — these are copied verbatim into
93
- // .agents/skills/ and .claude/skills/ alongside wrappers. They're NOT
94
- // orphans even though they share the .agents/skills/ directory with
95
- // command wrappers.
227
+ // - Bundled skill names from scaffold/skills/ — they now live in .dw/skills/,
228
+ // and a legacy copy under .agents/skills/ is handled by the relocation
229
+ // scan, not reported here as an orphan wrapper.
96
230
  const canonical = new Set();
97
231
  for (const lang of Object.keys(COMMANDS)) {
98
232
  for (const cmd of COMMANDS[lang]) {
99
233
  canonical.add(cmd.name);
100
234
  }
101
235
  }
102
- const scaffoldSkillsDir = path.join(__dirname, '..', 'scaffold', 'skills');
103
- if (fs.existsSync(scaffoldSkillsDir)) {
104
- for (const entry of fs.readdirSync(scaffoldSkillsDir, { withFileTypes: true })) {
105
- if (entry.isDirectory()) canonical.add(entry.name);
106
- }
107
- }
236
+ for (const name of listScaffoldSkillDirs()) canonical.add(name);
108
237
 
109
238
  // Scan locations
110
239
  const locations = [
@@ -152,9 +281,30 @@ function scanRemovedCommandSources(projectRoot) {
152
281
  return matches;
153
282
  }
154
283
 
155
- function rmRecursive(target) {
156
- if (!fs.existsSync(target)) return;
157
- fs.rmSync(target, { recursive: true, force: true });
284
+ function timestamp() {
285
+ return new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
286
+ }
287
+
288
+ // Move a retired path under .dw/.migrated/<stamp>/, keeping its relative path.
289
+ function moveToBackup(projectRoot, target, stamp = timestamp()) {
290
+ if (!fs.existsSync(target)) return null;
291
+ const dest = path.join(projectRoot, MIGRATED_REL, stamp, path.relative(projectRoot, target));
292
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
293
+ try {
294
+ fs.renameSync(target, dest);
295
+ } catch {
296
+ fs.cpSync(target, dest, { recursive: true });
297
+ fs.rmSync(target, { recursive: true, force: true });
298
+ }
299
+ return dest;
158
300
  }
159
301
 
160
- module.exports = { migrate };
302
+ module.exports = {
303
+ migrate,
304
+ isDevWorkflowSkillCopy,
305
+ classifyLegacySkill,
306
+ managedSet,
307
+ moveToBackup,
308
+ timestamp,
309
+ MIGRATED_REL,
310
+ };
@@ -5,8 +5,10 @@
5
5
  * this manifest forever so old installs still get cleaned up when the
6
6
  * user runs `dev-workflow update`.
7
7
  *
8
- * Each entry triggers `.agents/skills/<name>/` removal during update
9
- * (see lib/migrate-skills.js).
8
+ * Each entry retires `.dw/skills/<name>/` and `.agents/skills/<name>/` during
9
+ * update by moving them to `.dw/.migrated/<timestamp>/` (see lib/migrate-skills.js).
10
+ * The match is by name only, so the move is what keeps a same-named skill from
11
+ * elsewhere recoverable.
10
12
  */
11
13
  module.exports = [
12
14
  // v0.13.0 — replaced TASTE catalog (161 palettes) with discipline (hard-gate + anti-slop + WCAG floor)
@@ -6,11 +6,19 @@ const { readJson } = require('./utils');
6
6
  const SCAFFOLD_DIR = path.join(__dirname, '..', 'scaffold');
7
7
  const REGISTRY_PATH = path.join(SCAFFOLD_DIR, 'skill-registry.json');
8
8
  const SKILLS_DIR = path.join(SCAFFOLD_DIR, 'skills');
9
+ // Where a consumer project receives the bundled skills. Deliberately outside every
10
+ // directory an agent CLI scans for model-invocable skills (`.claude/skills`,
11
+ // `.agents/skills`, `.github/skills`, `.opencode/skills`): commands load these by
12
+ // path, so listing them in each tool's skill index only crowded the choice the
13
+ // model makes there. Releases up to 2.6.0 installed them under `.agents/skills/`;
14
+ // `migrate-skills.js` moves those out on update.
15
+ const BUNDLED_SKILLS_REL = path.join('.dw', 'skills');
16
+ const LEGACY_BUNDLED_SKILLS_REL = path.join('.agents', 'skills');
9
17
 
10
18
  const VALID_KINDS = new Set(['protocol', 'domain-pack', 'recipe-pack', 'asset-pack']);
11
19
  const VALID_LOAD_POLICIES = new Set(['always-small', 'lazy-references', 'opt-in-runtime']);
12
20
  // Optional invocation control (mattpocock/skills). Bundled skills live under
13
- // .agents/skills/ (read by path from command prose), so this is documental for
21
+ // .dw/skills/ (read by path from command prose), so this is documental for
14
22
  // them and feeds the à-la-carte plugin manifest. Default when absent: 'model'.
15
23
  const VALID_INVOCATIONS = new Set(['model', 'explicit']);
16
24
  const REQUIRED_STRUCTURED_RETURN_FIELDS = [
@@ -150,7 +158,7 @@ function validateSkillStructuredReturn(skillName, skillPath, issues, label = `Sk
150
158
  }
151
159
 
152
160
  function listInstalledSkillNames(projectRoot) {
153
- const installedDir = path.join(projectRoot, '.agents', 'skills');
161
+ const installedDir = path.join(projectRoot, BUNDLED_SKILLS_REL);
154
162
  if (!fs.existsSync(installedDir)) return [];
155
163
  return fs
156
164
  .readdirSync(installedDir, { withFileTypes: true })
@@ -161,6 +169,8 @@ function listInstalledSkillNames(projectRoot) {
161
169
 
162
170
  module.exports = {
163
171
  REGISTRY_PATH,
172
+ BUNDLED_SKILLS_REL,
173
+ LEGACY_BUNDLED_SKILLS_REL,
164
174
  readSkillRegistry,
165
175
  validateSkillRegistry,
166
176
  validateSkillStructuredReturn,
package/lib/uninstall.js CHANGED
@@ -3,6 +3,9 @@ const path = require('path');
3
3
  const { COMMANDS, PLATFORMS, MCP_SERVERS } = require('./constants');
4
4
  const { AGENT_TARGETS, LEGACY_AGENT_TARGETS, readAgentRegistry } = require('./agents');
5
5
  const { removeHooks } = require('./hooks');
6
+ const { BUNDLED_SKILLS_REL, LEGACY_BUNDLED_SKILLS_REL } = require('./skill-registry');
7
+ const { classifyLegacySkill, isDevWorkflowSkillCopy, managedSet, moveToBackup, timestamp, MIGRATED_REL } = require('./migrate-skills');
8
+ const { readInstallState } = require('./install-state');
6
9
 
7
10
  function run() {
8
11
  const projectRoot = process.cwd();
@@ -36,7 +39,19 @@ function run() {
36
39
  // 1. Remove .dw/commands/, .dw/templates/, .dw/references/, .dw/scripts/
37
40
  console.log(' Managed files:');
38
41
  removeDir(path.join(projectRoot, '.dw', 'commands'), '.dw/commands/');
39
- removeDir(path.join(projectRoot, '.dw', 'templates'), '.dw/templates/');
42
+ // .dw/templates/overrides/ is the owner's (it wins over the managed templates): keep it.
43
+ const templatesDir = path.join(projectRoot, '.dw', 'templates');
44
+ if (fs.existsSync(templatesDir)) {
45
+ for (const entry of fs.readdirSync(templatesDir)) {
46
+ if (entry === 'overrides') continue;
47
+ fs.rmSync(path.join(templatesDir, entry), { recursive: true, force: true });
48
+ }
49
+ if (fs.readdirSync(templatesDir).length === 0) fs.rmdirSync(templatesDir);
50
+ console.log(` \x1b[31m-\x1b[0m .dw/templates [removed${fs.existsSync(templatesDir) ? ', overrides/ kept' : ''}]`);
51
+ removed++;
52
+ } else {
53
+ skipped++;
54
+ }
40
55
  removeDir(path.join(projectRoot, '.dw', 'references'), '.dw/references/');
41
56
  removeDir(path.join(projectRoot, '.dw', 'scripts'), '.dw/scripts/');
42
57
  removeFile(path.join(projectRoot, '.dw', 'agent-registry.json'));
@@ -57,14 +72,39 @@ function run() {
57
72
  }
58
73
  console.log();
59
74
 
60
- // 3. Remove bundled skills from .agents/skills/
75
+ // 3. Remove bundled skills from .dw/skills/ (managed by this package). A legacy
76
+ // copy left under .agents/skills/ by a release up to 2.6.0 is moved to
77
+ // .dw/.migrated/<ts>/ — only when it is ours (classifyLegacySkill); a
78
+ // same-named skill installed from elsewhere is kept and reported.
61
79
  console.log(' Bundled skills:');
62
80
  const skillsDir = path.join(__dirname, '..', 'scaffold', 'skills');
63
81
  const bundledSkills = fs.existsSync(skillsDir)
64
82
  ? fs.readdirSync(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name)
65
83
  : [];
84
+ const managed = managedSet(readInstallState(projectRoot));
85
+ const stamp = timestamp();
86
+ let movedLegacy = 0;
66
87
  for (const skill of bundledSkills) {
67
- removeDir(path.join(projectRoot, '.agents', 'skills', skill));
88
+ removeDir(path.join(projectRoot, BUNDLED_SKILLS_REL, skill));
89
+ const legacy = path.join(projectRoot, LEGACY_BUNDLED_SKILLS_REL, skill);
90
+ if (!fs.existsSync(legacy)) continue;
91
+ const verdict = classifyLegacySkill(projectRoot, skill, managed);
92
+ const rel = path.relative(projectRoot, legacy);
93
+ if (verdict.ours) {
94
+ moveToBackup(projectRoot, legacy, stamp);
95
+ console.log(` \x1b[31m-\x1b[0m ${rel} [moved to ${path.join(MIGRATED_REL, stamp)}/]`);
96
+ removed++;
97
+ movedLegacy++;
98
+ } else {
99
+ console.log(` \x1b[36m!\x1b[0m ${rel} [kept: not a dev-workflow copy (${verdict.reason})]`);
100
+ }
101
+ }
102
+ if (movedLegacy > 0) {
103
+ console.log(` Legacy copies are in ${path.join(MIGRATED_REL, stamp)}/; delete that folder when you no longer need them.`);
104
+ }
105
+ const bundledRoot = path.join(projectRoot, BUNDLED_SKILLS_REL);
106
+ if (fs.existsSync(bundledRoot) && fs.readdirSync(bundledRoot).length === 0) {
107
+ fs.rmdirSync(bundledRoot);
68
108
  }
69
109
  console.log();
70
110
 
@@ -104,8 +144,8 @@ function run() {
104
144
  }
105
145
  console.log();
106
146
 
107
- // 4.5. Remove dev-workflow hooks + statusline from .claude/settings.json
108
- // (marker-based; user-defined hooks/statusLine are left intact). The hook
147
+ // 4.5. Remove dev-workflow hooks + statusline from .claude/settings.json and
148
+ // .codex/hooks.json (marker-based; user-defined hooks/statusLine are left intact). The hook
109
149
  // scripts themselves were removed with .dw/scripts/ above.
110
150
  console.log(' Hooks & statusline:');
111
151
  try {
@@ -125,8 +165,22 @@ function run() {
125
165
  removeFile(path.join(projectRoot, '.opencode', 'package.json'));
126
166
  removeFile(path.join(projectRoot, '.dw', 'install-state.json'));
127
167
 
128
- // 6. Remove legacy .codex/skills/
129
- removeDir(path.join(projectRoot, '.codex', 'skills'));
168
+ // 6. Retire dev-workflow entries from legacy .codex/skills/ (dw-* wrappers and
169
+ // bundled-skill copies that pass the contract check) by moving them to
170
+ // .dw/.migrated/<ts>/; anything else there belongs to the user or another tool.
171
+ const codexSkills = path.join(projectRoot, '.codex', 'skills');
172
+ if (fs.existsSync(codexSkills)) {
173
+ for (const entry of fs.readdirSync(codexSkills, { withFileTypes: true })) {
174
+ const target = path.join(codexSkills, entry.name);
175
+ const isBundledCopy = bundledSkills.includes(entry.name) && isDevWorkflowSkillCopy(target);
176
+ if (entry.name.startsWith('dw-') || isBundledCopy) {
177
+ moveToBackup(projectRoot, target, stamp);
178
+ console.log(` \x1b[31m-\x1b[0m ${path.relative(projectRoot, target)} [moved to ${path.join(MIGRATED_REL, stamp)}/]`);
179
+ removed++;
180
+ }
181
+ }
182
+ if (fs.readdirSync(codexSkills).length === 0) fs.rmdirSync(codexSkills);
183
+ }
130
184
 
131
185
  // Note: .dw/rules/, .dw/spec/, .dw/intel/ are USER DATA — never removed
132
186
  console.log(`\n ${'='.repeat(40)}`);
@@ -137,6 +191,8 @@ function run() {
137
191
  console.log(' .dw/spec/ (PRDs and specs)');
138
192
  console.log(' .dw/intel/ (codebase index from /dw-intel --build)');
139
193
  console.log(' .dw/minimalism.json (dw-minimalism intensity preference)');
194
+ console.log(' .dw/templates/overrides/ (your template overrides)');
195
+ console.log(' .dw/.migrated/ (anything uninstall or update retired; delete when no longer needed)');
140
196
  console.log();
141
197
  }
142
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brunosps00/dev-workflow",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
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"
@@ -27,7 +27,7 @@ During task breakdown, propose local or cross-tool development and a model, effo
27
27
 
28
28
  Claude can delegate implementation through `/dw-codex-run`; Codex can use `/dw-claude-run`. The parent owns review, corrections, and continuation. Local agents inherit the session model unless the approved task overrides it. Delegate bounded independent work when it improves quality or time; keep tightly coupled or small work local. At most 3 workers per workstream, and never concurrent writers in the same worktree.
29
29
 
30
- Read relevant files first. Use `.dw/intel/` or a code-explorer for broad or unfamiliar flows when useful, checking index claims against current source. Load skill entrypoints only for matching tasks, and references only for the selected mode. Subagents receive a compact packet and return evidence and decisions, not full logs.
30
+ Read relevant files first. Use `.dw/intel/` or a code-explorer for broad or unfamiliar flows when useful, checking index claims against current source. Load skill entrypoints only for matching tasks, and references only for the selected mode. dev-workflow skills live in `.dw/skills/<name>/`; when a command names one, read it from there. `.agents/skills/` and `.claude/skills/` also hold the user's and other tools' skills: outside `update`, delete nothing there without listing it and asking first. Subagents receive a compact packet and return evidence and decisions, not full logs.
31
31
 
32
32
  ## Completion and boundaries
33
33
 
@@ -61,7 +61,7 @@ digraph brainstorm_decision {
61
61
 
62
62
  ## Complementary Skills
63
63
 
64
- When available in the project under `./.agents/skills/`, use these skills to enrich ideation:
64
+ When available in the project under `./.dw/skills/`, use these skills to enrich ideation:
65
65
 
66
66
  - `dw-council`: invoked by the dispatcher's **council** mode — multi-advisor stress-test of the most promising options with mandatory steel-manning and concession tracking. The dispatcher fires it when 2+ paths tie OR consensus forms too quickly (false-consensus signal). Not invoked on every brainstorm — only when signals justify it.
67
67
  - `dw-simplification`: invoked by the dispatcher's **refactor-audit** mode — applies Chesterton's Fence + complexity metrics + the new **deep-modules** reference (deletion test, locality, leverage, interface depth) to every flagged smell.
@@ -345,7 +345,7 @@ turns and it writes files. Because it is stateful and mutating, it starts **only
345
345
 
346
346
  <critical>Ask exactly ONE unresolved decision per interaction, then wait. Never dump a list of questions.</critical>
347
347
 
348
- Grill is powered by two internal skills (loaded by path from `./.agents/skills/`, never exported):
348
+ Grill is powered by two internal skills (loaded by path from `./.dw/skills/`, never exported):
349
349
  - **`dw-grilling`** — the decision-tree interview engine (dependency ordering, one decision per turn, a
350
350
  recommended answer for every question, facts discovered rather than asked, the shared-understanding gate).
351
351
  - **`dw-domain-modeling`** — the vocabulary engine (canonical terms, fuzzy/overloaded-term challenge, code
@@ -36,7 +36,7 @@
36
36
 
37
37
  ## Complementary Skills
38
38
 
39
- When available in the project at `./.agents/skills/`, use these skills as contextual support without replacing this command:
39
+ When available in the project at `./.dw/skills/`, use these skills as contextual support without replacing this command:
40
40
 
41
41
  - `dw-debug-protocol`: **ALWAYS** — runs the bug through the six-step triage (Reproduce → Localize → Reduce → Fix Root Cause → Guard → Verify End-to-End). Stop-the-line discipline; root-cause over symptom; regression test committed in the same atomic commit. For non-trivial bugs, or after a first fix fails, Step 1 must establish a red-capable feedback loop before theorizing. Non-reproducible bugs follow the instrument-first sub-protocol — no guess fixes without explicit acknowledgement.
42
42
  - `dw-verify`: **ALWAYS** — in Direct mode, invoked before committing the fix. The VERIFICATION REPORT must show the original bug symptom no longer reproduces (not just that tests pass).
@@ -11,7 +11,7 @@
11
11
 
12
12
  ## Complementary Skills
13
13
 
14
- When available in the project under `./.agents/skills/`, use these skills as operational support without replacing this command:
14
+ When available in the project under `./.dw/skills/`, use these skills as operational support without replacing this command:
15
15
 
16
16
  - `dw-git-discipline`: **ALWAYS** — enforces atomic commits (one logical intent per commit; refactor separate from feature), Conventional Commits format, lint+tests+build green BEFORE the commit, and bans bypassing pre-commit hooks (`--no-verify`) or amending pushed commits. When mixed changes are detected, splits via `git add -p`.
17
17
 
@@ -44,7 +44,7 @@ This command is **complementary** to `/dw-new-project`:
44
44
  - Audit/dockerize report: `.dw/audit/dockerize-<YYYY-MM-DD>.md` (PRD scope: `.dw/spec/prd-<slug>/dockerize.md`)
45
45
  - Generated dev artifacts: `<SCOPE>/docker-compose.dev.yml`, `<SCOPE>/Dockerfile.dev`, `<SCOPE>/.dockerignore`
46
46
  - Generated prod artifacts: `<SCOPE>/Dockerfile`, `<SCOPE>/docker-compose.prod.yml` (optional), `<SCOPE>/.dockerignore`
47
- - Recipe source: `.agents/skills/docker-compose-recipes/services/*.yml`
47
+ - Recipe source: `.dw/skills/docker-compose-recipes/services/*.yml`
48
48
 
49
49
  ## Required Behavior — Pipeline
50
50
 
@@ -163,7 +163,7 @@ Without approval: write report (Phase 3 with `status: PLANNED`) and stop.
163
163
  #### 2.1 `--dev` artifacts
164
164
 
165
165
  **`docker-compose.dev.yml`**:
166
- - Compose service blocks from `.agents/skills/docker-compose-recipes/services/*.yml` per `references/compose-composition.md`.
166
+ - Compose service blocks from `.dw/skills/docker-compose-recipes/services/*.yml` per `references/compose-composition.md`.
167
167
  - Add app service(s) at the end: `build: { context: ., dockerfile: Dockerfile.dev }`, `volumes` for hot reload (bind mount source, anonymous volume for `node_modules`/`__pycache__`), `env_file: .env`, `depends_on` with `condition: service_healthy` per `references/healthcheck-patterns.md`.
168
168
  - Preserve each recipe's image tag (do NOT convert any of them to a digest — a digest freezes the base and never picks up a security rebuild), the required credential vars, and the `127.0.0.1` binds. The loopback ports intentionally keep the `apps on host` topology working.
169
169
  - Header comment: `# Generated by /dw-dockerize on YYYY-MM-DD`.
@@ -53,7 +53,7 @@ Works best with project analyzed by `/dw-analyze-project`
53
53
 
54
54
  ## Complementary Skills
55
55
 
56
- When available in the project under `./.agents/skills/`, use these skills as operational support without replacing this command as source of truth:
56
+ When available in the project under `./.dw/skills/`, use these skills as operational support without replacing this command as source of truth:
57
57
 
58
58
  - `dw-testing-discipline`: support for structuring E2E flows (`references/playwright-recipes.md`), evidence collection patterns, and applying core rules + selector hierarchy to any test the doc references
59
59
  - `remotion-best-practices`: mandatory support when there is a final human video, captions, composition, transitions, FFmpeg, or Remotion
@@ -29,7 +29,7 @@ You are the dev-workflow harness auditor.
29
29
  5. Recommend the top 3 fixes.
30
30
 
31
31
  ## Runtime degradation triage (on-demand)
32
- The scorecard above is deterministic **install** health. If instead the *agent* is misbehaving at runtime (loops, objective drift, hallucinated edits, degraded reasoning), that is a different diagnosis: point the user to `.agents/skills/dw-debug-protocol/references/agent-degradation.md` (symptom→cause table + ordered recovery). Advisory only — it does not affect the scorecard.
32
+ The scorecard above is deterministic **install** health. If instead the *agent* is misbehaving at runtime (loops, objective drift, hallucinated edits, degraded reasoning), that is a different diagnosis: point the user to `.dw/skills/dw-debug-protocol/references/agent-degradation.md` (symptom→cause table + ordered recovery). Advisory only — it does not affect the scorecard.
33
33
 
34
34
  ## Output
35
35
  Return a scorecard and do not mutate files. Tell the user to run `dev-workflow repair` for managed-file drift.
@@ -64,7 +64,7 @@ Don't refuse to answer — return the best info available.
64
64
 
65
65
  ### 2. Query shape detection
66
66
 
67
- Classify the user's `{{QUERY}}` into one of the shapes documented in `.agents/skills/dw-codebase-intel/references/query-patterns.md`:
67
+ Classify the user's `{{QUERY}}` into one of the shapes documented in `.dw/skills/dw-codebase-intel/references/query-patterns.md`:
68
68
 
69
69
  - **where-is** — primary: `files.json`, secondary: `apis.json`
70
70
  - **what-uses** — primary: `deps.json` (libs) or `files.json` (symbols)
@@ -23,7 +23,7 @@ Gather signals only from what is already persisted:
23
23
  4. **Present** candidates in chat as a markdown list — id, trigger, action, confidence, evidence. Do NOT write yet.
24
24
  5. **Ask**: "Approve which to store? (ids, or 'all', or 'none'). Any to promote to a constitution principle?"
25
25
  6. On approval:
26
- - Write each approved instinct to `.dw/memory/instincts/<slug>.md` in the format from `.agents/skills/dw-memory/references/instincts.md` (create or update; never duplicate an existing id).
26
+ - Write each approved instinct to `.dw/memory/instincts/<slug>.md` in the format from `.dw/skills/dw-memory/references/instincts.md` (create or update; never duplicate an existing id).
27
27
  - For any the user chooses to promote, hand off to the constitution flow — propose a `P-NNN` principle at `severity: info` for `/dw-analyze-project` Step 8. Do NOT edit `.dw/constitution.md` directly without the user re-confirming.
28
28
  7. Update confidence on existing instincts that new evidence reconfirms or contradicts.
29
29
 
@@ -39,7 +39,7 @@ You are a workspace bootstrap lead for the dev-workflow ecosystem. Your job is t
39
39
  - Project one-pager: `.dw/spec/projects/{{PROJECT_NAME}}.md` (uses `.dw/templates/project-onepager.md`)
40
40
  - Final report: `.dw/spec/projects/{{PROJECT_NAME}}-bootstrap.md`
41
41
  - Seeded rules: `.dw/rules/index.md` (minimal, replaceable later by `/dw-analyze-project`)
42
- - Compose recipes: `.agents/skills/docker-compose-recipes/services/*.yml`
42
+ - Compose recipes: `.dw/skills/docker-compose-recipes/services/*.yml`
43
43
 
44
44
  ## Required Behavior — Pipeline
45
45
 
@@ -157,7 +157,7 @@ If fullstack:
157
157
 
158
158
  #### 3.3 Generate `docker-compose.dev.yml`
159
159
 
160
- 1. Read `.agents/skills/docker-compose-recipes/SKILL.md` and the relevant `services/<name>.yml` files.
160
+ 1. Read `.dw/skills/docker-compose-recipes/SKILL.md` and the relevant `services/<name>.yml` files.
161
161
  2. Apply the merge algorithm in `references/compose-composition.md`:
162
162
  - Concatenate selected service blocks under `services:`.
163
163
  - Aggregate named volumes under `volumes:`.
@@ -300,7 +300,7 @@ monorepo: <pnpm-workspaces|turborepo|nx|none>
300
300
  | ... | ... | ... |
301
301
 
302
302
  ## Services Composed
303
- <table of services with port + UI URL + credential requirements, sourced from .agents/skills/docker-compose-recipes/>
303
+ <table of services with port + UI URL + credential requirements, sourced from .dw/skills/docker-compose-recipes/>
304
304
 
305
305
  ## Next Steps
306
306
  1. `cp .env.example .env`, generate unique local secrets, and set every required value such as `POSTGRES_PASSWORD`.
@@ -30,7 +30,7 @@ You are a planning orchestrator that takes an idea through the full PRD → Tech
30
30
 
31
31
  ## Complementary Skills
32
32
 
33
- When available under `./.agents/skills/`, use these as planning support:
33
+ When available under `./.dw/skills/`, use these as planning support:
34
34
 
35
35
  - `dw-source-grounding`: **ALWAYS** in TechSpec stage — every framework/library decision must cite official docs with version + retrieval date.
36
36
  - `dw-ui-discipline`: **REQUIRED** when the PRD has UI surfaces — runs the 4 grounding questions before any visual design lands in the TechSpec.
@@ -169,7 +169,7 @@ Runs when default mode (after TechSpec approval) OR `plan tasks`.
169
169
  3. **End-to-end coverage:** every user-facing flow has backend + frontend + functional UI subtasks if applicable.
170
170
  4. **Test placement (`dw-testing-discipline`):** every test-adding subtask names its invariant per the placement doctrine. Owning layer specified.
171
171
  5. **Constitution alignment:** every task lists `Constitution: respects P-NNN` or `Constitution: deviates P-NNN — ADR planned: <slug>` or `Constitution: n/a — reason: <one-liner>`.
172
- 6. **LLM-eval subtask (when applicable):** if the PRD has an AI feature, one task must include an Eval Plan subtask (reference dataset path, oracle rungs, judge calibration, target metrics).
172
+ 6. **LLM-eval subtask (when applicable):** if the PRD has an AI feature, one task must include an Eval Plan subtask (reference dataset path, oracle rungs, judge calibration, target metrics), ordered before the implementation subtasks: behavior spec (when / does / must not / keeps working), cases, and a baseline run on the current code where the new-behavior cases fail.
173
173
  7. **Dependency declaration:** each task explicitly lists which previous tasks it depends on. Write the field as `Depends on: none` or `Depends on: 1.0, 2.0` in `tasks.md` and each `<N>_task.md`. Validation rejects cycles.
174
174
  8. **Output locations:**
175
175
  - Summary: `.dw/spec/prd-<feature-slug>/tasks.md`