@brunosps00/dev-workflow 2.6.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 +72 -0
- package/README.md +7 -4
- package/lib/doctor.js +17 -2
- package/lib/init.js +16 -6
- 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 +61 -5
- 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 +4 -0
- 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 +5 -0
- package/scaffold/pt-br/templates/project-onepager.md +1 -1
- package/scaffold/scripts/hooks/session-cost.mjs +62 -21
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dev-workflow",
|
|
3
3
|
"description": "Standalone, à-la-carte skills from the dev-workflow pipeline: minimalism, search-first, simplification, verification, security review, and more.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.7.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Bruno Santos"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,78 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
10
10
|
> those versions were released, so they are summaries of what shipped, not
|
|
11
11
|
> contemporaneous release notes. `git log` remains the authoritative record.
|
|
12
12
|
|
|
13
|
+
## [2.7.0] — 2026-11-09
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Content stress in `dw-ui-discipline`.** New `references/content-stress.md`: a catalog of
|
|
18
|
+
realistic worst-case values (long and one-letter names, unbreakable emails, `1 members`,
|
|
19
|
+
1,000+ rows, RTL, emoji-first names), failure signatures with their CSS cause and fix, and
|
|
20
|
+
a per-field truncate/wrap/clamp rule. `/dw-qa` runs it in UI mode — worst-case data injected
|
|
21
|
+
at the data boundary (the project's mocks, or `page.route()`, never by editing the
|
|
22
|
+
component), checked at the real container width, 320px, 200% zoom and dark/RTL — and
|
|
23
|
+
`/dw-redesign-ui` runs it at validation. Adapted from `break-ui` in
|
|
24
|
+
[`emilkowalski/skills`](https://github.com/emilkowalski/skills) (MIT).
|
|
25
|
+
- **Mobile platform fixes** in `responsive.md`: hover gated by `(hover: hover) and
|
|
26
|
+
(pointer: fine)`, `100dvh`/`100svh`, 16px inputs instead of disabling zoom,
|
|
27
|
+
`touch-action: manipulation`, `overscroll-behavior`, `viewport-fit=cover` for safe areas,
|
|
28
|
+
`theme-color` per scheme, and the rule that none of it is verified in device emulation.
|
|
29
|
+
- **Command usage telemetry.** The `session-cost` hook records a `commands` object (reads of
|
|
30
|
+
`.dw/commands/<name>.md`, or a Skill call naming a command) beside `skills`.
|
|
31
|
+
`/dw-skill-health` reports per-command usage and classifies five command families as
|
|
32
|
+
`MERGE CANDIDATE`, `KEEP SEPARATE` or `TOO EARLY` (20 sessions or 30 days of window).
|
|
33
|
+
- `/dw-review` checks motion changes against the escalation triggers in `motion.md` and
|
|
34
|
+
data-rendering changes against the content-stress static signals.
|
|
35
|
+
- `/dw-redesign-ui` directions must diverge on a named axis (layout, density, personality,
|
|
36
|
+
motion, interaction model) and are named for it, never "Option A/B".
|
|
37
|
+
- **Eval-first in `dw-llm-eval`**, from LangChain's eval-driven development post: a four-line
|
|
38
|
+
behavior spec (when / does / must not / keeps working) and a baseline run on the current code
|
|
39
|
+
before the change, where new-behavior cases must fail; a `holdout` split (~20%) never tuned
|
|
40
|
+
against; and drift compared case by case — a case that passed and now fails blocks, whatever
|
|
41
|
+
the average says. `/dw-plan` orders the Eval Plan subtask before implementation; `/dw-qa --ai`
|
|
42
|
+
lists pass → fail flips and reports `holdout` apart from `dev`.
|
|
43
|
+
- **Eval runs are recorded** in `evals/runs/` and compared scenario by scenario; a change that
|
|
44
|
+
adds a dangerous surface adds its eval in the same change. New eval 7 covers deletion in
|
|
45
|
+
`update` and `uninstall`.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- **Bundled skills install to `.dw/skills/`** instead of `.agents/skills/`. Codex, Copilot and
|
|
50
|
+
OpenCode all scan `.agents/skills/`, so each listed the 27 bundled skills as model-invocable
|
|
51
|
+
next to the command wrappers; commands load them by path, and the tool indexes now hold only
|
|
52
|
+
the `dw-*` entry points. `update` retires a legacy copy from `.agents/skills/` only when it is
|
|
53
|
+
a dev-workflow copy — its `SKILL.md` carries the Structured Return contract and, when the
|
|
54
|
+
previous install state tracked files, is one of them — and leaves a same-named skill
|
|
55
|
+
installed from elsewhere in place, printed as `[kept]`; `uninstall` follows the same rule and
|
|
56
|
+
`doctor` warns about leftovers. Commands, agent instructions and the `/dw-update` snapshot
|
|
57
|
+
point at the new location.
|
|
58
|
+
- **`update` and `uninstall` no longer delete what they retire.** Legacy skill copies, removed
|
|
59
|
+
skills, orphan `dw-*` wrappers and removed command sources are moved to
|
|
60
|
+
`.dw/.migrated/<timestamp>/` with their relative paths, and files the team added inside a
|
|
61
|
+
retired copy are named in the output. A terminal `npx … update` takes no snapshot, so this is
|
|
62
|
+
the copy those users keep. The migration now runs after the new skills are copied, so an
|
|
63
|
+
interrupted update leaves the old copies in place.
|
|
64
|
+
- **`motion.md` rewritten.** Frequency decides before easing (keyboard-triggered and 100+/day
|
|
65
|
+
actions do not animate); `ease-in` is never used on UI, exits included (the previous rule
|
|
66
|
+
said exits ease in); no `scale(0)`, no `transition: all`; trigger-anchored
|
|
67
|
+
`transform-origin`; transitions over keyframes for rapid triggers; property cost tiers;
|
|
68
|
+
`will-change` only after a measured problem; a removal gate with its hazards. Springs are
|
|
69
|
+
allowed only for gesture-driven motion. Adapted from `emilkowalski/skills` (MIT).
|
|
70
|
+
- Reduced motion means fewer and gentler animations, not zero; the accessibility floor adds
|
|
71
|
+
item 11 (zoom is never disabled; content survives 200% zoom).
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- `/dw-update --rollback` copied the `.agents/skills` snapshot into the existing folder,
|
|
76
|
+
nesting it as `.agents/skills/agents-skills` instead of restoring it.
|
|
77
|
+
- A command wrapper under `.claude/skills/` was counted as a skill use in `costs.jsonl`.
|
|
78
|
+
- `/dw-update --rollback` merged the snapshot over the current files, so files added by the
|
|
79
|
+
newer release survived it; managed `.dw/` folders are now replaced, and the snapshot also
|
|
80
|
+
saves and restores `.dw/install-state.json`. `/dw-update` checks the installed version
|
|
81
|
+
against the CLI's `Installed version` line instead of accepting the old state file.
|
|
82
|
+
- `uninstall` removed `.codex/skills/` wholesale; it now removes only `dw-*` entries and
|
|
83
|
+
dev-workflow skill copies there.
|
|
84
|
+
|
|
13
85
|
## [2.6.0] — 2026-10-07
|
|
14
86
|
|
|
15
87
|
### Added
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ This will:
|
|
|
13
13
|
2. Create `.dw/commands/` with workflow commands
|
|
14
14
|
3. Create `.dw/templates/` with document templates (PRD, TechSpec, Tasks, ADR, etc.)
|
|
15
15
|
4. Create `.dw/rules/` (populated by `/dw-analyze-project`) + a curated `.dw/rules-library/` declarative baseline per stack (loaded lazily via `.dw/config/stack-mappings.json`)
|
|
16
|
-
5. Install bundled skills (`dw-verify`, `dw-memory`, `dw-review-rigor`, `dw-ui-discipline`, `dw-testing-discipline`, `security-review`, etc.) to `.
|
|
16
|
+
5. Install bundled skills (`dw-verify`, `dw-memory`, `dw-review-rigor`, `dw-ui-discipline`, `dw-testing-discipline`, `security-review`, etc.) to `.dw/skills/` — outside every agent's skill index; commands load them by path
|
|
17
17
|
6. Generate skill wrappers and project agents for Claude Code, Codex, Copilot, and OpenCode
|
|
18
18
|
7. Configure MCP servers (Context7 + Playwright)
|
|
19
19
|
8. Install enforcement hooks (git guardrails + a `SessionEnd` token-cost tracker → `.dw/metrics/costs.jsonl`) + a statusline (branch · active spec · minimalism mode · today's estimated spend) into `.claude/settings.json`, and the git guardrails into `.codex/hooks.json` for Codex (merge-aware — your own hooks/statusline are never overwritten; Codex runs project hooks only after you trust them)
|
|
@@ -226,6 +226,7 @@ your-project/
|
|
|
226
226
|
│ │ └── concerns.md # Risk map: hot spots, fragile integrations, bug history (Step 9 of /dw-analyze-project)
|
|
227
227
|
│ ├── constitution.md # Declarative principles (auto-installed when missing)
|
|
228
228
|
│ ├── skill-registry.json # Local skill taxonomy, triggers, owners, load policy, and context limits
|
|
229
|
+
│ ├── skills/ # Bundled skills, loaded by path from commands (not listed in any agent's skill index)
|
|
229
230
|
│ ├── references/ # Reference documentation
|
|
230
231
|
│ ├── scripts/ # Utility scripts
|
|
231
232
|
│ ├── spec/ # PRD directories — each contains tasks-validation.md
|
|
@@ -241,7 +242,7 @@ your-project/
|
|
|
241
242
|
│ ├── skills/ # Claude Code wrappers
|
|
242
243
|
│ ├── agents/ # Claude Code project agents
|
|
243
244
|
│ └── settings.json # MCP servers (Context7, Playwright)
|
|
244
|
-
├── .agents/skills/ # Codex/Copilot wrappers
|
|
245
|
+
├── .agents/skills/ # Codex/Copilot wrappers (bundled skills live in .dw/skills/)
|
|
245
246
|
├── .agents/agents/ # Codex/Copilot fallback agent definitions
|
|
246
247
|
├── .github/agents/ # GitHub Copilot custom agents
|
|
247
248
|
├── .opencode/commands/ # OpenCode wrappers
|
|
@@ -274,7 +275,7 @@ npx @brunosps00/dev-workflow subtask list
|
|
|
274
275
|
|
|
275
276
|
## Bundled Skills
|
|
276
277
|
|
|
277
|
-
Skills installed to `.
|
|
278
|
+
Skills installed to `.dw/skills/` for use by all commands. They sit outside the directories Codex, Copilot, OpenCode and Claude Code scan for model-invocable skills (`.agents/skills/`, `.claude/skills/`, `.github/skills/`, `.opencode/skills/`), so each tool's skill index lists only the `dw-*` command entry points; commands read the skills they need by path. Releases up to 2.6.0 installed them under `.agents/skills/`; `update` retires those copies once the new ones are in place, moving them to `.dw/.migrated/<timestamp>/` rather than deleting them, and leaves a same-named skill you installed from elsewhere where it is. The bundle is governed by `scaffold/skill-registry.json`, which records each skill's kind, tier, owner, trigger, expected output, load policy, context limit, invocation control (`model` vs `explicit`), and à-la-carte `exportable` flag.
|
|
278
279
|
|
|
279
280
|
Skill kinds:
|
|
280
281
|
|
|
@@ -378,6 +379,8 @@ Spec-Driven Development patterns — declarative constitution (`.dw/constitution
|
|
|
378
379
|
|
|
379
380
|
UI discipline (`dw-ui-discipline`) and testing doctrine (`dw-testing-discipline`) are original works in this repository. Earlier dev-workflow versions (≤0.13.x) drew on `pedronauck/skills` `ui-craft` and `testing-boss` as inspiration, but in v0.14.0 those skills were rewritten clean-room after a license audit confirmed that the upstream repo has no explicit LICENSE file at root — the README's MIT claim is unverified. The underlying ideas (grounding before design; behavior over mocks; mutation over coverage) are widely-documented general software engineering principles available in many sources (Beck, Fowler, Meszaros, Feathers, Google SRE Book, WCAG specifications). Browser-DevTools patterns from [`addyosmani/agent-skills`](https://github.com/addyosmani/agent-skills) (MIT) live inside `dw-testing-discipline/references/` (`security-boundary.md`, `three-workflow-patterns.md`).
|
|
380
381
|
|
|
382
|
+
The motion, mobile-web and content-stress references in `dw-ui-discipline` (`motion.md`, the "Mobile platform fixes" section of `responsive.md`, `content-stress.md`) adapt rules from [`emilkowalski/skills`](https://github.com/emilkowalski/skills) by Emil Kowalski (MIT): `emil-design-eng`, `review-animations`, `animation-performance`, `remove-animations`, `mobile-native` and `break-ui`. See [docs/skills-ecosystem-comparison.md](docs/skills-ecosystem-comparison.md), section 15.
|
|
383
|
+
|
|
381
384
|
Minimalism discipline (`dw-minimalism`), per-skill invocation control (`disable-model-invocation` on hidden/internal command wrappers), the git-guardrails enforcement hook, and à-la-carte plugin distribution were adapted from [`DietrichGebert/ponytail`](https://github.com/DietrichGebert/ponytail) (MIT — the "lazy senior" decision ladder, intensity modes, and statusline) and [`mattpocock/skills`](https://github.com/mattpocock/skills) (MIT — user-vs-model invocation and `git-guardrails-claude-code`). See [docs/skills-ecosystem-comparison.md](docs/skills-ecosystem-comparison.md) for the full gap analysis and what was and was not adopted.
|
|
382
385
|
|
|
383
386
|
Incident response (`dw-incident-response`) adapted from [`wilsto/claude-code-starter-kit/incident-response`](https://github.com/wilsto/claude-code-starter-kit) (MIT). The 5-phase workflow structure and runbook templates come from there. wilsto credits the upstream `wshobson/agents` plugin `incident-response` (v1.3.0); attribution chain preserved. Additional reading cited in the skill: Google SRE Book, Etsy Debriefing Facilitation Guide, PagerDuty Incident Response Documentation.
|
|
@@ -431,7 +434,7 @@ v1.0.0 consolidated the legacy command surface into the current `dw-*` pipeline.
|
|
|
431
434
|
On the next `npx @brunosps00/dev-workflow update`, the following happens automatically:
|
|
432
435
|
|
|
433
436
|
1. The 15 renamed commands' wrappers are detected as orphans (their names no longer exist in `lib/constants.js`) and removed from `.claude/skills/`, `.agents/skills/`, and `.opencode/commands/`.
|
|
434
|
-
2. The migrator prints each removal with the friendly "old → new" mapping (e.g., `[orphan]
|
|
437
|
+
2. The migrator prints each removal with the friendly "old → new" mapping (e.g., `[orphan] Retiring wrapper .agents/skills/dw-create-prd (replaced in v1.0.0 by 'dw-plan prd')`).
|
|
435
438
|
3. The current command wrappers are installed for the consolidated commands.
|
|
436
439
|
4. The bundled skills are refreshed.
|
|
437
440
|
|
package/lib/doctor.js
CHANGED
|
@@ -9,7 +9,14 @@ const {
|
|
|
9
9
|
AGENT_TARGETS,
|
|
10
10
|
} = require('./agents');
|
|
11
11
|
const { readInstallState, STATE_RELATIVE_PATH } = require('./install-state');
|
|
12
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
validateSkillRegistry,
|
|
14
|
+
readSkillRegistry,
|
|
15
|
+
validateSkillStructuredReturn,
|
|
16
|
+
BUNDLED_SKILLS_REL,
|
|
17
|
+
LEGACY_BUNDLED_SKILLS_REL,
|
|
18
|
+
} = require('./skill-registry');
|
|
19
|
+
const { isDevWorkflowSkillCopy } = require('./migrate-skills');
|
|
13
20
|
const { GIT_GUARDRAILS_SCRIPT, GIT_GUARDRAILS_CMD, STATUSLINE_CMD, HOOKS_DIR_MARKER, CODEX_HOOKS_FILE } = require('./hooks');
|
|
14
21
|
const removedCommands = require('./removed-commands');
|
|
15
22
|
|
|
@@ -177,12 +184,20 @@ function checkSkillRegistry(projectRoot, issues, warnings) {
|
|
|
177
184
|
const registry = readSkillRegistry();
|
|
178
185
|
for (const entry of registry.skills) {
|
|
179
186
|
if (!entry.bundled) continue;
|
|
180
|
-
const skillPath = path.join(projectRoot,
|
|
187
|
+
const skillPath = path.join(projectRoot, BUNDLED_SKILLS_REL, entry.name, 'SKILL.md');
|
|
181
188
|
if (!fs.existsSync(skillPath)) {
|
|
182
189
|
issues.push(`Missing bundled skill: ${path.relative(projectRoot, skillPath)}`);
|
|
183
190
|
} else {
|
|
184
191
|
validateSkillStructuredReturn(entry.name, skillPath, issues, `Installed bundled skill ${entry.name}`);
|
|
185
192
|
}
|
|
193
|
+
// A copy left under .agents/skills/ by a release up to 2.6.0 is still listed
|
|
194
|
+
// as model-invocable by Codex, Copilot and OpenCode. `update` removes it.
|
|
195
|
+
const legacySkillPath = path.join(projectRoot, LEGACY_BUNDLED_SKILLS_REL, entry.name, 'SKILL.md');
|
|
196
|
+
if (isDevWorkflowSkillCopy(path.dirname(legacySkillPath))) {
|
|
197
|
+
warnings.push(
|
|
198
|
+
`Legacy bundled skill copy at ${path.relative(projectRoot, path.dirname(legacySkillPath))} is still in agent skill indexes; run update to move it to ${BUNDLED_SKILLS_REL}/`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
186
201
|
}
|
|
187
202
|
}
|
|
188
203
|
|
package/lib/init.js
CHANGED
|
@@ -12,6 +12,7 @@ const { hasExistingInstall, readInstallState, writeInstallState, listManagedFile
|
|
|
12
12
|
const { resolveInstallSelection } = require('./profiles');
|
|
13
13
|
const { ensureSubtaskLayout } = require('./subtasks');
|
|
14
14
|
const { printPostUpdateActions } = require('./post-update');
|
|
15
|
+
const { BUNDLED_SKILLS_REL } = require('./skill-registry');
|
|
15
16
|
|
|
16
17
|
const SCAFFOLD_DIR = path.join(__dirname, '..', 'scaffold');
|
|
17
18
|
|
|
@@ -33,11 +34,10 @@ async function run({ force = false, lang = null, mode = 'init', profile = null,
|
|
|
33
34
|
console.log(' Existing dev-workflow install detected; reconciling managed files.\n');
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
// 0. GSD migration (idempotent; only runs if legacy GSD artifacts are detected)
|
|
37
|
-
//
|
|
37
|
+
// 0. GSD migration (idempotent; only runs if legacy GSD artifacts are detected).
|
|
38
|
+
// The skill migration runs later (5.55), once the new copies exist.
|
|
38
39
|
if (shouldReconcile) {
|
|
39
40
|
migrateGsd(projectRoot);
|
|
40
|
-
migrateSkills(projectRoot);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// 1. Select language
|
|
@@ -332,22 +332,32 @@ async function run({ force = false, lang = null, mode = 'init', profile = null,
|
|
|
332
332
|
ensureSubtaskLayout(projectRoot);
|
|
333
333
|
touchedFiles.push(path.join(projectRoot, '.dw', 'subtasks', '.gitignore'));
|
|
334
334
|
|
|
335
|
-
// 5.5. Copy bundled skills to .
|
|
335
|
+
// 5.5. Copy bundled skills to .dw/skills/ — outside every agent CLI's skill
|
|
336
|
+
// index; commands load them by path. Legacy copies under .agents/skills/ are
|
|
337
|
+
// retired by migrateSkills() (step 5.55) on update/repair/re-init.
|
|
336
338
|
const skillsSrcDir = path.join(SCAFFOLD_DIR, 'skills');
|
|
337
339
|
if (require('fs').existsSync(skillsSrcDir)) {
|
|
338
340
|
console.log(' Bundled skills:');
|
|
339
341
|
const skillsResults = copyDir(
|
|
340
342
|
skillsSrcDir,
|
|
341
|
-
path.join(projectRoot,
|
|
343
|
+
path.join(projectRoot, BUNDLED_SKILLS_REL),
|
|
342
344
|
managedForce
|
|
343
345
|
);
|
|
344
346
|
totalCreated += skillsResults.created;
|
|
345
347
|
totalSkipped += skillsResults.skipped;
|
|
346
348
|
totalOverwritten += skillsResults.overwritten;
|
|
347
|
-
touchedFiles.push(...listFilesRecursive(path.join(projectRoot,
|
|
349
|
+
touchedFiles.push(...listFilesRecursive(path.join(projectRoot, BUNDLED_SKILLS_REL)));
|
|
348
350
|
console.log(` ${skillsResults.created} created, ${skillsResults.skipped} skipped, ${skillsResults.overwritten} overwritten\n`);
|
|
349
351
|
}
|
|
350
352
|
|
|
353
|
+
// 5.55. Skill migration: removed bundled skills, legacy .agents/skills copies,
|
|
354
|
+
// orphan dw-* wrappers and removed command sources. Runs after the copy above
|
|
355
|
+
// so an interrupted update never leaves neither copy, and moves what it retires
|
|
356
|
+
// to .dw/.migrated/<ts>/ instead of deleting it (idempotent).
|
|
357
|
+
if (shouldReconcile) {
|
|
358
|
+
migrateSkills(projectRoot, { priorState });
|
|
359
|
+
}
|
|
360
|
+
|
|
351
361
|
// 5.6. Generate platform-native/fallback agents from the selected modules.
|
|
352
362
|
console.log(' Agents:');
|
|
353
363
|
const agentResults = generateAgents(projectRoot, selection.modules, managedForce);
|
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,
|