@brunosps00/dev-workflow 1.3.0 → 1.5.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 +24 -4
- package/lib/build-plugin.js +79 -0
- package/lib/constants.js +19 -14
- package/lib/doctor.js +33 -0
- package/lib/hooks.js +179 -0
- package/lib/init.js +68 -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/config/stack-mappings.json +40 -0
- package/scaffold/en/commands/dw-analyze-project.md +5 -1
- package/scaffold/en/commands/dw-context-budget.md +9 -0
- package/scaffold/en/commands/dw-harness-audit.md +3 -0
- package/scaffold/en/commands/dw-learn.md +41 -0
- package/scaffold/en/commands/dw-plan.md +3 -0
- package/scaffold/en/commands/dw-review.md +2 -0
- package/scaffold/en/commands/dw-run.md +2 -0
- package/scaffold/en/commands/dw-update.md +4 -1
- package/scaffold/pt-br/commands/dw-analyze-project.md +5 -1
- package/scaffold/pt-br/commands/dw-context-budget.md +9 -0
- package/scaffold/pt-br/commands/dw-harness-audit.md +3 -0
- package/scaffold/pt-br/commands/dw-learn.md +41 -0
- package/scaffold/pt-br/commands/dw-plan.md +3 -0
- package/scaffold/pt-br/commands/dw-review.md +2 -0
- package/scaffold/pt-br/commands/dw-run.md +2 -0
- package/scaffold/pt-br/commands/dw-update.md +4 -1
- package/scaffold/rules/README.md +22 -0
- package/scaffold/rules/common.md +29 -0
- package/scaffold/rules/csharp.md +18 -0
- package/scaffold/rules/python.md +18 -0
- package/scaffold/rules/react.md +18 -0
- package/scaffold/rules/rust.md +18 -0
- package/scaffold/rules/typescript.md +23 -0
- package/scaffold/rules-readme.md +8 -0
- package/scaffold/scripts/hooks/git-guardrails.mjs +79 -0
- package/scaffold/scripts/hooks/session-cost.mjs +126 -0
- package/scaffold/scripts/hooks/statusline.mjs +118 -0
- package/scaffold/scripts/lib/model-prices.json +12 -0
- package/scaffold/skill-registry.json +27 -0
- package/scaffold/skills/dw-cli-run/SKILL.md +3 -1
- package/scaffold/skills/dw-codebase-intel/SKILL.md +3 -1
- package/scaffold/skills/dw-debug-protocol/SKILL.md +4 -0
- package/scaffold/skills/dw-debug-protocol/references/agent-degradation.md +30 -0
- package/scaffold/skills/dw-execute-phase/SKILL.md +13 -0
- package/scaffold/skills/dw-git-discipline/SKILL.md +1 -0
- package/scaffold/skills/dw-llm-eval/SKILL.md +12 -0
- package/scaffold/skills/dw-memory/SKILL.md +12 -1
- package/scaffold/skills/dw-memory/references/instincts.md +45 -0
- package/scaffold/skills/dw-minimalism/SKILL.md +98 -0
- package/scaffold/skills/dw-review-rigor/SKILL.md +12 -0
- package/scaffold/skills/dw-review-rigor/references/false-positives.md +21 -0
- package/scaffold/skills/dw-review-rigor/references/self-eval-rubric.md +38 -0
- package/scaffold/skills/dw-silent-failure/SKILL.md +4 -2
- package/scaffold/skills/dw-simplification/SKILL.md +13 -0
- package/scaffold/skills/dw-simplification/references/dead-code-tools.md +23 -0
- package/scaffold/skills/dw-testing-discipline/SKILL.md +17 -0
- package/scaffold/skills/dw-ui-discipline/SKILL.md +1 -1
- package/scaffold/skills/dw-verify/SKILL.md +1 -1
- package/scaffold/skills/dw-verify/references/pre-check-and-status.md +28 -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.5.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
|
@@ -12,10 +12,11 @@ This will:
|
|
|
12
12
|
1. Ask you to select a language (English or Portuguese)
|
|
13
13
|
2. Create `.dw/commands/` with workflow commands
|
|
14
14
|
3. Create `.dw/templates/` with document templates (PRD, TechSpec, Tasks, ADR, etc.)
|
|
15
|
-
4. Create `.dw/rules/` (populated by `/dw-analyze-project`)
|
|
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
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 `SessionEnd` token-cost tracker → `.dw/metrics/costs.jsonl`) + a statusline (branch · active spec · minimalism mode · today's estimated spend) 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.5.0 ships **37 commands** organized into four tiers. Most users only invoke Tier 1 + Tier 2.
|
|
30
46
|
|
|
31
47
|
### Tier 1 — Gateway (3)
|
|
32
48
|
|
|
@@ -53,7 +69,7 @@ Use these when you want step-by-step control instead of `/dw-autopilot`.
|
|
|
53
69
|
| **`/dw-commit`** | Atomic Conventional Commits for pending changes. Applies `dw-git-discipline` (one intent per commit, lint+tests+build green before). |
|
|
54
70
|
| **`/dw-generate-pr [target]`** | Push the branch, draft a PR body with summary + test plan, open the browser. Hard gates: dw-verify PASS + secure-audit. |
|
|
55
71
|
|
|
56
|
-
### Tier 3 — Specialty (
|
|
72
|
+
### Tier 3 — Specialty (12)
|
|
57
73
|
|
|
58
74
|
| Command | What |
|
|
59
75
|
|---------|------|
|
|
@@ -64,6 +80,7 @@ Use these when you want step-by-step control instead of `/dw-autopilot`.
|
|
|
64
80
|
| **`/dw-context-budget`** | Audits context overhead from commands, skills, agents, instruction files, and MCPs. |
|
|
65
81
|
| **`/dw-harness-audit`** | Deterministic health scorecard for the dev-workflow install, agents, wrappers, MCPs, and gates. |
|
|
66
82
|
| **`/dw-skill-health`** | Audits installed skills and agents for bloat, duplication, and missing references. |
|
|
83
|
+
| **`/dw-learn`** | Synthesizes durable decisions (confidence-tagged), bugfixes, deviations, and git history into atomic confidence-weighted **instincts** you approve at `.dw/memory/instincts/`; can propose constitution principles. On-demand and human-in-the-loop — **no always-on observer**. |
|
|
67
84
|
| **`/dw-new-project`** | Bootstrap a new project from empty directory. Stack interview, wraps official `create-*` tools, composes docker-compose for dev, seeds `.env`, scripts, CI, `.dw/rules/`. |
|
|
68
85
|
| **`/dw-dockerize`** | Reads existing project, detects stack + runtime deps, proposes Dockerfile + docker-compose for dev/prod with explicit trade-offs (Conservative/Balanced/Bold). |
|
|
69
86
|
| **`/dw-install-azure-skills`** | **Opt-in.** Clones curated Azure skills from [`MicrosoftDocs/Agent-Skills`](https://github.com/MicrosoftDocs/Agent-Skills) (CC-BY-4.0) into `.agents/skills/azure/` and registers the [Microsoft Learn MCP Server](https://learn.microsoft.com/en-us/training/support/mcp-get-started) (HTTP, no-auth). Interactive category selection (Compute / Data & Storage / AI & ML / Networking / Identity & Security / DevOps / Observability / Integration / Architecture / All). Re-run to refresh from upstream. Also available as CLI: `npx @brunosps00/dev-workflow install-azure-skills`. |
|
|
@@ -249,7 +266,7 @@ npx @brunosps00/dev-workflow subtask list
|
|
|
249
266
|
|
|
250
267
|
## Bundled Skills
|
|
251
268
|
|
|
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
|
|
269
|
+
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
270
|
|
|
254
271
|
Skill kinds:
|
|
255
272
|
|
|
@@ -274,6 +291,7 @@ These are not slash commands — they are primitives other commands call to enfo
|
|
|
274
291
|
| **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
292
|
| **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
293
|
| **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) |
|
|
294
|
+
| **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
295
|
| **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
296
|
| **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
297
|
|
|
@@ -347,6 +365,8 @@ Spec-Driven Development patterns — declarative constitution (`.dw/constitution
|
|
|
347
365
|
|
|
348
366
|
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
367
|
|
|
368
|
+
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.
|
|
369
|
+
|
|
350
370
|
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
371
|
|
|
352
372
|
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.' },
|
|
@@ -20,6 +20,7 @@ const COMMANDS = {
|
|
|
20
20
|
{ name: 'dw-install-aws-skills', description: 'Trigger when user asks to install AWS expertise, setup AWS MCP server, or add AWS agent skills. Opt-in: clones curated skills from aws/agent-toolkit-for-aws into .agents/skills/aws/ and registers the unified AWS MCP Server (stdio via mcp-proxy-for-aws). Requires uv, aws cli, and AWS credentials.' },
|
|
21
21
|
{ name: 'dw-install-azure-skills', description: 'Trigger when user asks to install Azure expertise, setup Microsoft docs MCP, or add Azure agent skills. Opt-in: clones curated skills from MicrosoftDocs/Agent-Skills into .agents/skills/azure/ and registers the Microsoft Learn MCP server.' },
|
|
22
22
|
{ name: 'dw-intel', description: 'Codebase intelligence: query mode (default) answers questions citing .dw/intel/ + .dw/rules/; --build mode (re)builds the index.' },
|
|
23
|
+
{ name: 'dw-learn', description: 'Synthesizes durable decisions, bugfixes, deviations, and git history into atomic confidence-weighted instincts you approve; can propose constitution principles. On-demand and human-in-the-loop — no always-on observer.' },
|
|
23
24
|
{ name: 'dw-new-project', description: 'Interviews you about stack and infra, then scaffolds a working monorepo with docker-compose for dev, .env, scripts, CI, and seeded rules.' },
|
|
24
25
|
{ name: 'dw-opportunities', description: 'Scans the installed project for product, UX, automation, refactor, and security opportunities, then routes each idea to the right next dw-* command.' },
|
|
25
26
|
{ name: 'dw-pause', description: 'Trigger when user says "pause work", "end session", or "save where we are". Consolidates open loops, decisions, blockers, and todos into .dw/STATE.md so the next session can resume.' },
|
|
@@ -32,9 +33,9 @@ const COMMANDS = {
|
|
|
32
33
|
{ 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
34
|
{ 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
35
|
{ 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.' },
|
|
36
|
+
{ name: 'dw-subtask-start', userInvoked: true, description: 'Creates a minimal input packet for a subagent without injecting the parent transcript.' },
|
|
37
|
+
{ name: 'dw-subtask-complete', userInvoked: true, description: 'Records a structured subagent handoff with result, files, decisions, risks, verification, and blockers.' },
|
|
38
|
+
{ name: 'dw-subtask-resume', userInvoked: true, description: 'Consumes pending subagent handoffs, summarizes them for the parent, and archives the local packets.' },
|
|
38
39
|
{ name: 'dw-update', description: 'Updates dev-workflow to the latest npm release in-place, with a snapshot you can roll back to.' },
|
|
39
40
|
],
|
|
40
41
|
'pt-br': [
|
|
@@ -43,10 +44,10 @@ const COMMANDS = {
|
|
|
43
44
|
{ 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
45
|
{ 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
46
|
{ 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.' },
|
|
47
|
+
{ 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.' },
|
|
48
|
+
{ 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
49
|
{ 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.' },
|
|
50
|
+
{ 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
51
|
{ 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
52
|
{ 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
53
|
{ name: 'dw-functional-doc', description: 'Mapeia telas e fluxos em um dossie funcional, validado E2E com Playwright.' },
|
|
@@ -58,6 +59,7 @@ const COMMANDS = {
|
|
|
58
59
|
{ name: 'dw-install-aws-skills', description: 'Trigger quando usuario pede pra instalar expertise AWS, configurar MCP da AWS, ou adicionar agent skills AWS. Opt-in: clona skills curadas de aws/agent-toolkit-for-aws para .agents/skills/aws/ e registra o AWS MCP Server unificado (stdio via mcp-proxy-for-aws). Requer uv, aws cli e credenciais AWS.' },
|
|
59
60
|
{ name: 'dw-install-azure-skills', description: 'Trigger quando usuario pede pra instalar expertise Azure, configurar MCP do Microsoft docs, ou adicionar agent skills Azure. Opt-in: clona skills curadas de MicrosoftDocs/Agent-Skills para .agents/skills/azure/ e registra o Microsoft Learn MCP server.' },
|
|
60
61
|
{ name: 'dw-intel', description: 'Inteligencia do codebase: modo query (default) responde citando .dw/intel/ + .dw/rules/; modo --build (re)constroi indice.' },
|
|
62
|
+
{ name: 'dw-learn', description: 'Sintetiza decisoes duraveis, bugfixes, deviations e historico git em instincts atomicos ponderados por confianca que voce aprova; pode propor principios de constitution. On-demand e human-in-the-loop — sem observador sempre-ligado.' },
|
|
61
63
|
{ name: 'dw-new-project', description: 'Entrevista voce sobre stack e infra, depois faz scaffold de um monorepo com docker-compose para dev, .env, scripts, CI e rules seed.' },
|
|
62
64
|
{ name: 'dw-opportunities', description: 'Escaneia o projeto instalado por oportunidades de produto, UX, automacao, refactor e seguranca, e roteia cada ideia para o proximo comando dw-* correto.' },
|
|
63
65
|
{ name: 'dw-pause', description: 'Trigger quando usuario diz "pausa o trabalho", "encerra a sessao" ou "salva onde paramos". Consolida pontas soltas, decisoes, bloqueios e todos em .dw/STATE.md para a proxima sessao retomar.' },
|
|
@@ -70,9 +72,9 @@ const COMMANDS = {
|
|
|
70
72
|
{ 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
73
|
{ 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
74
|
{ 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.' },
|
|
75
|
+
{ name: 'dw-subtask-start', userInvoked: true, description: 'Cria um input packet minimo para subagente sem injetar transcript bruto do parent.' },
|
|
76
|
+
{ name: 'dw-subtask-complete', userInvoked: true, description: 'Registra handoff estruturado do subagente com resultado, arquivos, decisoes, riscos, verificacao e bloqueios.' },
|
|
77
|
+
{ name: 'dw-subtask-resume', userInvoked: true, description: 'Consome handoffs pendentes de subagentes, resume para o parent e arquiva os pacotes locais.' },
|
|
76
78
|
{ name: 'dw-update', description: 'Atualiza o dev-workflow para o release mais recente no npm, com snapshot para rollback.' },
|
|
77
79
|
],
|
|
78
80
|
};
|
|
@@ -80,9 +82,12 @@ const COMMANDS = {
|
|
|
80
82
|
const PLATFORMS = {
|
|
81
83
|
claude: {
|
|
82
84
|
dir: '.claude/skills',
|
|
83
|
-
|
|
85
|
+
// opts.userInvoked → emit disable-model-invocation: true so Claude Code never
|
|
86
|
+
// auto-fires hidden/internal commands (e.g. the worktree runners); the user
|
|
87
|
+
// still invokes them explicitly with /<name>.
|
|
88
|
+
wrapperTemplate: (name, description, opts = {}) => `---
|
|
84
89
|
name: ${name}
|
|
85
|
-
description: "${description.replace(/"/g, '\\"')}"
|
|
90
|
+
description: "${description.replace(/"/g, '\\"')}"${opts.userInvoked ? '\ndisable-model-invocation: true' : ''}
|
|
86
91
|
---
|
|
87
92
|
|
|
88
93
|
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,179 @@
|
|
|
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
|
+
const SESSION_COST_CMD = 'node .dw/scripts/hooks/session-cost.mjs';
|
|
11
|
+
const SESSION_COST_SCRIPT = 'session-cost.mjs';
|
|
12
|
+
|
|
13
|
+
function isOurs(command) {
|
|
14
|
+
return typeof command === 'string' && command.includes(HOOKS_DIR_MARKER);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reconcile dev-workflow hooks + statusLine into .claude/settings.json.
|
|
19
|
+
*
|
|
20
|
+
* Marker-based and merge-aware (unlike installMCPs' add-if-missing):
|
|
21
|
+
* - adds our entries when absent;
|
|
22
|
+
* - refreshes our entries in place when the script path/shape changed (so an
|
|
23
|
+
* update actually updates);
|
|
24
|
+
* - never removes or rewrites hooks/statusLine the user added;
|
|
25
|
+
* - leaves a user-defined statusLine untouched (reports it so the caller can
|
|
26
|
+
* surface a post-update note).
|
|
27
|
+
*
|
|
28
|
+
* Returns { hooks, statusLine } where each is 'added' | 'updated' | 'unchanged'
|
|
29
|
+
* (statusLine may also be 'skipped-user').
|
|
30
|
+
*/
|
|
31
|
+
function installHooks(projectRoot) {
|
|
32
|
+
const settings = readSettings(projectRoot);
|
|
33
|
+
const before = JSON.stringify(settings);
|
|
34
|
+
|
|
35
|
+
const hooksResult = reconcilePreToolUseGitGuardrails(settings);
|
|
36
|
+
const sessionCostResult = reconcileSessionEndCost(settings);
|
|
37
|
+
const statusLineResult = reconcileStatusLine(settings);
|
|
38
|
+
|
|
39
|
+
if (JSON.stringify(settings) !== before) {
|
|
40
|
+
writeSettings(projectRoot, settings);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { hooks: hooksResult, sessionCost: sessionCostResult, statusLine: statusLineResult };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Reconcile the SessionEnd cost-tracking hook (marker-based, merge-aware — same
|
|
48
|
+
* contract as the git guardrail). No matcher: fires on every session end.
|
|
49
|
+
* Returns 'added' | 'updated' | 'unchanged'.
|
|
50
|
+
*/
|
|
51
|
+
function reconcileSessionEndCost(settings) {
|
|
52
|
+
if (!settings.hooks || typeof settings.hooks !== 'object') settings.hooks = {};
|
|
53
|
+
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
54
|
+
|
|
55
|
+
const groups = settings.hooks.SessionEnd;
|
|
56
|
+
|
|
57
|
+
const existed = groups.some(
|
|
58
|
+
(g) => g && Array.isArray(g.hooks) && g.hooks.some((h) => h && typeof h.command === 'string' && h.command.includes(SESSION_COST_SCRIPT)),
|
|
59
|
+
);
|
|
60
|
+
const exactPresent = groups.some(
|
|
61
|
+
(g) => g && Array.isArray(g.hooks) && g.hooks.some((h) => h && h.command === SESSION_COST_CMD),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (exactPresent) return 'unchanged';
|
|
65
|
+
|
|
66
|
+
// Drop any stale/relocated copies of OUR hook, preserving user hooks.
|
|
67
|
+
for (const group of groups) {
|
|
68
|
+
if (!group || !Array.isArray(group.hooks)) continue;
|
|
69
|
+
group.hooks = group.hooks.filter(
|
|
70
|
+
(h) => !(h && typeof h.command === 'string' && h.command.includes(SESSION_COST_SCRIPT)),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
settings.hooks.SessionEnd = groups.filter((g) => g && Array.isArray(g.hooks) && g.hooks.length > 0);
|
|
74
|
+
|
|
75
|
+
settings.hooks.SessionEnd.push({ hooks: [{ type: 'command', command: SESSION_COST_CMD }] });
|
|
76
|
+
|
|
77
|
+
return existed ? 'updated' : 'added';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function reconcilePreToolUseGitGuardrails(settings) {
|
|
81
|
+
if (!settings.hooks || typeof settings.hooks !== 'object') settings.hooks = {};
|
|
82
|
+
if (!Array.isArray(settings.hooks.PreToolUse)) settings.hooks.PreToolUse = [];
|
|
83
|
+
|
|
84
|
+
const groups = settings.hooks.PreToolUse;
|
|
85
|
+
|
|
86
|
+
// Was our guardrail already wired (anywhere, any shape)?
|
|
87
|
+
const existed = groups.some(
|
|
88
|
+
(g) =>
|
|
89
|
+
g && Array.isArray(g.hooks) && g.hooks.some((h) => h && typeof h.command === 'string' && h.command.includes(GIT_GUARDRAILS_SCRIPT)),
|
|
90
|
+
);
|
|
91
|
+
// Already wired with the exact canonical command in a Bash group?
|
|
92
|
+
const exactPresent = groups.some(
|
|
93
|
+
(g) =>
|
|
94
|
+
g && g.matcher === 'Bash' && Array.isArray(g.hooks) && g.hooks.some((h) => h && h.command === GIT_GUARDRAILS_CMD),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (exactPresent) return 'unchanged';
|
|
98
|
+
|
|
99
|
+
// Drop any stale/relocated copies of OUR guardrail, preserving user hooks.
|
|
100
|
+
for (const group of groups) {
|
|
101
|
+
if (!group || !Array.isArray(group.hooks)) continue;
|
|
102
|
+
group.hooks = group.hooks.filter(
|
|
103
|
+
(h) => !(h && typeof h.command === 'string' && h.command.includes(GIT_GUARDRAILS_SCRIPT)),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
// Prune groups we just emptied (only those left with zero hooks).
|
|
107
|
+
settings.hooks.PreToolUse = groups.filter((g) => g && Array.isArray(g.hooks) && g.hooks.length > 0);
|
|
108
|
+
|
|
109
|
+
// Re-add the canonical entry: into an existing Bash group if present, else new.
|
|
110
|
+
const bashGroup = settings.hooks.PreToolUse.find((g) => g && g.matcher === 'Bash' && Array.isArray(g.hooks));
|
|
111
|
+
if (bashGroup) {
|
|
112
|
+
bashGroup.hooks.push({ type: 'command', command: GIT_GUARDRAILS_CMD });
|
|
113
|
+
} else {
|
|
114
|
+
settings.hooks.PreToolUse.push({ matcher: 'Bash', hooks: [{ type: 'command', command: GIT_GUARDRAILS_CMD }] });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return existed ? 'updated' : 'added';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function reconcileStatusLine(settings) {
|
|
121
|
+
const desired = { type: 'command', command: STATUSLINE_CMD };
|
|
122
|
+
const current = settings.statusLine;
|
|
123
|
+
|
|
124
|
+
if (!current) {
|
|
125
|
+
settings.statusLine = desired;
|
|
126
|
+
return 'added';
|
|
127
|
+
}
|
|
128
|
+
// Only manage a statusLine we own; never clobber a user's custom one.
|
|
129
|
+
if (current.command === STATUSLINE_CMD) return 'unchanged';
|
|
130
|
+
if (isOurs(current.command)) {
|
|
131
|
+
settings.statusLine = desired; // our script moved/renamed — refresh
|
|
132
|
+
return 'updated';
|
|
133
|
+
}
|
|
134
|
+
return 'skipped-user';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Remove ONLY dev-workflow-owned hook + statusLine entries (used by uninstall).
|
|
139
|
+
* Returns the number of entries removed.
|
|
140
|
+
*/
|
|
141
|
+
function removeHooks(projectRoot) {
|
|
142
|
+
const settings = readSettings(projectRoot);
|
|
143
|
+
let removed = 0;
|
|
144
|
+
|
|
145
|
+
// Remove OUR hook entries from every event (PreToolUse, SessionEnd, PostToolUse, …),
|
|
146
|
+
// identified by the .dw/scripts/hooks/ marker; never touch user-added hooks.
|
|
147
|
+
if (settings.hooks && typeof settings.hooks === 'object') {
|
|
148
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
149
|
+
const groups = settings.hooks[event];
|
|
150
|
+
if (!Array.isArray(groups)) continue;
|
|
151
|
+
for (const group of groups) {
|
|
152
|
+
if (!group || !Array.isArray(group.hooks)) continue;
|
|
153
|
+
const kept = group.hooks.filter((h) => !(h && isOurs(h.command)));
|
|
154
|
+
removed += group.hooks.length - kept.length;
|
|
155
|
+
group.hooks = kept;
|
|
156
|
+
}
|
|
157
|
+
settings.hooks[event] = groups.filter((g) => g && Array.isArray(g.hooks) && g.hooks.length > 0);
|
|
158
|
+
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
159
|
+
}
|
|
160
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (settings.statusLine && isOurs(settings.statusLine.command)) {
|
|
164
|
+
delete settings.statusLine;
|
|
165
|
+
removed++;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (removed > 0) writeSettings(projectRoot, settings);
|
|
169
|
+
return removed;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
installHooks,
|
|
174
|
+
removeHooks,
|
|
175
|
+
HOOKS_DIR_MARKER,
|
|
176
|
+
GIT_GUARDRAILS_CMD,
|
|
177
|
+
STATUSLINE_CMD,
|
|
178
|
+
SESSION_COST_CMD,
|
|
179
|
+
};
|
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');
|
|
@@ -169,6 +170,30 @@ async function run({ force = false, lang = null, mode = 'init', profile = null,
|
|
|
169
170
|
console.log();
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
// 3.9. Copy curated rules library + stack mappings (declarative baseline per stack,
|
|
174
|
+
// loaded lazily by the detected stack). Managed: reconciled on update/repair.
|
|
175
|
+
const rulesLibSrc = path.join(SCAFFOLD_DIR, 'rules');
|
|
176
|
+
if (require('fs').existsSync(rulesLibSrc)) {
|
|
177
|
+
console.log(' Rules library:');
|
|
178
|
+
const rlResults = copyDir(rulesLibSrc, path.join(projectRoot, '.dw', 'rules-library'), managedForce);
|
|
179
|
+
totalCreated += rlResults.created;
|
|
180
|
+
totalSkipped += rlResults.skipped;
|
|
181
|
+
totalOverwritten += rlResults.overwritten;
|
|
182
|
+
touchedFiles.push(...listFilesRecursive(path.join(projectRoot, '.dw', 'rules-library')));
|
|
183
|
+
console.log(` ${rlResults.created} created, ${rlResults.skipped} skipped, ${rlResults.overwritten} overwritten\n`);
|
|
184
|
+
}
|
|
185
|
+
const stackMapSrc = path.join(SCAFFOLD_DIR, 'config', 'stack-mappings.json');
|
|
186
|
+
if (require('fs').existsSync(stackMapSrc)) {
|
|
187
|
+
const cfgDir = path.join(projectRoot, '.dw', 'config');
|
|
188
|
+
ensureDir(cfgDir);
|
|
189
|
+
const stackMapDest = path.join(cfgDir, 'stack-mappings.json');
|
|
190
|
+
const smStatus = writeFile(stackMapDest, require('fs').readFileSync(stackMapSrc, 'utf-8'), managedForce);
|
|
191
|
+
if (smStatus === 'created') totalCreated++;
|
|
192
|
+
else if (smStatus === 'overwritten') totalOverwritten++;
|
|
193
|
+
else totalSkipped++;
|
|
194
|
+
touchedFiles.push(stackMapDest);
|
|
195
|
+
}
|
|
196
|
+
|
|
172
197
|
// 4. Create .dw/rules/ with README
|
|
173
198
|
if (!isUpdate) {
|
|
174
199
|
console.log(' Rules:');
|
|
@@ -211,6 +236,16 @@ async function run({ force = false, lang = null, mode = 'init', profile = null,
|
|
|
211
236
|
console.log();
|
|
212
237
|
}
|
|
213
238
|
|
|
239
|
+
// 5.15. Seed .dw/minimalism.json (init only; preserved on update). Holds the
|
|
240
|
+
// dw-minimalism intensity mode read by the skill and the statusline hook.
|
|
241
|
+
if (!isUpdate) {
|
|
242
|
+
const minimalismPath = path.join(projectRoot, '.dw', 'minimalism.json');
|
|
243
|
+
const minimalismStatus = writeFile(minimalismPath, JSON.stringify({ mode: 'full' }, null, 2) + '\n', false);
|
|
244
|
+
if (minimalismStatus === 'created') totalCreated++;
|
|
245
|
+
else totalSkipped++;
|
|
246
|
+
touchedFiles.push(minimalismPath);
|
|
247
|
+
}
|
|
248
|
+
|
|
214
249
|
// 5.2. Create .dw/bugfixes/ with .gitkeep (init only; preserved on update)
|
|
215
250
|
if (!isUpdate) {
|
|
216
251
|
const bugfixesDir = path.join(projectRoot, '.dw', 'bugfixes');
|
|
@@ -222,6 +257,30 @@ async function run({ force = false, lang = null, mode = 'init', profile = null,
|
|
|
222
257
|
touchedFiles.push(gitkeepPath);
|
|
223
258
|
}
|
|
224
259
|
|
|
260
|
+
// 5.25. Ensure .dw/metrics/ + .gitignore (runtime cost tracking; costs.jsonl is
|
|
261
|
+
// machine-local so it is gitignored). Created if missing on both init and update.
|
|
262
|
+
{
|
|
263
|
+
const metricsDir = path.join(projectRoot, '.dw', 'metrics');
|
|
264
|
+
ensureDir(metricsDir);
|
|
265
|
+
const metricsGitignore = path.join(metricsDir, '.gitignore');
|
|
266
|
+
const mStatus = writeFile(metricsGitignore, 'costs.jsonl\n', false);
|
|
267
|
+
if (mStatus === 'created') totalCreated++;
|
|
268
|
+
else totalSkipped++;
|
|
269
|
+
touchedFiles.push(metricsGitignore);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 5.26. Ensure .dw/memory/instincts/ (durable learnings promoted by /dw-learn;
|
|
273
|
+
// format in the dw-memory skill's references/instincts.md). Init only; preserved on update.
|
|
274
|
+
if (!isUpdate) {
|
|
275
|
+
const instinctsDir = path.join(projectRoot, '.dw', 'memory', 'instincts');
|
|
276
|
+
ensureDir(instinctsDir);
|
|
277
|
+
const instinctsKeep = path.join(instinctsDir, '.gitkeep');
|
|
278
|
+
const ikStatus = writeFile(instinctsKeep, '', false);
|
|
279
|
+
if (ikStatus === 'created') totalCreated++;
|
|
280
|
+
else totalSkipped++;
|
|
281
|
+
touchedFiles.push(instinctsKeep);
|
|
282
|
+
}
|
|
283
|
+
|
|
225
284
|
// 5.3. Create local ephemeral subtask handoff directories.
|
|
226
285
|
ensureSubtaskLayout(projectRoot);
|
|
227
286
|
touchedFiles.push(path.join(projectRoot, '.dw', 'subtasks', '.gitignore'));
|
|
@@ -302,6 +361,15 @@ async function run({ force = false, lang = null, mode = 'init', profile = null,
|
|
|
302
361
|
const mcpResults = installMCPs(projectRoot);
|
|
303
362
|
console.log(` ${mcpResults.added} configured, ${mcpResults.skipped} already present\n`);
|
|
304
363
|
|
|
364
|
+
// 9.2. Reconcile dev-workflow hooks + statusline (marker-based, merge-aware:
|
|
365
|
+
// refreshes our entries, never touches user-defined hooks/statusLine).
|
|
366
|
+
console.log(' Hooks & statusline:');
|
|
367
|
+
const hookResults = installHooks(projectRoot);
|
|
368
|
+
console.log(` git-guardrails: ${hookResults.hooks}, session-cost: ${hookResults.sessionCost}, statusline: ${hookResults.statusLine}\n`);
|
|
369
|
+
if (hookResults.statusLine === 'skipped-user') {
|
|
370
|
+
console.log(' Note: a custom statusLine is set; to show dev-workflow state add `node .dw/scripts/hooks/statusline.mjs`.\n');
|
|
371
|
+
}
|
|
372
|
+
|
|
305
373
|
// 9.5. Persist install state for diagnostics and repair.
|
|
306
374
|
const installState = writeInstallState(projectRoot, {
|
|
307
375
|
lang: selectedLang,
|