@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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +101 -0
- package/README.md +8 -5
- package/lib/doctor.js +63 -11
- package/lib/hooks.js +112 -27
- package/lib/init.js +27 -11
- package/lib/migrate-skills.js +189 -39
- package/lib/removed-bundled-skills.js +4 -2
- package/lib/skill-registry.js +12 -2
- package/lib/uninstall.js +63 -7
- package/package.json +1 -1
- package/scaffold/en/agent-instructions.md +1 -1
- package/scaffold/en/commands/dw-brainstorm.md +2 -2
- package/scaffold/en/commands/dw-bugfix.md +1 -1
- package/scaffold/en/commands/dw-commit.md +1 -1
- package/scaffold/en/commands/dw-dockerize.md +2 -2
- package/scaffold/en/commands/dw-functional-doc.md +1 -1
- package/scaffold/en/commands/dw-harness-audit.md +1 -1
- package/scaffold/en/commands/dw-intel.md +1 -1
- package/scaffold/en/commands/dw-learn.md +1 -1
- package/scaffold/en/commands/dw-new-project.md +3 -3
- package/scaffold/en/commands/dw-plan.md +2 -2
- package/scaffold/en/commands/dw-qa.md +6 -5
- package/scaffold/en/commands/dw-redesign-ui.md +3 -3
- package/scaffold/en/commands/dw-review.md +2 -2
- package/scaffold/en/commands/dw-skill-health.md +24 -1
- package/scaffold/en/commands/dw-update.md +28 -11
- package/scaffold/en/references/invariants.md +7 -2
- package/scaffold/en/templates/project-onepager.md +1 -1
- package/scaffold/pt-br/agent-instructions.md +1 -1
- package/scaffold/pt-br/commands/dw-brainstorm.md +2 -2
- package/scaffold/pt-br/commands/dw-bugfix.md +1 -1
- package/scaffold/pt-br/commands/dw-commit.md +1 -1
- package/scaffold/pt-br/commands/dw-dockerize.md +2 -2
- package/scaffold/pt-br/commands/dw-functional-doc.md +1 -1
- package/scaffold/pt-br/commands/dw-harness-audit.md +1 -1
- package/scaffold/pt-br/commands/dw-intel.md +1 -1
- package/scaffold/pt-br/commands/dw-learn.md +1 -1
- package/scaffold/pt-br/commands/dw-new-project.md +3 -3
- package/scaffold/pt-br/commands/dw-plan.md +2 -2
- package/scaffold/pt-br/commands/dw-qa.md +6 -5
- package/scaffold/pt-br/commands/dw-redesign-ui.md +3 -3
- package/scaffold/pt-br/commands/dw-review.md +2 -2
- package/scaffold/pt-br/commands/dw-skill-health.md +24 -1
- package/scaffold/pt-br/commands/dw-update.md +28 -11
- package/scaffold/pt-br/references/invariants.md +8 -2
- package/scaffold/pt-br/templates/project-onepager.md +1 -1
- package/scaffold/scripts/hooks/git-guardrails.mjs +3 -2
- package/scaffold/scripts/hooks/session-cost.mjs +62 -21
- package/scaffold/scripts/hooks/statusline.mjs +14 -11
- package/scaffold/skills/docker-compose-recipes/references/compose-composition.md +1 -1
- package/scaffold/skills/dw-llm-eval/SKILL.md +3 -1
- package/scaffold/skills/dw-llm-eval/references/integration-with-dev-workflow-commands-detail.md +2 -2
- package/scaffold/skills/dw-llm-eval/references/reference-dataset.md +57 -3
- package/scaffold/skills/dw-ui-discipline/SKILL.md +8 -5
- package/scaffold/skills/dw-ui-discipline/references/accessibility-floor.md +20 -8
- package/scaffold/skills/dw-ui-discipline/references/content-stress.md +246 -0
- package/scaffold/skills/dw-ui-discipline/references/motion.md +205 -35
- package/scaffold/skills/dw-ui-discipline/references/responsive.md +44 -2
package/lib/migrate-skills.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
14
|
-
* skills removed in past releases.
|
|
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
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
-
*
|
|
22
|
-
*
|
|
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
|
|
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 (
|
|
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}]
|
|
68
|
+
` \x1b[33m-\x1b[0m [v${entry.removedIn}] Retiring legacy skill ${path.relative(projectRoot, entry.path)} (replaced by ${entry.replacedBy})`
|
|
42
69
|
);
|
|
43
|
-
|
|
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]
|
|
57
|
-
|
|
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]
|
|
107
|
+
` \x1b[33m-\x1b[0m [legacy] Retiring command source ${rel} (replaced in v${entry.removedIn} by \`${entry.replacedBy}\`)`
|
|
64
108
|
);
|
|
65
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
79
|
-
const target = path.join(
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
93
|
-
// .agents/skills/
|
|
94
|
-
//
|
|
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
|
|
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
|
|
156
|
-
|
|
157
|
-
|
|
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 = {
|
|
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
|
|
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)
|
package/lib/skill-registry.js
CHANGED
|
@@ -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
|
-
// .
|
|
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,
|
|
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
|
-
|
|
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 .
|
|
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,
|
|
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.
|
|
129
|
-
|
|
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.
|
|
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 `./.
|
|
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 `./.
|
|
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 `./.
|
|
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 `./.
|
|
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: `.
|
|
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 `.
|
|
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 `./.
|
|
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 `.
|
|
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 `.
|
|
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 `.
|
|
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: `.
|
|
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 `.
|
|
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 .
|
|
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 `./.
|
|
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`
|