@brunosps00/dev-workflow 1.3.0 → 1.4.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/marketplace.json +13 -0
- package/.claude-plugin/plugin.json +31 -0
- package/README.md +21 -2
- package/lib/build-plugin.js +79 -0
- package/lib/constants.js +17 -14
- package/lib/doctor.js +33 -0
- package/lib/hooks.js +137 -0
- package/lib/init.js +20 -0
- package/lib/skill-registry.js +13 -0
- package/lib/uninstall.js +22 -3
- package/lib/validate.js +62 -0
- package/lib/wrappers.js +1 -1
- package/package.json +6 -1
- package/scaffold/en/commands/dw-plan.md +1 -0
- package/scaffold/en/commands/dw-review.md +1 -0
- package/scaffold/en/commands/dw-run.md +1 -0
- package/scaffold/en/commands/dw-update.md +4 -1
- package/scaffold/pt-br/commands/dw-plan.md +1 -0
- package/scaffold/pt-br/commands/dw-review.md +1 -0
- package/scaffold/pt-br/commands/dw-run.md +1 -0
- package/scaffold/pt-br/commands/dw-update.md +4 -1
- package/scaffold/scripts/hooks/git-guardrails.mjs +79 -0
- package/scaffold/scripts/hooks/statusline.mjs +87 -0
- package/scaffold/skill-registry.json +27 -0
- package/scaffold/skills/dw-minimalism/SKILL.md +98 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "brunosps00-dev-workflow",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Bruno Santos"
|
|
5
|
+
},
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"name": "dev-workflow",
|
|
9
|
+
"source": "./",
|
|
10
|
+
"description": "Standalone, à-la-carte skills from the dev-workflow pipeline: minimalism, search-first, simplification, verification, security review, and more."
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dev-workflow",
|
|
3
|
+
"description": "Standalone, à-la-carte skills from the dev-workflow pipeline: minimalism, search-first, simplification, verification, security review, and more.",
|
|
4
|
+
"version": "1.4.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Bruno Santos"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://github.com/brunosantos/dev-workflow",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"ai",
|
|
12
|
+
"workflow",
|
|
13
|
+
"skills",
|
|
14
|
+
"claude",
|
|
15
|
+
"minimalism",
|
|
16
|
+
"verification",
|
|
17
|
+
"security"
|
|
18
|
+
],
|
|
19
|
+
"skills": [
|
|
20
|
+
"./scaffold/skills/api-testing-recipes",
|
|
21
|
+
"./scaffold/skills/docker-compose-recipes",
|
|
22
|
+
"./scaffold/skills/dw-minimalism",
|
|
23
|
+
"./scaffold/skills/dw-search-first",
|
|
24
|
+
"./scaffold/skills/dw-simplification",
|
|
25
|
+
"./scaffold/skills/dw-verify",
|
|
26
|
+
"./scaffold/skills/humanizer",
|
|
27
|
+
"./scaffold/skills/remotion-best-practices",
|
|
28
|
+
"./scaffold/skills/security-review",
|
|
29
|
+
"./scaffold/skills/vercel-react-best-practices"
|
|
30
|
+
]
|
|
31
|
+
}
|
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ This will:
|
|
|
16
16
|
5. Install bundled skills (`dw-verify`, `dw-memory`, `dw-review-rigor`, `dw-ui-discipline`, `dw-testing-discipline`, `security-review`, etc.) to `.agents/skills/`
|
|
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
|
+
8. Install enforcement hooks (git guardrails) + a statusline into `.claude/settings.json` (merge-aware — your own hooks/statusline are never overwritten)
|
|
19
20
|
|
|
20
21
|
> **Compozy-inspired disciplines.** Since 0.5.0, dev-workflow bundles three primitives — `dw-verify`, `dw-memory`, `dw-review-rigor` — adapted from the [Compozy](https://github.com/compozy/compozy) project and invoked internally by existing commands. See [docs/compozy-integration.md](docs/compozy-integration.md) for what was ported and what was not.
|
|
21
22
|
|
|
@@ -24,9 +25,24 @@ Optional dependencies (Playwright browsers, react-doctor, Trivy, Semgrep, gitlea
|
|
|
24
25
|
npx @brunosps00/dev-workflow install-deps
|
|
25
26
|
```
|
|
26
27
|
|
|
28
|
+
### Install individual skills (à-la-carte)
|
|
29
|
+
|
|
30
|
+
Beyond the full pipeline, the standalone disciplines are also published as a Claude Code plugin, so you can adopt one skill without scaffolding the whole workflow:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Claude Code: add the marketplace, then install the plugin
|
|
34
|
+
/plugin marketplace add brunosps00/dev-workflow
|
|
35
|
+
/plugin install dev-workflow@brunosps00-dev-workflow
|
|
36
|
+
|
|
37
|
+
# or via the skills ecosystem
|
|
38
|
+
npx skills add brunosps00/dev-workflow
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Exportable skills (no `.dw/` pipeline required): `dw-minimalism`, `dw-search-first`, `dw-simplification`, `dw-verify`, `humanizer`, `security-review`, `vercel-react-best-practices`, `remotion-best-practices`, `api-testing-recipes`, `docker-compose-recipes`. The plugin manifest is generated from the skill registry (`npm run build:plugin`); pipeline-bound skills (`dw-execute-phase`, `dw-memory`, `dw-codebase-intel`, `dw-cli-run`) are intentionally not exported.
|
|
42
|
+
|
|
27
43
|
## Commands
|
|
28
44
|
|
|
29
|
-
dev-workflow v1.
|
|
45
|
+
dev-workflow v1.4.0 ships **36 commands** organized into four tiers. Most users only invoke Tier 1 + Tier 2.
|
|
30
46
|
|
|
31
47
|
### Tier 1 — Gateway (3)
|
|
32
48
|
|
|
@@ -249,7 +265,7 @@ npx @brunosps00/dev-workflow subtask list
|
|
|
249
265
|
|
|
250
266
|
## Bundled Skills
|
|
251
267
|
|
|
252
|
-
Skills installed to `.agents/skills/` for use by all commands. The bundle is governed by `scaffold/skill-registry.json`, which records each skill's kind, tier, owner, trigger, expected output, load policy, and
|
|
268
|
+
Skills installed to `.agents/skills/` for use by all commands. 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.
|
|
253
269
|
|
|
254
270
|
Skill kinds:
|
|
255
271
|
|
|
@@ -274,6 +290,7 @@ These are not slash commands — they are primitives other commands call to enfo
|
|
|
274
290
|
| **dw-execute-phase** | Goal-backward 6-dimension plan verification (`plan-checker`) and wave-based parallel task execution (`executor`) with atomic commit, deviation handling, and checkpoint recovery | `/dw-run`, `/dw-goal`, `/dw-autopilot` | [`get-shit-done-cc`](https://github.com/gsd-build/get-shit-done) (MIT) |
|
|
275
291
|
| **dw-source-grounding** | Detect → Fetch → Implement → Cite protocol with `[source: <url>, version: X.Y, retrieved: YYYY-MM-DD]` citations. Strict source-priority hierarchy (Tier 1 official docs > Tier 2 maintainer blogs > Tier 3 Stack Overflow as discovery only). | `/dw-plan techspec`, `/dw-secure-audit --plan`, `/dw-opportunities --research`, `/dw-brainstorm --research` | [`addyosmani/agent-skills`](https://github.com/addyosmani/agent-skills) (MIT) |
|
|
276
292
|
| **dw-simplification** | Chesterton's Fence (understand WHY before changing), behavior-preserving refactor protocol (test gate before/after), complexity metrics (cyclomatic, cognitive, depth, fan-out), Rule of 500 for large refactors | `/dw-review`, `/dw-refactor`, `/dw-brainstorm --mode=refactor-audit` | [`addyosmani/agent-skills`](https://github.com/addyosmani/agent-skills) (MIT) |
|
|
293
|
+
| **dw-minimalism** | Pre-generation YAGNI decision ladder (need it? reuse? stdlib? native? installed dep? one line?) with `lite`/`full`/`ultra` intensity. The missing rung between `dw-search-first` (dependencies) and `dw-simplification` (post-hoc cleanup). | `/dw-run`, `/dw-plan`, `/dw-review` | [`DietrichGebert/ponytail`](https://github.com/DietrichGebert/ponytail) (MIT) |
|
|
277
294
|
| **dw-debug-protocol** | Stop-the-line discipline plus six-step triage (Reproduce → Localize → Reduce → Fix Root Cause → Guard → Verify End-to-End). Error categorization matrix; instrument-first non-reproducible-bug strategy. | `/dw-bugfix`, `/dw-qa --fix` | [`addyosmani/agent-skills`](https://github.com/addyosmani/agent-skills) (MIT) |
|
|
278
295
|
| **dw-git-discipline** | Trunk-based pattern (1-3 day branches, daily rebase, feature flags), atomic commit discipline (one intent per commit; refactor separate from feature), Conventional Commits, branch hygiene | `dw-commit`, `dw-generate-pr` | [`addyosmani/agent-skills`](https://github.com/addyosmani/agent-skills) (MIT) |
|
|
279
296
|
|
|
@@ -347,6 +364,8 @@ Spec-Driven Development patterns — declarative constitution (`.dw/constitution
|
|
|
347
364
|
|
|
348
365
|
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`).
|
|
349
366
|
|
|
367
|
+
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.
|
|
368
|
+
|
|
350
369
|
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.
|
|
351
370
|
|
|
352
371
|
LLM evaluation (`dw-llm-eval`) trajectory-match modes (strict / unordered / subset / superset) and tool-argument matching strategies adapted from [`langchain-ai/agentevals`](https://github.com/langchain-ai/agentevals) (MIT). The broader oracle-ladder framing, judge-calibration discipline, and reference-dataset principle are distilled from the open evaluations literature (OpenAI evals cookbook, Anthropic evals guidance, the academic eval-of-LLM body of work) and rewritten in our voice.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const pkg = require('../package.json');
|
|
4
|
+
const { readSkillRegistry } = require('./skill-registry');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate the à-la-carte distribution manifests from the skill registry.
|
|
8
|
+
*
|
|
9
|
+
* Single source of truth: scaffold/skill-registry.json. Only skills marked
|
|
10
|
+
* `exportable: true` (standalone, no .dw/ pipeline dependency) are published.
|
|
11
|
+
* Re-run `npm run build:plugin` whenever the registry changes; CI validates
|
|
12
|
+
* that the committed manifests match.
|
|
13
|
+
*
|
|
14
|
+
* Writes:
|
|
15
|
+
* .claude-plugin/plugin.json — the dev-workflow skills plugin
|
|
16
|
+
* .claude-plugin/marketplace.json — single-plugin marketplace listing
|
|
17
|
+
*
|
|
18
|
+
* This does NOT change the full-pipeline installer (`dev-workflow init`), which
|
|
19
|
+
* scaffolds all skills + commands into a project. The plugin is the parallel
|
|
20
|
+
* "install one skill" path.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const REPO_ROOT = path.join(__dirname, '..');
|
|
24
|
+
const PLUGIN_DIR = path.join(REPO_ROOT, '.claude-plugin');
|
|
25
|
+
|
|
26
|
+
function buildManifests() {
|
|
27
|
+
const registry = readSkillRegistry();
|
|
28
|
+
const exportable = registry.skills
|
|
29
|
+
.filter((s) => s.exportable === true)
|
|
30
|
+
.map((s) => s.name)
|
|
31
|
+
.sort();
|
|
32
|
+
|
|
33
|
+
const author = typeof pkg.author === 'string' ? { name: pkg.author } : pkg.author || { name: 'Bruno Santos' };
|
|
34
|
+
|
|
35
|
+
const plugin = {
|
|
36
|
+
name: 'dev-workflow',
|
|
37
|
+
description:
|
|
38
|
+
'Standalone, à-la-carte skills from the dev-workflow pipeline: minimalism, search-first, simplification, verification, security review, and more.',
|
|
39
|
+
version: pkg.version,
|
|
40
|
+
author,
|
|
41
|
+
homepage: (pkg.repository && pkg.repository.url) || undefined,
|
|
42
|
+
license: pkg.license,
|
|
43
|
+
keywords: ['ai', 'workflow', 'skills', 'claude', 'minimalism', 'verification', 'security'],
|
|
44
|
+
skills: exportable.map((name) => `./scaffold/skills/${name}`),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const marketplace = {
|
|
48
|
+
name: 'brunosps00-dev-workflow',
|
|
49
|
+
owner: author,
|
|
50
|
+
plugins: [
|
|
51
|
+
{
|
|
52
|
+
name: 'dev-workflow',
|
|
53
|
+
source: './',
|
|
54
|
+
description: plugin.description,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return { plugin, marketplace, exportable };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writeJsonStable(filePath, obj) {
|
|
63
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
64
|
+
fs.writeFileSync(filePath, JSON.stringify(obj, null, 2) + '\n', 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function run() {
|
|
68
|
+
const { plugin, marketplace, exportable } = buildManifests();
|
|
69
|
+
writeJsonStable(path.join(PLUGIN_DIR, 'plugin.json'), plugin);
|
|
70
|
+
writeJsonStable(path.join(PLUGIN_DIR, 'marketplace.json'), marketplace);
|
|
71
|
+
console.log(`Wrote .claude-plugin/plugin.json + marketplace.json (${exportable.length} exportable skills):`);
|
|
72
|
+
for (const name of exportable) console.log(` - ${name}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { buildManifests, run };
|
|
76
|
+
|
|
77
|
+
if (require.main === module) {
|
|
78
|
+
run();
|
|
79
|
+
}
|
package/lib/constants.js
CHANGED
|
@@ -5,10 +5,10 @@ const COMMANDS = {
|
|
|
5
5
|
{ name: 'dw-autopilot', description: 'Trigger when user asks to implement, build, create, or add a feature non-trivially. First invocation plans and stops; second invocation resumes via dw-goal, then commit and PR.' },
|
|
6
6
|
{ name: 'dw-brainstorm', description: 'Refine a concrete idea against the product\'s existing features. Modes: ideation, --research, --onepager, --council. Use dw-opportunities before you have an idea and dw-refactor for code-health audits.' },
|
|
7
7
|
{ name: 'dw-bugfix', description: 'Trigger when user reports a bug, pastes an error, or describes broken behavior. Triages with three questions, then fixes or routes to PRD.' },
|
|
8
|
-
{ name: 'dw-claude-run', description: 'Trigger when user asks to run/fire Claude (claude -p headless) in a dedicated worktree to implement a prepared prompt/spec. Claude adapter over the dw-cli-run protocol: durable audit log, resumable session via --session-id, 0-10 dual evaluation, STOP for the gate. Never the main checkout; never merges.' },
|
|
9
|
-
{ name: 'dw-codex-run', description: 'Trigger when user asks to run/fire Codex (codex exec) in a dedicated worktree to implement a prepared prompt/spec. Codex adapter over the dw-cli-run protocol: durable audit log, resumable per-task session, 0-10 dual evaluation, STOP for the gate. Never the main checkout; never merges.' },
|
|
8
|
+
{ name: 'dw-claude-run', userInvoked: true, description: 'Trigger when user asks to run/fire Claude (claude -p headless) in a dedicated worktree to implement a prepared prompt/spec. Claude adapter over the dw-cli-run protocol: durable audit log, resumable session via --session-id, 0-10 dual evaluation, STOP for the gate. Never the main checkout; never merges.' },
|
|
9
|
+
{ name: 'dw-codex-run', userInvoked: true, description: 'Trigger when user asks to run/fire Codex (codex exec) in a dedicated worktree to implement a prepared prompt/spec. Codex adapter over the dw-cli-run protocol: durable audit log, resumable per-task session, 0-10 dual evaluation, STOP for the gate. Never the main checkout; never merges.' },
|
|
10
10
|
{ name: 'dw-commit', description: 'Trigger when implementation is complete and pending changes need to be committed. Atomic commits with Conventional Commits messages.' },
|
|
11
|
-
{ name: 'dw-copilot-run', description: 'Trigger when user asks to run/fire GitHub Copilot CLI (copilot -p) in a dedicated worktree to implement a prepared prompt/spec. Copilot adapter over the dw-cli-run protocol: durable audit log, resumable session, 0-10 dual evaluation, STOP for the gate. Never the main checkout; never merges.' },
|
|
11
|
+
{ name: 'dw-copilot-run', userInvoked: true, description: 'Trigger when user asks to run/fire GitHub Copilot CLI (copilot -p) in a dedicated worktree to implement a prepared prompt/spec. Copilot adapter over the dw-cli-run protocol: durable audit log, resumable session, 0-10 dual evaluation, STOP for the gate. Never the main checkout; never merges.' },
|
|
12
12
|
{ name: 'dw-dockerize', description: 'Reads a project, detects the stack and runtime deps, then proposes Dockerfile and docker-compose for dev and prod with explicit trade-offs.' },
|
|
13
13
|
{ name: 'dw-find-skills', description: 'Searches the npx skills ecosystem for a skill matching what you need, vets it, and installs it where you choose.' },
|
|
14
14
|
{ name: 'dw-functional-doc', description: 'Maps screens and user flows into a functional doc, validated end-to-end with Playwright.' },
|
|
@@ -32,9 +32,9 @@ const COMMANDS = {
|
|
|
32
32
|
{ name: 'dw-run', description: 'Trigger when user wants to execute tasks. Default runs all pending in dependency order; \'run <task-id>\' runs one; \'run --resume\' continues an interrupted plan.' },
|
|
33
33
|
{ name: 'dw-secure-audit', description: 'Security Gate: OWASP + Semgrep SAST (diff) + gitleaks secrets + Trivy SCA/IaC + lockfile + supply-chain + outdated. Rigoroso: secrets/CRITICAL/HIGH block. Auto-invoked by /dw-review and /dw-generate-pr, explicit phase in /dw-autopilot, and runnable standalone.' },
|
|
34
34
|
{ name: 'dw-skill-health', description: 'Audits installed skills and agents for bloat, duplication, missing references, and stale or unused modules.' },
|
|
35
|
-
{ name: 'dw-subtask-start', description: 'Creates a minimal input packet for a subagent without injecting the parent transcript.' },
|
|
36
|
-
{ name: 'dw-subtask-complete', description: 'Records a structured subagent handoff with result, files, decisions, risks, verification, and blockers.' },
|
|
37
|
-
{ name: 'dw-subtask-resume', description: 'Consumes pending subagent handoffs, summarizes them for the parent, and archives the local packets.' },
|
|
35
|
+
{ name: 'dw-subtask-start', userInvoked: true, description: 'Creates a minimal input packet for a subagent without injecting the parent transcript.' },
|
|
36
|
+
{ name: 'dw-subtask-complete', userInvoked: true, description: 'Records a structured subagent handoff with result, files, decisions, risks, verification, and blockers.' },
|
|
37
|
+
{ name: 'dw-subtask-resume', userInvoked: true, description: 'Consumes pending subagent handoffs, summarizes them for the parent, and archives the local packets.' },
|
|
38
38
|
{ name: 'dw-update', description: 'Updates dev-workflow to the latest npm release in-place, with a snapshot you can roll back to.' },
|
|
39
39
|
],
|
|
40
40
|
'pt-br': [
|
|
@@ -43,10 +43,10 @@ const COMMANDS = {
|
|
|
43
43
|
{ name: 'dw-autopilot', description: 'Trigger quando usuario pede pra implementar, criar ou adicionar uma feature nao-trivial. Primeira invocacao planeja e para; segunda retoma via dw-goal, depois commit e PR.' },
|
|
44
44
|
{ name: 'dw-brainstorm', description: 'Refina uma ideia concreta contra features existentes do produto. Modos: ideacao, --research, --onepager, --council. Use dw-opportunities antes de ter uma ideia e dw-refactor para auditoria de code health.' },
|
|
45
45
|
{ name: 'dw-bugfix', description: 'Trigger quando usuario reporta bug, cola erro ou descreve comportamento quebrado. Tria com tres perguntas, depois corrige ou roteia pra PRD.' },
|
|
46
|
-
{ name: 'dw-claude-run', description: 'Trigger quando usuario pede pra rodar/disparar o Claude (claude -p headless) numa worktree dedicada pra implementar um prompt/spec preparado. Adapter Claude sobre o protocolo dw-cli-run: log de auditoria duravel, sessao resumivel via --session-id, dupla avaliacao 0-10, PARA pro gate. Nunca no checkout principal; nunca mergeia.' },
|
|
47
|
-
{ name: 'dw-codex-run', description: 'Trigger quando usuario pede pra rodar/disparar o Codex (codex exec) numa worktree dedicada pra implementar um prompt/spec preparado. Adapter Codex sobre o protocolo dw-cli-run: log de auditoria duravel, sessao resumivel por tarefa, dupla avaliacao 0-10, PARA pro gate. Nunca no checkout principal; nunca mergeia.' },
|
|
46
|
+
{ name: 'dw-claude-run', userInvoked: true, description: 'Trigger quando usuario pede pra rodar/disparar o Claude (claude -p headless) numa worktree dedicada pra implementar um prompt/spec preparado. Adapter Claude sobre o protocolo dw-cli-run: log de auditoria duravel, sessao resumivel via --session-id, dupla avaliacao 0-10, PARA pro gate. Nunca no checkout principal; nunca mergeia.' },
|
|
47
|
+
{ name: 'dw-codex-run', userInvoked: true, description: 'Trigger quando usuario pede pra rodar/disparar o Codex (codex exec) numa worktree dedicada pra implementar um prompt/spec preparado. Adapter Codex sobre o protocolo dw-cli-run: log de auditoria duravel, sessao resumivel por tarefa, dupla avaliacao 0-10, PARA pro gate. Nunca no checkout principal; nunca mergeia.' },
|
|
48
48
|
{ name: 'dw-commit', description: 'Trigger quando implementacao esta completa e ha mudancas pendentes pra commit. Commits atomicos com mensagens Conventional Commits.' },
|
|
49
|
-
{ name: 'dw-copilot-run', description: 'Trigger quando usuario pede pra rodar/disparar o GitHub Copilot CLI (copilot -p) numa worktree dedicada pra implementar um prompt/spec preparado. Adapter Copilot sobre o protocolo dw-cli-run: log de auditoria duravel, sessao resumivel, dupla avaliacao 0-10, PARA pro gate. Nunca no checkout principal; nunca mergeia.' },
|
|
49
|
+
{ name: 'dw-copilot-run', userInvoked: true, description: 'Trigger quando usuario pede pra rodar/disparar o GitHub Copilot CLI (copilot -p) numa worktree dedicada pra implementar um prompt/spec preparado. Adapter Copilot sobre o protocolo dw-cli-run: log de auditoria duravel, sessao resumivel, dupla avaliacao 0-10, PARA pro gate. Nunca no checkout principal; nunca mergeia.' },
|
|
50
50
|
{ name: 'dw-dockerize', description: 'Le um projeto, detecta stack e deps de runtime, e propoe Dockerfile e docker-compose para dev e prod com trade-offs explicitos.' },
|
|
51
51
|
{ name: 'dw-find-skills', description: 'Busca no ecossistema npx skills uma skill que resolva o que voce precisa, valida e instala onde voce escolher.' },
|
|
52
52
|
{ name: 'dw-functional-doc', description: 'Mapeia telas e fluxos em um dossie funcional, validado E2E com Playwright.' },
|
|
@@ -70,9 +70,9 @@ const COMMANDS = {
|
|
|
70
70
|
{ name: 'dw-run', description: 'Trigger quando usuario quer executar tasks. Default roda todas pendentes em ordem de dependencia; \'run <task-id>\' roda uma; \'run --resume\' continua plan interrompido.' },
|
|
71
71
|
{ name: 'dw-secure-audit', description: 'Security Gate: OWASP + Semgrep SAST (diff) + gitleaks secrets + Trivy SCA/IaC + lockfile + supply-chain + outdated. Rigoroso: secrets/CRITICAL/HIGH bloqueiam. Auto-invocado por /dw-review e /dw-generate-pr, fase explicita no /dw-autopilot, e executavel standalone.' },
|
|
72
72
|
{ name: 'dw-skill-health', description: 'Audita skills e agentes instalados por bloat, duplicacao, referencias quebradas e modulos sem uso.' },
|
|
73
|
-
{ name: 'dw-subtask-start', description: 'Cria um input packet minimo para subagente sem injetar transcript bruto do parent.' },
|
|
74
|
-
{ name: 'dw-subtask-complete', description: 'Registra handoff estruturado do subagente com resultado, arquivos, decisoes, riscos, verificacao e bloqueios.' },
|
|
75
|
-
{ name: 'dw-subtask-resume', description: 'Consome handoffs pendentes de subagentes, resume para o parent e arquiva os pacotes locais.' },
|
|
73
|
+
{ name: 'dw-subtask-start', userInvoked: true, description: 'Cria um input packet minimo para subagente sem injetar transcript bruto do parent.' },
|
|
74
|
+
{ name: 'dw-subtask-complete', userInvoked: true, description: 'Registra handoff estruturado do subagente com resultado, arquivos, decisoes, riscos, verificacao e bloqueios.' },
|
|
75
|
+
{ name: 'dw-subtask-resume', userInvoked: true, description: 'Consome handoffs pendentes de subagentes, resume para o parent e arquiva os pacotes locais.' },
|
|
76
76
|
{ name: 'dw-update', description: 'Atualiza o dev-workflow para o release mais recente no npm, com snapshot para rollback.' },
|
|
77
77
|
],
|
|
78
78
|
};
|
|
@@ -80,9 +80,12 @@ const COMMANDS = {
|
|
|
80
80
|
const PLATFORMS = {
|
|
81
81
|
claude: {
|
|
82
82
|
dir: '.claude/skills',
|
|
83
|
-
|
|
83
|
+
// opts.userInvoked → emit disable-model-invocation: true so Claude Code never
|
|
84
|
+
// auto-fires hidden/internal commands (e.g. the worktree runners); the user
|
|
85
|
+
// still invokes them explicitly with /<name>.
|
|
86
|
+
wrapperTemplate: (name, description, opts = {}) => `---
|
|
84
87
|
name: ${name}
|
|
85
|
-
description: "${description.replace(/"/g, '\\"')}"
|
|
88
|
+
description: "${description.replace(/"/g, '\\"')}"${opts.userInvoked ? '\ndisable-model-invocation: true' : ''}
|
|
86
89
|
---
|
|
87
90
|
|
|
88
91
|
Read and follow ALL instructions in \`.dw/commands/${name}.md\`.
|
package/lib/doctor.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
} = require('./agents');
|
|
11
11
|
const { readInstallState, STATE_RELATIVE_PATH } = require('./install-state');
|
|
12
12
|
const { validateSkillRegistry, readSkillRegistry, validateSkillStructuredReturn } = require('./skill-registry');
|
|
13
|
+
const { GIT_GUARDRAILS_CMD, STATUSLINE_CMD, HOOKS_DIR_MARKER } = require('./hooks');
|
|
13
14
|
const removedCommands = require('./removed-commands');
|
|
14
15
|
|
|
15
16
|
function run() {
|
|
@@ -33,6 +34,7 @@ function run() {
|
|
|
33
34
|
checkSubtaskLayout(projectRoot, issues);
|
|
34
35
|
checkSkillRegistry(projectRoot, issues, warnings);
|
|
35
36
|
checkMcp(projectRoot, issues);
|
|
37
|
+
checkHooks(projectRoot, issues, warnings);
|
|
36
38
|
checkLegacyCommands(projectRoot, warnings);
|
|
37
39
|
|
|
38
40
|
console.log('dev-workflow doctor\n');
|
|
@@ -203,6 +205,37 @@ function checkMcp(projectRoot, issues) {
|
|
|
203
205
|
}
|
|
204
206
|
}
|
|
205
207
|
|
|
208
|
+
function checkHooks(projectRoot, issues, warnings) {
|
|
209
|
+
// Hook scripts (copied from scaffold/scripts/ into .dw/scripts/).
|
|
210
|
+
for (const script of ['git-guardrails.mjs', 'statusline.mjs']) {
|
|
211
|
+
const file = path.join(projectRoot, '.dw', 'scripts', 'hooks', script);
|
|
212
|
+
if (!fs.existsSync(file)) issues.push(`Missing hook script: ${path.relative(projectRoot, file)}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
|
|
216
|
+
if (!fs.existsSync(settingsPath)) return; // checkMcp already reports this
|
|
217
|
+
|
|
218
|
+
let settings;
|
|
219
|
+
try {
|
|
220
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
221
|
+
} catch {
|
|
222
|
+
return; // checkMcp already reports malformed settings
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const preToolUse = (settings.hooks && settings.hooks.PreToolUse) || [];
|
|
226
|
+
const guardrailWired = preToolUse.some(
|
|
227
|
+
(g) => g && Array.isArray(g.hooks) && g.hooks.some((h) => h && h.command === GIT_GUARDRAILS_CMD),
|
|
228
|
+
);
|
|
229
|
+
if (!guardrailWired) issues.push('Missing git-guardrails PreToolUse hook in .claude/settings.json');
|
|
230
|
+
|
|
231
|
+
const statusLine = settings.statusLine;
|
|
232
|
+
if (!statusLine) {
|
|
233
|
+
issues.push('Missing statusLine in .claude/settings.json');
|
|
234
|
+
} else if (statusLine.command !== STATUSLINE_CMD && !String(statusLine.command || '').includes(HOOKS_DIR_MARKER)) {
|
|
235
|
+
warnings.push('Custom statusLine set; dev-workflow statusline is not active (add `node .dw/scripts/hooks/statusline.mjs`).');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
206
239
|
function checkLegacyCommands(projectRoot, warnings) {
|
|
207
240
|
for (const entry of removedCommands) {
|
|
208
241
|
const file = path.join(projectRoot, '.dw', 'commands', `${entry.name}.md`);
|
package/lib/hooks.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const { readSettings, writeSettings } = require('./mcp');
|
|
2
|
+
|
|
3
|
+
// dev-workflow-owned hook/statusline scripts live under this path. The substring
|
|
4
|
+
// is the marker used to identify OUR entries in .claude/settings.json so we can
|
|
5
|
+
// add/refresh them on update without ever touching the user's own hooks.
|
|
6
|
+
const HOOKS_DIR_MARKER = '.dw/scripts/hooks/';
|
|
7
|
+
const GIT_GUARDRAILS_CMD = 'node .dw/scripts/hooks/git-guardrails.mjs';
|
|
8
|
+
const STATUSLINE_CMD = 'node .dw/scripts/hooks/statusline.mjs';
|
|
9
|
+
const GIT_GUARDRAILS_SCRIPT = 'git-guardrails.mjs';
|
|
10
|
+
|
|
11
|
+
function isOurs(command) {
|
|
12
|
+
return typeof command === 'string' && command.includes(HOOKS_DIR_MARKER);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Reconcile dev-workflow hooks + statusLine into .claude/settings.json.
|
|
17
|
+
*
|
|
18
|
+
* Marker-based and merge-aware (unlike installMCPs' add-if-missing):
|
|
19
|
+
* - adds our entries when absent;
|
|
20
|
+
* - refreshes our entries in place when the script path/shape changed (so an
|
|
21
|
+
* update actually updates);
|
|
22
|
+
* - never removes or rewrites hooks/statusLine the user added;
|
|
23
|
+
* - leaves a user-defined statusLine untouched (reports it so the caller can
|
|
24
|
+
* surface a post-update note).
|
|
25
|
+
*
|
|
26
|
+
* Returns { hooks, statusLine } where each is 'added' | 'updated' | 'unchanged'
|
|
27
|
+
* (statusLine may also be 'skipped-user').
|
|
28
|
+
*/
|
|
29
|
+
function installHooks(projectRoot) {
|
|
30
|
+
const settings = readSettings(projectRoot);
|
|
31
|
+
const before = JSON.stringify(settings);
|
|
32
|
+
|
|
33
|
+
const hooksResult = reconcilePreToolUseGitGuardrails(settings);
|
|
34
|
+
const statusLineResult = reconcileStatusLine(settings);
|
|
35
|
+
|
|
36
|
+
if (JSON.stringify(settings) !== before) {
|
|
37
|
+
writeSettings(projectRoot, settings);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { hooks: hooksResult, statusLine: statusLineResult };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function reconcilePreToolUseGitGuardrails(settings) {
|
|
44
|
+
if (!settings.hooks || typeof settings.hooks !== 'object') settings.hooks = {};
|
|
45
|
+
if (!Array.isArray(settings.hooks.PreToolUse)) settings.hooks.PreToolUse = [];
|
|
46
|
+
|
|
47
|
+
const groups = settings.hooks.PreToolUse;
|
|
48
|
+
|
|
49
|
+
// Was our guardrail already wired (anywhere, any shape)?
|
|
50
|
+
const existed = groups.some(
|
|
51
|
+
(g) =>
|
|
52
|
+
g && Array.isArray(g.hooks) && g.hooks.some((h) => h && typeof h.command === 'string' && h.command.includes(GIT_GUARDRAILS_SCRIPT)),
|
|
53
|
+
);
|
|
54
|
+
// Already wired with the exact canonical command in a Bash group?
|
|
55
|
+
const exactPresent = groups.some(
|
|
56
|
+
(g) =>
|
|
57
|
+
g && g.matcher === 'Bash' && Array.isArray(g.hooks) && g.hooks.some((h) => h && h.command === GIT_GUARDRAILS_CMD),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (exactPresent) return 'unchanged';
|
|
61
|
+
|
|
62
|
+
// Drop any stale/relocated copies of OUR guardrail, preserving user hooks.
|
|
63
|
+
for (const group of groups) {
|
|
64
|
+
if (!group || !Array.isArray(group.hooks)) continue;
|
|
65
|
+
group.hooks = group.hooks.filter(
|
|
66
|
+
(h) => !(h && typeof h.command === 'string' && h.command.includes(GIT_GUARDRAILS_SCRIPT)),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
// Prune groups we just emptied (only those left with zero hooks).
|
|
70
|
+
settings.hooks.PreToolUse = groups.filter((g) => g && Array.isArray(g.hooks) && g.hooks.length > 0);
|
|
71
|
+
|
|
72
|
+
// Re-add the canonical entry: into an existing Bash group if present, else new.
|
|
73
|
+
const bashGroup = settings.hooks.PreToolUse.find((g) => g && g.matcher === 'Bash' && Array.isArray(g.hooks));
|
|
74
|
+
if (bashGroup) {
|
|
75
|
+
bashGroup.hooks.push({ type: 'command', command: GIT_GUARDRAILS_CMD });
|
|
76
|
+
} else {
|
|
77
|
+
settings.hooks.PreToolUse.push({ matcher: 'Bash', hooks: [{ type: 'command', command: GIT_GUARDRAILS_CMD }] });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return existed ? 'updated' : 'added';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function reconcileStatusLine(settings) {
|
|
84
|
+
const desired = { type: 'command', command: STATUSLINE_CMD };
|
|
85
|
+
const current = settings.statusLine;
|
|
86
|
+
|
|
87
|
+
if (!current) {
|
|
88
|
+
settings.statusLine = desired;
|
|
89
|
+
return 'added';
|
|
90
|
+
}
|
|
91
|
+
// Only manage a statusLine we own; never clobber a user's custom one.
|
|
92
|
+
if (current.command === STATUSLINE_CMD) return 'unchanged';
|
|
93
|
+
if (isOurs(current.command)) {
|
|
94
|
+
settings.statusLine = desired; // our script moved/renamed — refresh
|
|
95
|
+
return 'updated';
|
|
96
|
+
}
|
|
97
|
+
return 'skipped-user';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Remove ONLY dev-workflow-owned hook + statusLine entries (used by uninstall).
|
|
102
|
+
* Returns the number of entries removed.
|
|
103
|
+
*/
|
|
104
|
+
function removeHooks(projectRoot) {
|
|
105
|
+
const settings = readSettings(projectRoot);
|
|
106
|
+
let removed = 0;
|
|
107
|
+
|
|
108
|
+
if (settings.hooks && Array.isArray(settings.hooks.PreToolUse)) {
|
|
109
|
+
for (const group of settings.hooks.PreToolUse) {
|
|
110
|
+
if (!group || !Array.isArray(group.hooks)) continue;
|
|
111
|
+
const kept = group.hooks.filter((h) => !(h && isOurs(h.command)));
|
|
112
|
+
removed += group.hooks.length - kept.length;
|
|
113
|
+
group.hooks = kept;
|
|
114
|
+
}
|
|
115
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
|
|
116
|
+
(g) => g && Array.isArray(g.hooks) && g.hooks.length > 0,
|
|
117
|
+
);
|
|
118
|
+
if (settings.hooks.PreToolUse.length === 0) delete settings.hooks.PreToolUse;
|
|
119
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (settings.statusLine && isOurs(settings.statusLine.command)) {
|
|
123
|
+
delete settings.statusLine;
|
|
124
|
+
removed++;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (removed > 0) writeSettings(projectRoot, settings);
|
|
128
|
+
return removed;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
installHooks,
|
|
133
|
+
removeHooks,
|
|
134
|
+
HOOKS_DIR_MARKER,
|
|
135
|
+
GIT_GUARDRAILS_CMD,
|
|
136
|
+
STATUSLINE_CMD,
|
|
137
|
+
};
|
package/lib/init.js
CHANGED
|
@@ -4,6 +4,7 @@ const { ensureDir, copyDir, writeFile, upsertDelimitedBlock, log, listFilesRecur
|
|
|
4
4
|
const { selectLanguage } = require('./prompts');
|
|
5
5
|
const { generateWrappers } = require('./wrappers');
|
|
6
6
|
const { installMCPs } = require('./mcp');
|
|
7
|
+
const { installHooks } = require('./hooks');
|
|
7
8
|
const { generateAgents } = require('./agents');
|
|
8
9
|
const { migrate: migrateGsd } = require('./migrate-gsd');
|
|
9
10
|
const { migrate: migrateSkills } = require('./migrate-skills');
|
|
@@ -211,6 +212,16 @@ async function run({ force = false, lang = null, mode = 'init', profile = null,
|
|
|
211
212
|
console.log();
|
|
212
213
|
}
|
|
213
214
|
|
|
215
|
+
// 5.15. Seed .dw/minimalism.json (init only; preserved on update). Holds the
|
|
216
|
+
// dw-minimalism intensity mode read by the skill and the statusline hook.
|
|
217
|
+
if (!isUpdate) {
|
|
218
|
+
const minimalismPath = path.join(projectRoot, '.dw', 'minimalism.json');
|
|
219
|
+
const minimalismStatus = writeFile(minimalismPath, JSON.stringify({ mode: 'full' }, null, 2) + '\n', false);
|
|
220
|
+
if (minimalismStatus === 'created') totalCreated++;
|
|
221
|
+
else totalSkipped++;
|
|
222
|
+
touchedFiles.push(minimalismPath);
|
|
223
|
+
}
|
|
224
|
+
|
|
214
225
|
// 5.2. Create .dw/bugfixes/ with .gitkeep (init only; preserved on update)
|
|
215
226
|
if (!isUpdate) {
|
|
216
227
|
const bugfixesDir = path.join(projectRoot, '.dw', 'bugfixes');
|
|
@@ -302,6 +313,15 @@ async function run({ force = false, lang = null, mode = 'init', profile = null,
|
|
|
302
313
|
const mcpResults = installMCPs(projectRoot);
|
|
303
314
|
console.log(` ${mcpResults.added} configured, ${mcpResults.skipped} already present\n`);
|
|
304
315
|
|
|
316
|
+
// 9.2. Reconcile dev-workflow hooks + statusline (marker-based, merge-aware:
|
|
317
|
+
// refreshes our entries, never touches user-defined hooks/statusLine).
|
|
318
|
+
console.log(' Hooks & statusline:');
|
|
319
|
+
const hookResults = installHooks(projectRoot);
|
|
320
|
+
console.log(` git-guardrails: ${hookResults.hooks}, statusline: ${hookResults.statusLine}\n`);
|
|
321
|
+
if (hookResults.statusLine === 'skipped-user') {
|
|
322
|
+
console.log(' Note: a custom statusLine is set; to show dev-workflow state add `node .dw/scripts/hooks/statusline.mjs`.\n');
|
|
323
|
+
}
|
|
324
|
+
|
|
305
325
|
// 9.5. Persist install state for diagnostics and repair.
|
|
306
326
|
const installState = writeInstallState(projectRoot, {
|
|
307
327
|
lang: selectedLang,
|
package/lib/skill-registry.js
CHANGED
|
@@ -9,6 +9,10 @@ const SKILLS_DIR = path.join(SCAFFOLD_DIR, 'skills');
|
|
|
9
9
|
|
|
10
10
|
const VALID_KINDS = new Set(['protocol', 'domain-pack', 'recipe-pack', 'asset-pack']);
|
|
11
11
|
const VALID_LOAD_POLICIES = new Set(['always-small', 'lazy-references', 'opt-in-runtime']);
|
|
12
|
+
// Optional invocation control (mattpocock/skills). Bundled skills live under
|
|
13
|
+
// .agents/skills/ (read by path from command prose), so this is documental for
|
|
14
|
+
// them and feeds the à-la-carte plugin manifest. Default when absent: 'model'.
|
|
15
|
+
const VALID_INVOCATIONS = new Set(['model', 'explicit']);
|
|
12
16
|
const REQUIRED_STRUCTURED_RETURN_FIELDS = [
|
|
13
17
|
'**Status:**',
|
|
14
18
|
'**Scope:**',
|
|
@@ -106,6 +110,15 @@ function validateEntry(entry, issues) {
|
|
|
106
110
|
if (typeof entry.context_limit !== 'number' || entry.context_limit <= 0) {
|
|
107
111
|
issues.push(`Skill registry entry ${entry.name} must set a positive numeric context_limit`);
|
|
108
112
|
}
|
|
113
|
+
|
|
114
|
+
// Optional fields (back-compat: absent is valid; defaults applied by consumers).
|
|
115
|
+
if (entry.invocation !== undefined && !VALID_INVOCATIONS.has(entry.invocation)) {
|
|
116
|
+
issues.push(`Skill registry entry ${entry.name} has invalid invocation: ${entry.invocation}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (entry.exportable !== undefined && typeof entry.exportable !== 'boolean') {
|
|
120
|
+
issues.push(`Skill registry entry ${entry.name} exportable must be a boolean`);
|
|
121
|
+
}
|
|
109
122
|
}
|
|
110
123
|
|
|
111
124
|
function validateSkillStructuredReturn(skillName, skillPath, issues, label = `Skill ${skillName}`) {
|
package/lib/uninstall.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
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
|
+
const { removeHooks } = require('./hooks');
|
|
5
6
|
|
|
6
7
|
function run() {
|
|
7
8
|
const projectRoot = process.cwd();
|
|
@@ -103,6 +104,23 @@ function run() {
|
|
|
103
104
|
}
|
|
104
105
|
console.log();
|
|
105
106
|
|
|
107
|
+
// 4.5. Remove dev-workflow hooks + statusline from .claude/settings.json
|
|
108
|
+
// (marker-based; user-defined hooks/statusLine are left intact). The hook
|
|
109
|
+
// scripts themselves were removed with .dw/scripts/ above.
|
|
110
|
+
console.log(' Hooks & statusline:');
|
|
111
|
+
try {
|
|
112
|
+
const removedHooks = removeHooks(projectRoot);
|
|
113
|
+
if (removedHooks > 0) {
|
|
114
|
+
console.log(` \x1b[31m-\x1b[0m ${removedHooks} dev-workflow hook/statusline entr${removedHooks === 1 ? 'y' : 'ies'} [removed]`);
|
|
115
|
+
removed += removedHooks;
|
|
116
|
+
} else {
|
|
117
|
+
skipped++;
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
console.log(' Could not parse .claude/settings.json, skipping hook cleanup');
|
|
121
|
+
}
|
|
122
|
+
console.log();
|
|
123
|
+
|
|
106
124
|
// 5. Remove .opencode/package.json
|
|
107
125
|
removeFile(path.join(projectRoot, '.opencode', 'package.json'));
|
|
108
126
|
removeFile(path.join(projectRoot, '.dw', 'install-state.json'));
|
|
@@ -115,9 +133,10 @@ function run() {
|
|
|
115
133
|
console.log(` Done! ${removed} removed, ${skipped} already absent`);
|
|
116
134
|
console.log();
|
|
117
135
|
console.log(' Preserved (user data):');
|
|
118
|
-
console.log(' .dw/rules/
|
|
119
|
-
console.log(' .dw/spec/
|
|
120
|
-
console.log(' .dw/intel/
|
|
136
|
+
console.log(' .dw/rules/ (project rules)');
|
|
137
|
+
console.log(' .dw/spec/ (PRDs and specs)');
|
|
138
|
+
console.log(' .dw/intel/ (codebase index from /dw-intel --build)');
|
|
139
|
+
console.log(' .dw/minimalism.json (dw-minimalism intensity preference)');
|
|
121
140
|
console.log();
|
|
122
141
|
}
|
|
123
142
|
|
package/lib/validate.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { validateSkillRegistry } = require('./skill-registry');
|
|
4
|
+
const { validateAgentRegistry, readAgentRegistry } = require('./agents');
|
|
5
|
+
const { buildManifests } = require('./build-plugin');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Repo quality gate (npm run validate). Validates the skill + agent registries
|
|
9
|
+
* and confirms the committed .claude-plugin/ manifests match what the registry
|
|
10
|
+
* would generate (no drift). Exits non-zero on any issue.
|
|
11
|
+
*/
|
|
12
|
+
function run() {
|
|
13
|
+
const issues = [];
|
|
14
|
+
const warnings = [];
|
|
15
|
+
|
|
16
|
+
const skill = validateSkillRegistry();
|
|
17
|
+
issues.push(...skill.issues);
|
|
18
|
+
warnings.push(...skill.warnings);
|
|
19
|
+
|
|
20
|
+
const agent = validateAgentRegistry(readAgentRegistry());
|
|
21
|
+
issues.push(...agent.issues);
|
|
22
|
+
warnings.push(...agent.warnings);
|
|
23
|
+
|
|
24
|
+
// Plugin manifest drift check.
|
|
25
|
+
const { plugin, marketplace } = buildManifests();
|
|
26
|
+
checkManifestDrift('.claude-plugin/plugin.json', plugin, issues);
|
|
27
|
+
checkManifestDrift('.claude-plugin/marketplace.json', marketplace, issues);
|
|
28
|
+
|
|
29
|
+
console.log('dev-workflow validate\n');
|
|
30
|
+
if (warnings.length) {
|
|
31
|
+
console.log('Warnings:');
|
|
32
|
+
for (const w of warnings) console.log(`- ${w}`);
|
|
33
|
+
console.log();
|
|
34
|
+
}
|
|
35
|
+
if (issues.length) {
|
|
36
|
+
console.log('Issues:');
|
|
37
|
+
for (const i of issues) console.log(`- ${i}`);
|
|
38
|
+
console.log();
|
|
39
|
+
console.log('FAIL');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
console.log('PASS: skill + agent registries valid; plugin manifests in sync.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function checkManifestDrift(relPath, expected, issues) {
|
|
46
|
+
const file = path.join(__dirname, '..', relPath);
|
|
47
|
+
if (!fs.existsSync(file)) {
|
|
48
|
+
issues.push(`Missing ${relPath}; run \`npm run build:plugin\`.`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const onDisk = fs.readFileSync(file, 'utf-8');
|
|
52
|
+
const want = JSON.stringify(expected, null, 2) + '\n';
|
|
53
|
+
if (onDisk !== want) {
|
|
54
|
+
issues.push(`${relPath} is out of sync with the registry; run \`npm run build:plugin\`.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { run };
|
|
59
|
+
|
|
60
|
+
if (require.main === module) {
|
|
61
|
+
run();
|
|
62
|
+
}
|
package/lib/wrappers.js
CHANGED
|
@@ -15,7 +15,7 @@ function generateWrappers(projectRoot, commands, force = false) {
|
|
|
15
15
|
// Directory format: .claude/skills/dw-brainstorm/SKILL.md
|
|
16
16
|
skillFile = path.join(projectRoot, platform.dir, cmd.name, 'SKILL.md');
|
|
17
17
|
}
|
|
18
|
-
const content = platform.wrapperTemplate(cmd.name, cmd.description);
|
|
18
|
+
const content = platform.wrapperTemplate(cmd.name, cmd.description, cmd);
|
|
19
19
|
const status = writeFile(skillFile, content, force);
|
|
20
20
|
results[status]++;
|
|
21
21
|
results.files.push(skillFile);
|
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brunosps00/dev-workflow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.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"
|
|
7
7
|
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"validate": "node lib/validate.js",
|
|
10
|
+
"build:plugin": "node lib/build-plugin.js"
|
|
11
|
+
},
|
|
8
12
|
"files": [
|
|
9
13
|
"bin/",
|
|
10
14
|
"lib/",
|
|
11
15
|
"scaffold/",
|
|
16
|
+
".claude-plugin/",
|
|
12
17
|
"README.md",
|
|
13
18
|
"LICENSE"
|
|
14
19
|
],
|
|
@@ -39,6 +39,7 @@ When available under `./.agents/skills/`, use these as planning support:
|
|
|
39
39
|
- `dw-council` (opt-in via `--council`): multi-advisor stress-test on the major architectural decision during TechSpec stage.
|
|
40
40
|
- `dw-codebase-intel`: consulted for API conventions, architecture patterns, naming when designing the TechSpec.
|
|
41
41
|
- `dw-search-first`: **REQUIRED** before adding a new dependency, integration, MCP server, reusable helper, or framework-level abstraction.
|
|
42
|
+
- `dw-minimalism`: **REQUIRED** before specifying new modules/services — pressure-tests scope with the YAGNI ladder so the TechSpec doesn't over-build; composes with `dw-search-first`.
|
|
42
43
|
- `dw-context-budget`: use when the plan exceeds 12 tasks, touches 30+ files, or requires loading many docs/skills.
|
|
43
44
|
|
|
44
45
|
## Agent Dispatch
|
|
@@ -47,6 +47,7 @@ When available under `./.agents/skills/`, these are invoked as analytical suppor
|
|
|
47
47
|
- `dw-secure-audit` (**Security Gate**): **ALWAYS for TS/Python/C#/Rust projects** — triggered here and the verdict is enforced. If the project's language is supported and a fresh `.dw/secure-audit/audit-summary.md` is missing OR has REJECTED status, the review verdict is **REJECTED** — no exception. The same gate is also a standalone command (`/dw-secure-audit`) and an explicit phase in `/dw-autopilot`. It now adds Semgrep SAST (diff) + gitleaks secrets on top of OWASP/Trivy/SCA.
|
|
48
48
|
- `security-review`: the OWASP diff-level skill the gate uses (injection, authz, secrets, SSRF, crypto — HIGH CONFIDENCE only).
|
|
49
49
|
- `dw-simplification`: use when the diff touches dense or twisty code — applies Chesterton's Fence, behavior-preserving refactor protocol, complexity metrics.
|
|
50
|
+
- `dw-minimalism`: use when the diff adds code that may be over-built — flags speculative generality, single-caller helpers, premature abstraction, and YAGNI violations (the pre-generation counterpart to `dw-simplification`).
|
|
50
51
|
- `dw-ui-discipline`: use when the diff touches UI — runs the 14 visual-slop patterns + accessibility floor checks. For a deterministic gate, also run `node .dw/scripts/lib/ui-slop-detect.mjs <changed-ui-paths> --fail-on error` (a wrapper over the impeccable detector); treat blocking findings as **REJECTED** and surface warnings in the report.
|
|
51
52
|
- `dw-testing-discipline`: use when the diff touches tests — applies the 25 anti-patterns catalog + 6 agent guardrails (when tests were agent-authored).
|
|
52
53
|
- `dw-llm-eval`: **REQUIRED when the diff touches AI/LLM feature code paths**. Reference dataset + ≥2 oracle rungs + judge calibration (if rung 4 used) + eval run results MUST be in the PR. Missing → REJECTED.
|
|
@@ -35,6 +35,7 @@ When available under `./.agents/skills/`, these skills are invoked per task:
|
|
|
35
35
|
- `dw-testing-discipline`: applies the placement doctrine, 6 agent guardrails, and 25 anti-patterns when adding tests during the task.
|
|
36
36
|
- `dw-ui-discipline`: when the task touches UI, the 4 grounding questions must be answered before any visual decision lands.
|
|
37
37
|
- `dw-llm-eval`: when the task touches AI feature code paths, the reference dataset + oracle ladder rules apply.
|
|
38
|
+
- `dw-minimalism`: **before writing new code** — climb the YAGNI decision ladder (need it? reuse? stdlib? native? installed dep? one line?) at the active intensity, then write the minimum. Hands new dependencies to `dw-search-first`.
|
|
38
39
|
- `vercel-react-best-practices`: when the task touches React/Next.js performance.
|
|
39
40
|
|
|
40
41
|
## Constitution Gate
|
|
@@ -26,6 +26,8 @@ SNAPSHOT_DIR=".dw/.backup/$(date -u +%Y%m%dT%H%M%SZ)"
|
|
|
26
26
|
mkdir -p "$SNAPSHOT_DIR"
|
|
27
27
|
cp -r .dw/commands .dw/templates .dw/references .dw/scripts "$SNAPSHOT_DIR/" 2>/dev/null
|
|
28
28
|
[ -d .agents/skills ] && cp -r .agents/skills "$SNAPSHOT_DIR/agents-skills" 2>/dev/null
|
|
29
|
+
# .claude/settings.json is now mutated by update (MCPs + hooks + statusLine)
|
|
30
|
+
[ -f .claude/settings.json ] && mkdir -p "$SNAPSHOT_DIR/claude" && cp .claude/settings.json "$SNAPSHOT_DIR/claude/settings.json" 2>/dev/null
|
|
29
31
|
echo "Snapshot saved to $SNAPSHOT_DIR"
|
|
30
32
|
```
|
|
31
33
|
|
|
@@ -127,7 +129,7 @@ If invoked with `--rollback`:
|
|
|
127
129
|
1. List snapshots in `.dw/.backup/`
|
|
128
130
|
2. If none exist: STOP and report "No snapshot available"
|
|
129
131
|
3. If more than one exists: ask the user which to restore (default: most recent)
|
|
130
|
-
4. Confirm with the user: "Restore snapshot `<path>`? This OVERWRITES `.dw/commands/`, `.dw/templates/`, `.dw/references/`, `.dw/scripts/`,
|
|
132
|
+
4. Confirm with the user: "Restore snapshot `<path>`? This OVERWRITES `.dw/commands/`, `.dw/templates/`, `.dw/references/`, `.dw/scripts/`, `.agents/skills/`, and `.claude/settings.json`. Proceed? [y/N]"
|
|
131
133
|
5. Only after `y`: copy back
|
|
132
134
|
|
|
133
135
|
```bash
|
|
@@ -136,6 +138,7 @@ cp -r "$SNAPSHOT_DIR/templates" .dw/
|
|
|
136
138
|
cp -r "$SNAPSHOT_DIR/references" .dw/ 2>/dev/null
|
|
137
139
|
cp -r "$SNAPSHOT_DIR/scripts" .dw/ 2>/dev/null
|
|
138
140
|
[ -d "$SNAPSHOT_DIR/agents-skills" ] && cp -r "$SNAPSHOT_DIR/agents-skills" .agents/skills 2>/dev/null
|
|
141
|
+
[ -f "$SNAPSHOT_DIR/claude/settings.json" ] && cp "$SNAPSHOT_DIR/claude/settings.json" .claude/settings.json 2>/dev/null
|
|
139
142
|
```
|
|
140
143
|
|
|
141
144
|
6. Report: snapshot restored, version likely recovered (read from `.dw/commands/dw-help.md` or metadata if present)
|
|
@@ -39,6 +39,7 @@ Quando disponíveis em `./.agents/skills/`, use como suporte:
|
|
|
39
39
|
- `dw-council` (opt-in via `--council`): stress-test multi-conselheiro sobre a decisão arquitetural principal no estágio TechSpec.
|
|
40
40
|
- `dw-codebase-intel`: consultado para convenções de API, padrões arquiteturais, naming ao desenhar TechSpec.
|
|
41
41
|
- `dw-search-first`: **OBRIGATÓRIO** antes de adicionar nova dependency, integration, MCP server, helper reutilizavel ou abstracao de framework.
|
|
42
|
+
- `dw-minimalism`: **OBRIGATÓRIO** antes de especificar novos módulos/serviços — pressiona o escopo com o ladder YAGNI pro TechSpec não over-buildar; compõe com `dw-search-first`.
|
|
42
43
|
- `dw-context-budget`: use quando o plano passar de 12 tasks, tocar 30+ arquivos, ou exigir carregar muitos docs/skills.
|
|
43
44
|
|
|
44
45
|
## Agent Dispatch
|
|
@@ -47,6 +47,7 @@ Quando disponíveis em `./.agents/skills/`, são invocadas como apoio analítico
|
|
|
47
47
|
- `dw-secure-audit` (**Security Gate**): **SEMPRE para projetos TS/Python/C#/Rust** — acionado aqui e o verdict é enforced. Se a linguagem é suportada e `.dw/secure-audit/audit-summary.md` fresco está ausente OU REPROVADO, o verdict do review é **REPROVADO** — sem exceção. O mesmo gate também é comando standalone (`/dw-secure-audit`) e fase explícita no `/dw-autopilot`. Agora soma Semgrep SAST (diff) + gitleaks secrets sobre OWASP/Trivy/SCA.
|
|
48
48
|
- `security-review`: a skill OWASP nível-diff que o gate usa (injection, authz, secrets, SSRF, crypto — só HIGH CONFIDENCE).
|
|
49
49
|
- `dw-simplification`: use quando diff toca código denso — aplica Chesterton's Fence, protocolo de refactor preservando comportamento, métricas de complexidade.
|
|
50
|
+
- `dw-minimalism`: use quando o diff adiciona código que pode estar over-built — flagra generalidade especulativa, helpers de um único caller, abstração prematura e violações de YAGNI (a contraparte pré-geração do `dw-simplification`).
|
|
50
51
|
- `dw-ui-discipline`: use quando diff toca UI — roda os 14 visual-slop patterns + accessibility floor. Para um gate determinístico, rode também `node .dw/scripts/lib/ui-slop-detect.mjs <paths-ui-alterados> --fail-on error` (wrapper sobre o detector do impeccable); trate findings bloqueantes como **REJECTED** e reporte os warnings.
|
|
51
52
|
- `dw-testing-discipline`: use quando diff toca testes — aplica catálogo de 25 anti-patterns + 6 agent guardrails (quando testes foram agent-authored).
|
|
52
53
|
- `dw-llm-eval`: **OBRIGATÓRIO quando diff toca código de feature AI/LLM**. Reference dataset + ≥2 oracle rungs + judge calibration (se rung 4 usado) + eval run results DEVEM estar no PR. Faltando → REPROVADO.
|
|
@@ -35,6 +35,7 @@ Quando disponíveis em `./.agents/skills/`, estas skills são invocadas por task
|
|
|
35
35
|
- `dw-testing-discipline`: aplica placement doctrine, 6 agent guardrails e 25 anti-patterns ao adicionar testes.
|
|
36
36
|
- `dw-ui-discipline`: quando task toca UI, as 4 grounding questions precisam ser respondidas antes de qualquer decisão visual.
|
|
37
37
|
- `dw-llm-eval`: quando task toca código de feature AI, regras de reference dataset + oracle ladder se aplicam.
|
|
38
|
+
- `dw-minimalism`: **antes de escrever código novo** — sobe o decision ladder YAGNI (precisa existir? reusar? stdlib? nativo? dep instalada? uma linha?) na intensidade ativa, depois escreve o mínimo. Passa novas dependências pro `dw-search-first`.
|
|
38
39
|
- `vercel-react-best-practices`: quando task toca performance React/Next.js.
|
|
39
40
|
|
|
40
41
|
## Constitution Gate
|
|
@@ -27,6 +27,8 @@ mkdir -p "$SNAPSHOT_DIR"
|
|
|
27
27
|
cp -r .dw/commands .dw/templates .dw/references .dw/scripts "$SNAPSHOT_DIR/" 2>/dev/null
|
|
28
28
|
# agents/skills (bundled) tambem fazem parte do update
|
|
29
29
|
[ -d .agents/skills ] && cp -r .agents/skills "$SNAPSHOT_DIR/agents-skills" 2>/dev/null
|
|
30
|
+
# .claude/settings.json agora e mutado pelo update (MCPs + hooks + statusLine)
|
|
31
|
+
[ -f .claude/settings.json ] && mkdir -p "$SNAPSHOT_DIR/claude" && cp .claude/settings.json "$SNAPSHOT_DIR/claude/settings.json" 2>/dev/null
|
|
30
32
|
echo "Snapshot salvo em $SNAPSHOT_DIR"
|
|
31
33
|
```
|
|
32
34
|
|
|
@@ -128,7 +130,7 @@ Se invocado com `--rollback`:
|
|
|
128
130
|
1. Listar snapshots em `.dw/.backup/`
|
|
129
131
|
2. Se nenhum existir: PARAR e reportar "Nenhum snapshot disponível"
|
|
130
132
|
3. Se mais de um existir: perguntar ao usuário qual restaurar (padrão: mais recente)
|
|
131
|
-
4. Confirmar com o usuário: "Restaurar snapshot `<path>`? Isso SOBRESCREVE `.dw/commands/`, `.dw/templates/`, `.dw/references/`, `.dw/scripts/` e `.
|
|
133
|
+
4. Confirmar com o usuário: "Restaurar snapshot `<path>`? Isso SOBRESCREVE `.dw/commands/`, `.dw/templates/`, `.dw/references/`, `.dw/scripts/`, `.agents/skills/` e `.claude/settings.json`. Prosseguir? [s/N]"
|
|
132
134
|
5. Somente após `s`: copiar de volta
|
|
133
135
|
|
|
134
136
|
```bash
|
|
@@ -137,6 +139,7 @@ cp -r "$SNAPSHOT_DIR/templates" .dw/
|
|
|
137
139
|
cp -r "$SNAPSHOT_DIR/references" .dw/ 2>/dev/null
|
|
138
140
|
cp -r "$SNAPSHOT_DIR/scripts" .dw/ 2>/dev/null
|
|
139
141
|
[ -d "$SNAPSHOT_DIR/agents-skills" ] && cp -r "$SNAPSHOT_DIR/agents-skills" .agents/skills 2>/dev/null
|
|
142
|
+
[ -f "$SNAPSHOT_DIR/claude/settings.json" ] && cp "$SNAPSHOT_DIR/claude/settings.json" .claude/settings.json 2>/dev/null
|
|
140
143
|
```
|
|
141
144
|
|
|
142
145
|
6. Reportar: snapshot restaurado, versão provavelmente recuperada (ler de `.dw/commands/dw-help.md` ou metadata se houver)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dev-workflow git guardrails — Claude Code PreToolUse hook (matcher: Bash).
|
|
4
|
+
*
|
|
5
|
+
* Blocks irreversible git commands (force push, hard reset, clean -f, branch
|
|
6
|
+
* deletion, remote branch deletion). Everything else is allowed.
|
|
7
|
+
*
|
|
8
|
+
* Enforces the destructive-command rules from the `dw-git-discipline` skill at
|
|
9
|
+
* the harness level instead of relying on prose. Inspired by mattpocock/skills
|
|
10
|
+
* `git-guardrails-claude-code` (MIT).
|
|
11
|
+
*
|
|
12
|
+
* Contract: reads the PreToolUse payload as JSON on stdin, emits a
|
|
13
|
+
* permissionDecision via hookSpecificOutput. Fails OPEN — any parsing/runtime
|
|
14
|
+
* error allows the command, so a hook bug never blocks the user.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const DANGEROUS = [
|
|
18
|
+
{ re: /\bgit\s+push\b[^\n|&;]*(--force\b|--force-with-lease\b|\s-f\b)/, why: 'force push rewrites remote history' },
|
|
19
|
+
{ re: /\bgit\s+push\b[^\n|&;]*(--delete\b|\s-d\b|\s:\S)/, why: 'deletes a remote branch' },
|
|
20
|
+
{ re: /\bgit\s+reset\b[^\n|&;]*--hard\b/, why: 'hard reset discards uncommitted work and rewrites the branch' },
|
|
21
|
+
{ re: /\bgit\s+clean\b[^\n|&;]*-[a-z]*f/, why: 'git clean -f permanently deletes untracked files' },
|
|
22
|
+
{ re: /\bgit\s+branch\b[^\n|&;]*\s-D\b/, why: 'force-deletes a branch that may be unmerged' },
|
|
23
|
+
{ re: /\bgit\s+checkout\b[^\n|&;]*\s(--\s+\.|\.\s*$)/, why: 'discards all local changes in the working tree' },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function readStdin() {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
let data = '';
|
|
29
|
+
if (process.stdin.isTTY) return resolve('');
|
|
30
|
+
process.stdin.setEncoding('utf-8');
|
|
31
|
+
process.stdin.on('data', (chunk) => (data += chunk));
|
|
32
|
+
process.stdin.on('end', () => resolve(data));
|
|
33
|
+
process.stdin.on('error', () => resolve(''));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function allow() {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function deny(reason) {
|
|
42
|
+
process.stdout.write(
|
|
43
|
+
JSON.stringify({
|
|
44
|
+
hookSpecificOutput: {
|
|
45
|
+
hookEventName: 'PreToolUse',
|
|
46
|
+
permissionDecision: 'deny',
|
|
47
|
+
permissionDecisionReason: reason,
|
|
48
|
+
},
|
|
49
|
+
}) + '\n',
|
|
50
|
+
);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function main() {
|
|
55
|
+
let payload;
|
|
56
|
+
try {
|
|
57
|
+
payload = JSON.parse(await readStdin());
|
|
58
|
+
} catch {
|
|
59
|
+
return allow(); // fail open
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const command = payload && payload.tool_input && typeof payload.tool_input.command === 'string'
|
|
63
|
+
? payload.tool_input.command
|
|
64
|
+
: '';
|
|
65
|
+
if (!command) return allow();
|
|
66
|
+
|
|
67
|
+
for (const rule of DANGEROUS) {
|
|
68
|
+
if (rule.re.test(command)) {
|
|
69
|
+
return deny(
|
|
70
|
+
`Blocked by dev-workflow git guardrails: ${rule.why}. ` +
|
|
71
|
+
`If this is intentional, run it yourself outside the agent, or adjust .claude/settings.json hooks.`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return allow();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main().catch(() => allow());
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dev-workflow statusline — Claude Code statusLine command.
|
|
4
|
+
*
|
|
5
|
+
* Prints one line: git branch · dev-workflow active spec (if any) · minimalism
|
|
6
|
+
* mode. Reads the session payload (JSON on stdin) for the working dir; falls
|
|
7
|
+
* back to process.cwd(). Fails SAFE — any error prints a minimal line so the
|
|
8
|
+
* statusline never breaks the session.
|
|
9
|
+
*
|
|
10
|
+
* Minimalism mode is read from .dw/minimalism.json (see the dw-minimalism skill).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
|
|
17
|
+
function readStdin() {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
let data = '';
|
|
20
|
+
if (process.stdin.isTTY) return resolve('');
|
|
21
|
+
process.stdin.setEncoding('utf-8');
|
|
22
|
+
process.stdin.on('data', (c) => (data += c));
|
|
23
|
+
process.stdin.on('end', () => resolve(data));
|
|
24
|
+
process.stdin.on('error', () => resolve(''));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function gitBranch(cwd) {
|
|
29
|
+
try {
|
|
30
|
+
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
31
|
+
cwd,
|
|
32
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
33
|
+
})
|
|
34
|
+
.toString()
|
|
35
|
+
.trim();
|
|
36
|
+
} catch {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function minimalismMode(cwd) {
|
|
42
|
+
try {
|
|
43
|
+
const file = join(cwd, '.dw', 'minimalism.json');
|
|
44
|
+
if (!existsSync(file)) return 'full';
|
|
45
|
+
const mode = JSON.parse(readFileSync(file, 'utf-8')).mode;
|
|
46
|
+
return typeof mode === 'string' ? mode : 'full';
|
|
47
|
+
} catch {
|
|
48
|
+
return 'full';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function activeSpec(cwd) {
|
|
53
|
+
try {
|
|
54
|
+
const specDir = join(cwd, '.dw', 'spec');
|
|
55
|
+
if (!existsSync(specDir)) return '';
|
|
56
|
+
const dirs = readdirSync(specDir, { withFileTypes: true })
|
|
57
|
+
.filter((e) => e.isDirectory())
|
|
58
|
+
.map((e) => e.name);
|
|
59
|
+
return dirs.length ? `spec:${dirs.length}` : '';
|
|
60
|
+
} catch {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function main() {
|
|
66
|
+
let cwd = process.cwd();
|
|
67
|
+
try {
|
|
68
|
+
const payload = JSON.parse(await readStdin());
|
|
69
|
+
cwd =
|
|
70
|
+
(payload && payload.workspace && payload.workspace.current_dir) ||
|
|
71
|
+
(payload && payload.cwd) ||
|
|
72
|
+
cwd;
|
|
73
|
+
} catch {
|
|
74
|
+
/* use process.cwd() */
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const parts = [];
|
|
78
|
+
const branch = gitBranch(cwd);
|
|
79
|
+
if (branch) parts.push(`⎇ ${branch}`);
|
|
80
|
+
const spec = activeSpec(cwd);
|
|
81
|
+
if (spec) parts.push(spec);
|
|
82
|
+
parts.push(`min:${minimalismMode(cwd)}`);
|
|
83
|
+
|
|
84
|
+
process.stdout.write(`dw · ${parts.join(' · ')}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
main().catch(() => process.stdout.write('dw'));
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"expected_output": "A selected API testing recipe and evidence path for the active stack.",
|
|
11
11
|
"load_policy": "lazy-references",
|
|
12
12
|
"context_limit": 12000,
|
|
13
|
+
"invocation": "explicit",
|
|
14
|
+
"exportable": true,
|
|
13
15
|
"bundled": true
|
|
14
16
|
},
|
|
15
17
|
{
|
|
@@ -21,6 +23,8 @@
|
|
|
21
23
|
"expected_output": "A selected service recipe and composition guidance.",
|
|
22
24
|
"load_policy": "lazy-references",
|
|
23
25
|
"context_limit": 12000,
|
|
26
|
+
"invocation": "explicit",
|
|
27
|
+
"exportable": true,
|
|
24
28
|
"bundled": true
|
|
25
29
|
},
|
|
26
30
|
{
|
|
@@ -122,6 +126,19 @@
|
|
|
122
126
|
"context_limit": 12000,
|
|
123
127
|
"bundled": true
|
|
124
128
|
},
|
|
129
|
+
{
|
|
130
|
+
"name": "dw-minimalism",
|
|
131
|
+
"kind": "protocol",
|
|
132
|
+
"tier": "core",
|
|
133
|
+
"owner": "dw-run,dw-plan,dw-review",
|
|
134
|
+
"trigger": "Before writing new code, infrastructure, or abstractions: YAGNI decision ladder and lite/full/ultra intensity",
|
|
135
|
+
"expected_output": "A minimalism verdict (build/reuse/inline/skip) with the ladder rung that justified it.",
|
|
136
|
+
"load_policy": "always-small",
|
|
137
|
+
"context_limit": 8000,
|
|
138
|
+
"invocation": "model",
|
|
139
|
+
"exportable": true,
|
|
140
|
+
"bundled": true
|
|
141
|
+
},
|
|
125
142
|
{
|
|
126
143
|
"name": "dw-review-rigor",
|
|
127
144
|
"kind": "protocol",
|
|
@@ -142,6 +159,7 @@
|
|
|
142
159
|
"expected_output": "Adopt/wrap/compose/build decision with checked search channels.",
|
|
143
160
|
"load_policy": "always-small",
|
|
144
161
|
"context_limit": 6000,
|
|
162
|
+
"exportable": true,
|
|
145
163
|
"bundled": true
|
|
146
164
|
},
|
|
147
165
|
{
|
|
@@ -164,6 +182,7 @@
|
|
|
164
182
|
"expected_output": "Simplification target, risk, behavior-preserving checks, and recommendation.",
|
|
165
183
|
"load_policy": "lazy-references",
|
|
166
184
|
"context_limit": 12000,
|
|
185
|
+
"exportable": true,
|
|
167
186
|
"bundled": true
|
|
168
187
|
},
|
|
169
188
|
{
|
|
@@ -208,6 +227,7 @@
|
|
|
208
227
|
"expected_output": "Fresh verification report with cited command evidence.",
|
|
209
228
|
"load_policy": "always-small",
|
|
210
229
|
"context_limit": 12000,
|
|
230
|
+
"exportable": true,
|
|
211
231
|
"bundled": true
|
|
212
232
|
},
|
|
213
233
|
{
|
|
@@ -219,6 +239,8 @@
|
|
|
219
239
|
"expected_output": "Rewritten prose with AI-writing tells removed and voice preserved.",
|
|
220
240
|
"load_policy": "lazy-references",
|
|
221
241
|
"context_limit": 6000,
|
|
242
|
+
"invocation": "explicit",
|
|
243
|
+
"exportable": true,
|
|
222
244
|
"bundled": true
|
|
223
245
|
},
|
|
224
246
|
{
|
|
@@ -230,6 +252,8 @@
|
|
|
230
252
|
"expected_output": "Selected Remotion rule references and implementation guidance.",
|
|
231
253
|
"load_policy": "lazy-references",
|
|
232
254
|
"context_limit": 8000,
|
|
255
|
+
"invocation": "explicit",
|
|
256
|
+
"exportable": true,
|
|
233
257
|
"bundled": true
|
|
234
258
|
},
|
|
235
259
|
{
|
|
@@ -241,6 +265,7 @@
|
|
|
241
265
|
"expected_output": "High-confidence security findings or explicit pass.",
|
|
242
266
|
"load_policy": "lazy-references",
|
|
243
267
|
"context_limit": 14000,
|
|
268
|
+
"exportable": true,
|
|
244
269
|
"bundled": true
|
|
245
270
|
},
|
|
246
271
|
{
|
|
@@ -252,6 +277,8 @@
|
|
|
252
277
|
"expected_output": "Selected performance rule references and metric-driven recommendations.",
|
|
253
278
|
"load_policy": "lazy-references",
|
|
254
279
|
"context_limit": 12000,
|
|
280
|
+
"invocation": "explicit",
|
|
281
|
+
"exportable": true,
|
|
255
282
|
"bundled": true
|
|
256
283
|
}
|
|
257
284
|
]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dw-minimalism
|
|
3
|
+
description: Pre-generation YAGNI gate. Before writing new code, infra, or abstractions, climb the decision ladder (need it? reuse? stdlib? native? installed dep? one line?) at the active lite/full/ultra intensity. Triggers from /dw-run, /dw-plan, /dw-review and any "add X / build Y" intent.
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Grep
|
|
7
|
+
- Glob
|
|
8
|
+
- Bash
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# dw-minimalism — Write the Least Code That Solves the Problem
|
|
12
|
+
|
|
13
|
+
The cheapest code to maintain is the code you never wrote. This skill runs a
|
|
14
|
+
**pre-generation gate**: before producing a new function, file, dependency, or
|
|
15
|
+
abstraction, justify it against a decision ladder. It is the missing rung
|
|
16
|
+
between `dw-search-first` (which evaluates *external dependencies*) and
|
|
17
|
+
`dw-simplification` (which cleans up code *after* it exists).
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
Read this skill when:
|
|
22
|
+
|
|
23
|
+
- `/dw-plan` is about to specify new modules/services — pressure-test scope before tasks are written.
|
|
24
|
+
- `/dw-run` is about to implement — climb the ladder before the first line of new code.
|
|
25
|
+
- `/dw-review` flags over-engineering (speculative generality, premature abstraction, unused options).
|
|
26
|
+
- The user (or a task) says "add X", "build Y", "create a helper/wrapper/abstraction for Z".
|
|
27
|
+
|
|
28
|
+
Do NOT use to:
|
|
29
|
+
|
|
30
|
+
- Block code that is genuinely required by an accepted PRD/TechSpec acceptance criterion.
|
|
31
|
+
- Strip safety, validation, accessibility, security, or error handling — minimalism is about *necessity*, never about cutting correctness. Removing a guard is not minimalism.
|
|
32
|
+
- Re-litigate existing code you are not touching (that is `dw-simplification`'s scope discipline).
|
|
33
|
+
|
|
34
|
+
## The Decision Ladder
|
|
35
|
+
|
|
36
|
+
Evaluate **in order** and stop at the first rung that resolves the need. Record which rung justified the decision.
|
|
37
|
+
|
|
38
|
+
1. **Does it need to exist? (YAGNI)** — Is there a current, concrete requirement, or is this for an imagined future? No requirement → don't build it. Defer to a task/ADR instead.
|
|
39
|
+
2. **Already in the codebase?** — Search first (`grep`/`glob`/`.dw/intel/`). Reuse an existing function, type, or pattern over a new one.
|
|
40
|
+
3. **Standard library / language built-in?** — Prefer the platform's own primitives over hand-rolled equivalents.
|
|
41
|
+
4. **Native framework feature?** — The framework/runtime already in use may cover it (routing, validation, caching, etc.).
|
|
42
|
+
5. **An already-installed dependency?** — Use a package the project already depends on before adding a new one. (New dependency → hand off to `dw-search-first` for the adopt/wrap/compose/build decision.)
|
|
43
|
+
6. **Can it be one line / inlined?** — A small inline expression often beats a new helper, indirection layer, or config knob. Don't extract until there are real, repeated call sites.
|
|
44
|
+
7. **Only then: the minimum viable implementation** — the smallest change that satisfies the requirement, no speculative parameters, hooks, or generality.
|
|
45
|
+
|
|
46
|
+
If you cannot name the rung that justifies the code, you are not ready to write it.
|
|
47
|
+
|
|
48
|
+
## Intensity Modes
|
|
49
|
+
|
|
50
|
+
The active mode is read from `.dw/minimalism.json` (`{"mode":"full"}`); default `full` when absent.
|
|
51
|
+
|
|
52
|
+
| Mode | Behavior |
|
|
53
|
+
|------|----------|
|
|
54
|
+
| `off` | Skill is dormant; no minimalism gate applied. |
|
|
55
|
+
| `lite` | Apply rungs 1–2 only (YAGNI + reuse). Light touch; flags only obvious over-build. |
|
|
56
|
+
| `full` | **Default.** Apply the full ladder; flag speculative generality and premature abstraction. |
|
|
57
|
+
| `ultra` | Aggressive. Prefer inlining over any new helper/file; demand explicit justification for every new module, dependency, parameter, and config option. |
|
|
58
|
+
|
|
59
|
+
Stricter modes raise the bar for "this needs to exist"; none of them authorize cutting correctness, validation, or security.
|
|
60
|
+
|
|
61
|
+
## Anti-patterns this gate catches
|
|
62
|
+
|
|
63
|
+
- A new utility/wrapper with a single caller (rung 6 — inline it).
|
|
64
|
+
- A config flag, strategy interface, or plugin point with exactly one implementation (rung 1 — YAGNI).
|
|
65
|
+
- A hand-rolled debounce/clone/group-by/date-format when stdlib or an installed dep already has it (rungs 3–5).
|
|
66
|
+
- A new dependency for a few lines of logic (rung 5 → `dw-search-first`).
|
|
67
|
+
- "Future-proofing" abstractions for requirements that do not exist yet (rung 1).
|
|
68
|
+
|
|
69
|
+
## Red Flags (stop and climb the ladder)
|
|
70
|
+
|
|
71
|
+
"we'll probably need", "to be safe", "make it generic", "for flexibility",
|
|
72
|
+
"in case we want to", a base class / interface with one subclass, an options
|
|
73
|
+
object where every caller passes the same value.
|
|
74
|
+
|
|
75
|
+
## Composition
|
|
76
|
+
|
|
77
|
+
- **New external dependency?** → hand off to `dw-search-first` (adopt / wrap / compose / build).
|
|
78
|
+
- **Code already exists and is too complex?** → that is `dw-simplification` (Chesterton's Fence, behavior-preserving).
|
|
79
|
+
- This skill sits *before* both: it decides whether the code should be written at all.
|
|
80
|
+
|
|
81
|
+
## Structured Return
|
|
82
|
+
|
|
83
|
+
When invoked directly or by a harness, return or merge this block:
|
|
84
|
+
|
|
85
|
+
- **Status:** `PASS` when the proposed code is the minimum justified by a ladder rung, `FINDINGS` when over-build is detected and a leaner alternative exists, `BLOCKED` when the requirement is too unclear to judge necessity, `NOT_APPLICABLE` when no new code/dependency/abstraction is being introduced.
|
|
86
|
+
- **Scope:** the proposed code, the requirement it serves, and the active intensity mode.
|
|
87
|
+
- **Evidence:** the ladder rung that justified (or failed) the code, search hits for existing reuse, stdlib/native/installed-dep alternatives checked.
|
|
88
|
+
- **Artifacts:** the leaner alternative, a deferral task/ADR for speculative work, or the minimal implementation sketch.
|
|
89
|
+
- **Decisions:** build / reuse / inline / use-existing-dep / defer / skip — with the rejected heavier option.
|
|
90
|
+
- **Risks:** correctness or safety that must NOT be cut, hidden requirement that could justify more, future cost of the deferral.
|
|
91
|
+
- **Next Step:** write the minimal version, reuse the found code, or hand off to `dw-search-first`/`dw-simplification`.
|
|
92
|
+
|
|
93
|
+
## Inspired by
|
|
94
|
+
|
|
95
|
+
Adapted from [`DietrichGebert/ponytail`](https://github.com/DietrichGebert/ponytail)
|
|
96
|
+
(MIT) — the "lazy senior developer" decision ladder and lite/full/ultra intensity
|
|
97
|
+
modes. Rebased on dev-workflow conventions and wired to compose with
|
|
98
|
+
`dw-search-first` and `dw-simplification`.
|