@brunosps00/dev-workflow 1.7.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/CHANGELOG.md +201 -0
  3. package/README.md +2 -2
  4. package/lib/agents.js +6 -1
  5. package/lib/init.js +14 -0
  6. package/lib/install-deps.js +15 -0
  7. package/lib/install-open-design.js +39 -0
  8. package/lib/install-state.js +8 -4
  9. package/lib/utils.js +46 -6
  10. package/package.json +3 -2
  11. package/scaffold/agents/core/dw-build-fixer.md +0 -1
  12. package/scaffold/agents/core/dw-code-explorer.md +0 -1
  13. package/scaffold/agents/core/dw-code-reviewer.md +0 -1
  14. package/scaffold/agents/core/dw-planner.md +0 -1
  15. package/scaffold/agents/core/dw-test-author.md +0 -1
  16. package/scaffold/agents/csharp/dw-csharp-reviewer.md +0 -1
  17. package/scaffold/agents/csharp/dw-dotnet-build-fixer.md +0 -1
  18. package/scaffold/agents/frontend/dw-qa-runner.md +0 -1
  19. package/scaffold/agents/python/dw-python-build-fixer.md +0 -1
  20. package/scaffold/agents/python/dw-python-reviewer.md +0 -1
  21. package/scaffold/agents/rust/dw-rust-build-fixer.md +0 -1
  22. package/scaffold/agents/rust/dw-rust-reviewer.md +0 -1
  23. package/scaffold/agents/security/dw-security-reviewer.md +0 -1
  24. package/scaffold/agents/security/dw-silent-failure-hunter.md +0 -1
  25. package/scaffold/agents/typescript/dw-typescript-build-fixer.md +0 -1
  26. package/scaffold/agents/typescript/dw-typescript-reviewer.md +0 -1
  27. package/scaffold/config/routing.json +60 -0
  28. package/scaffold/en/commands/dw-claude-run.md +5 -1
  29. package/scaffold/en/commands/dw-codex-run.md +5 -1
  30. package/scaffold/en/commands/dw-dockerize.md +14 -7
  31. package/scaffold/en/commands/dw-generate-pr.md +13 -0
  32. package/scaffold/en/commands/dw-new-project.md +34 -30
  33. package/scaffold/en/commands/dw-run.md +1 -1
  34. package/scaffold/en/templates/project-onepager.md +16 -14
  35. package/scaffold/en/templates/task-template.md +1 -1
  36. package/scaffold/en/templates/tasks-template.md +7 -5
  37. package/scaffold/pt-br/commands/dw-claude-run.md +5 -1
  38. package/scaffold/pt-br/commands/dw-codex-run.md +5 -1
  39. package/scaffold/pt-br/commands/dw-dockerize.md +14 -7
  40. package/scaffold/pt-br/commands/dw-generate-pr.md +13 -0
  41. package/scaffold/pt-br/commands/dw-new-project.md +34 -30
  42. package/scaffold/pt-br/commands/dw-plan.md +1 -1
  43. package/scaffold/pt-br/commands/dw-run.md +1 -1
  44. package/scaffold/pt-br/templates/project-onepager.md +16 -14
  45. package/scaffold/pt-br/templates/task-template.md +1 -1
  46. package/scaffold/pt-br/templates/tasks-template.md +7 -5
  47. package/scaffold/skills/docker-compose-recipes/SKILL.md +4 -5
  48. package/scaffold/skills/docker-compose-recipes/references/compose-composition.md +6 -6
  49. package/scaffold/skills/docker-compose-recipes/references/env-conventions.md +18 -9
  50. package/scaffold/skills/docker-compose-recipes/references/healthcheck-patterns.md +4 -5
  51. package/scaffold/skills/docker-compose-recipes/references/prod-vs-dev.md +7 -2
  52. package/scaffold/skills/docker-compose-recipes/services/elasticsearch.yml +3 -3
  53. package/scaffold/skills/docker-compose-recipes/services/mailpit.yml +4 -4
  54. package/scaffold/skills/docker-compose-recipes/services/meilisearch.yml +2 -2
  55. package/scaffold/skills/docker-compose-recipes/services/minio.yml +8 -4
  56. package/scaffold/skills/docker-compose-recipes/services/mysql.yml +4 -4
  57. package/scaffold/skills/docker-compose-recipes/services/postgres.yml +2 -2
  58. package/scaffold/skills/docker-compose-recipes/services/rabbitmq.yml +3 -3
  59. package/scaffold/skills/docker-compose-recipes/services/typesense.yml +3 -3
  60. package/scaffold/skills/dw-cli-run/SKILL.md +42 -38
  61. package/scaffold/skills/dw-cli-run/references/dispatch-tuning.md +85 -0
  62. package/scaffold/skills/dw-execute-phase/SKILL.md +4 -2
  63. package/scaffold/skills/dw-execute-phase/agents/executor.md +4 -2
  64. package/scaffold/skills/dw-execute-phase/agents/plan-checker.md +9 -7
  65. package/scaffold/skills/dw-execute-phase/references/atomic-commits.md +12 -8
  66. package/scaffold/skills/dw-execute-phase/references/plan-verification.md +6 -4
  67. package/scaffold/skills/docker-compose-recipes/services/mailhog.yml +0 -23
@@ -16,6 +16,10 @@ The recipes default to **dev**. When `/dw-dockerize --prod` (or `--both`) emits
16
16
  | Logging | Default driver | `json-file` with rotation, or ship to a log aggregator (Loki, CloudWatch) |
17
17
  | Resource limits | None (machine has plenty) | `mem_limit`, `cpus` set per service to prevent runaway containers |
18
18
 
19
+ Every recipe tracks a tag rather than a digest on purpose: a digest freezes the base image and accrues CVEs with no upgrade path, since only a rebuild clears them. Tracking a tag is not a production approval either — a dev recipe still needs its own review before any non-local environment.
20
+
21
+ Every credential-bearing dev recipe binds its host port to `127.0.0.1` so apps on the same workstation can connect without publishing the service on every host interface. [source: https://docs.docker.com/reference/compose-file/services/#ports, version: Compose Specification, retrieved: 2026-07-15]
22
+
19
23
  ## Per-service prod transforms
20
24
 
21
25
  ### Postgres / MySQL / Redis (data tier)
@@ -42,7 +46,7 @@ The recipes default to **dev**. When `/dw-dockerize --prod` (or `--both`) emits
42
46
  - **Remove entirely from prod compose.** LocalStack is dev-only.
43
47
  - Replace with the real AWS endpoints; `AWS_ENDPOINT_URL` becomes unset (default to AWS).
44
48
 
45
- ### MailHog / Mailpit / smtp4dev
49
+ ### Mailpit / smtp4dev
46
50
 
47
51
  - **Remove entirely from prod compose.** Email-in-dev tools are dev-only.
48
52
  - Replace with a real provider via `SMTP_*` or API client (SendGrid, Resend, Postmark, SES).
@@ -80,6 +84,7 @@ The recipes default to **dev**. When `/dw-dockerize --prod` (or `--both`) emits
80
84
  - Ship secrets in committed files.
81
85
  - Use `:latest` tags.
82
86
  - Expose data-tier ports publicly.
83
- - Include MailHog / Mailpit / smtp4dev / LocalStack.
87
+ - Pin any image by digest (it blocks the base-image security rebuilds prod depends on).
88
+ - Include Mailpit / smtp4dev / LocalStack.
84
89
  - Skip healthchecks.
85
90
  - Forget to set non-root `USER` in the application Dockerfile.
@@ -8,14 +8,14 @@ elasticsearch:
8
8
  environment:
9
9
  discovery.type: single-node
10
10
  xpack.security.enabled: "true"
11
- ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:-elastic}
11
+ ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:?Set ELASTIC_PASSWORD in .env}
12
12
  ES_JAVA_OPTS: "-Xms512m -Xmx512m"
13
13
  ports:
14
- - "${ELASTICSEARCH_PORT:-9200}:9200"
14
+ - "127.0.0.1:${ELASTICSEARCH_PORT:-9200}:9200"
15
15
  volumes:
16
16
  - elasticsearch_data:/usr/share/elasticsearch/data
17
17
  healthcheck:
18
- test: ["CMD-SHELL", "curl -fsu elastic:${ELASTIC_PASSWORD:-elastic} http://localhost:9200/_cluster/health | grep -E 'green|yellow'"]
18
+ test: ["CMD-SHELL", "curl -fsu elastic:${ELASTIC_PASSWORD} http://localhost:9200/_cluster/health | grep -E 'green|yellow'"]
19
19
  interval: 10s
20
20
  timeout: 5s
21
21
  retries: 10
@@ -1,9 +1,9 @@
1
- # Mailpit — modern alternative to MailHog (active maintenance, full-text search, REST API)
1
+ # Mailpit — maintained email capture for local development
2
2
  # Env vars: MAILPIT_SMTP_PORT (default 1025), MAILPIT_UI_PORT (default 8025)
3
- # Application-side SMTP config: same as MailHog (drop-in replacement)
3
+ # Application-side SMTP config: SMTP_HOST=mailpit; SMTP_PORT=1025
4
4
 
5
5
  mailpit:
6
- image: axllent/mailpit:latest
6
+ image: axllent/mailpit:v1.30
7
7
  restart: unless-stopped
8
8
  ports:
9
9
  - "${MAILPIT_SMTP_PORT:-1025}:1025"
@@ -24,4 +24,4 @@ mailpit:
24
24
  # mailpit_data: {}
25
25
  #
26
26
  # Prod notes:
27
- # - DEV ONLY. Do not ship to prod. See mailhog.yml notes for prod email guidance.
27
+ # - DEV ONLY. Do not ship to prod; replace it with a real SMTP relay or transactional email API.
@@ -6,11 +6,11 @@ meilisearch:
6
6
  image: getmeili/meilisearch:v1.10
7
7
  restart: unless-stopped
8
8
  environment:
9
- MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:-meili-dev-key-change-me}
9
+ MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:?Set MEILI_MASTER_KEY in .env}
10
10
  MEILI_ENV: ${MEILI_ENV:-development}
11
11
  MEILI_NO_ANALYTICS: "true"
12
12
  ports:
13
- - "${MEILISEARCH_PORT:-7700}:7700"
13
+ - "127.0.0.1:${MEILISEARCH_PORT:-7700}:7700"
14
14
  volumes:
15
15
  - meilisearch_data:/meili_data
16
16
  healthcheck:
@@ -1,17 +1,21 @@
1
1
  # MinIO — S3-compatible object storage (dev replacement for AWS S3)
2
+ # Pinned, not `latest`: a floating tag makes a scan result describe what was pulled
3
+ # that day rather than what the next user runs. This is the newest release published
4
+ # to Docker Hub (2025-09-07); MinIO stopped pushing there afterwards, so `latest`
5
+ # currently resolves to the same image — the pin buys reproducibility, not fewer CVEs.
2
6
  # Env vars: MINIO_ROOT_USER, MINIO_ROOT_PASSWORD, MINIO_API_PORT (default 9000), MINIO_UI_PORT (default 9001)
3
7
  # Application-side: AWS_ENDPOINT_URL=http://minio:9000; AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER}; AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
4
8
 
5
9
  minio:
6
- image: minio/minio:latest
10
+ image: minio/minio:RELEASE.2025-09-07T16-13-09Z
7
11
  restart: unless-stopped
8
12
  command: server /data --console-address ":9001"
9
13
  environment:
10
14
  MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio}
11
- MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minio12345}
15
+ MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?Set MINIO_ROOT_PASSWORD in .env}
12
16
  ports:
13
- - "${MINIO_API_PORT:-9000}:9000"
14
- - "${MINIO_UI_PORT:-9001}:9001"
17
+ - "127.0.0.1:${MINIO_API_PORT:-9000}:9000"
18
+ - "127.0.0.1:${MINIO_UI_PORT:-9001}:9001"
15
19
  volumes:
16
20
  - minio_data:/data
17
21
  healthcheck:
@@ -7,16 +7,16 @@ mysql:
7
7
  restart: unless-stopped
8
8
  command: --default-authentication-plugin=mysql_native_password
9
9
  environment:
10
- MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
10
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?Set MYSQL_ROOT_PASSWORD in .env}
11
11
  MYSQL_USER: ${MYSQL_USER:-app}
12
- MYSQL_PASSWORD: ${MYSQL_PASSWORD:-app}
12
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD:?Set MYSQL_PASSWORD in .env}
13
13
  MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
14
14
  ports:
15
- - "${MYSQL_PORT:-3306}:3306"
15
+ - "127.0.0.1:${MYSQL_PORT:-3306}:3306"
16
16
  volumes:
17
17
  - mysql_data:/var/lib/mysql
18
18
  healthcheck:
19
- test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER:-app} -p${MYSQL_PASSWORD:-app}"]
19
+ test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER:-app} -p${MYSQL_PASSWORD}"]
20
20
  interval: 5s
21
21
  timeout: 3s
22
22
  retries: 10
@@ -7,10 +7,10 @@ postgres:
7
7
  restart: unless-stopped
8
8
  environment:
9
9
  POSTGRES_USER: ${POSTGRES_USER:-app}
10
- POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-app}
10
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
11
11
  POSTGRES_DB: ${POSTGRES_DB:-app}
12
12
  ports:
13
- - "${POSTGRES_PORT:-5432}:5432"
13
+ - "127.0.0.1:${POSTGRES_PORT:-5432}:5432"
14
14
  volumes:
15
15
  - postgres_data:/var/lib/postgresql/data
16
16
  healthcheck:
@@ -7,10 +7,10 @@ rabbitmq:
7
7
  restart: unless-stopped
8
8
  environment:
9
9
  RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-app}
10
- RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:-app}
10
+ RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:?Set RABBITMQ_PASSWORD in .env}
11
11
  ports:
12
- - "${RABBITMQ_PORT:-5672}:5672"
13
- - "${RABBITMQ_UI_PORT:-15672}:15672"
12
+ - "127.0.0.1:${RABBITMQ_PORT:-5672}:5672"
13
+ - "127.0.0.1:${RABBITMQ_UI_PORT:-15672}:15672"
14
14
  volumes:
15
15
  - rabbitmq_data:/var/lib/rabbitmq
16
16
  healthcheck:
@@ -3,11 +3,11 @@
3
3
  # Application-side: TYPESENSE_HOST=typesense; TYPESENSE_PORT=8108; TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
4
4
 
5
5
  typesense:
6
- image: typesense/typesense:0.27.1
6
+ image: typesense/typesense:30.2
7
7
  restart: unless-stopped
8
- command: --data-dir /data --api-key=${TYPESENSE_API_KEY:-typesense-dev-key} --enable-cors
8
+ command: --data-dir /data --api-key=${TYPESENSE_API_KEY:?Set TYPESENSE_API_KEY in .env} --enable-cors
9
9
  ports:
10
- - "${TYPESENSE_PORT:-8108}:8108"
10
+ - "127.0.0.1:${TYPESENSE_PORT:-8108}:8108"
11
11
  volumes:
12
12
  - typesense_data:/data
13
13
  healthcheck:
@@ -27,7 +27,11 @@ with these slots. Substitute them into the steps below — everything else is id
27
27
  |---|---|
28
28
  | `DISPATCH` | first-run command (background, streaming → audit log, `</dev/null`) |
29
29
  | `STREAM` | flag(s) that make it emit parseable streaming events (JSON/JSONL) |
30
- | `AUTO` | auto-approve / non-interactive flag (justified by the isolated worktree) |
30
+ | `MODEL` | flag that selects the model tier for this dispatch |
31
+ | `EFFORT` | flag that selects the reasoning budget for this dispatch |
32
+ | `AUTO` | auto-approve / non-interactive flag for a **WRITE** dispatch (justified by the isolated worktree) |
33
+ | `AUTO_READONLY` | plan/sandbox flag for a **READ-ONLY** dispatch (makes edits impossible; no worktree needed) |
34
+ | `NO_MCP` | flag that starts the CLI with no MCP servers (cold-start trim) |
31
35
  | `RESUME <id>` | re-run command that **continues the SAME session/context** by id |
32
36
  | `RESUME_LAST` | "resume the last session of this cwd" fallback |
33
37
  | `SESSION_ID` | how to obtain/fix the session-id (from the stream, a sidecar, or a flag) |
@@ -36,20 +40,30 @@ with these slots. Substitute them into the steps below — everything else is id
36
40
 
37
41
  If the command did not supply an adapter table, return `BLOCKED` — this skill is not invoked standalone.
38
42
 
39
- ## RULE (hard) — never the main thread / main checkout
40
- Run **only** inside a **dedicated git worktree** (off main). **NEVER** fire the CLI in the repo's **main checkout**
41
- (the root tree on the primary branch, e.g. `~/code/<project>` on `main`) — the CLI edits autonomously and that
42
- would corrupt the owner's active tree. If the target is the main checkout, **ABORT** (`BLOCKED`) and instruct:
43
- create/use a worktree first (`git worktree add ../<project>-<slug> -b <branch> main`). Mandatory check before
44
- running: `git worktree list` → the target is a secondary worktree (not the one flagged as main) **AND** the CLI's
45
- `cwd` is that worktree.
43
+ ## Dispatch modes — WRITE and READ-ONLY
44
+
45
+ Every dispatch declares its mode up front; it decides isolation, permission flags, and gate. **WRITE** edits and
46
+ commits → worktree mandatory, `AUTO`, one per worktree, full 0–10 gate. **READ-ONLY** reads/reviews/reports → no
47
+ worktree, `AUTO_READONLY`, freely parallel, score the report without a re-run gate. Per-mode detail:
48
+ `references/dispatch-tuning.md`.
49
+
50
+ <critical>RULE (hard) — a WRITE dispatch runs **only** inside a **dedicated git worktree** (off main). **NEVER** fire a WRITE dispatch in the repo's **main checkout** (the root tree on the primary branch, e.g. `~/code/<project>` on `main`) — the CLI edits autonomously and that would corrupt the owner's active tree. If the target is the main checkout, **ABORT** (`BLOCKED`) and instruct: create/use a worktree first (`git worktree add ../<project>-<slug> -b <branch> main`). Mandatory check before running: `git worktree list` → the target is a secondary worktree (not the one flagged as main) **AND** the CLI's `cwd` is that worktree.</critical>
51
+
52
+ <critical>A READ-ONLY dispatch MAY run in the main checkout, because the read-only flags make autonomous edits impossible — that is the entire reason the worktree rule exists. This is NOT a relaxation of the WRITE rule: if the dispatch can write, it is a WRITE dispatch and the worktree is mandatory. When in doubt, treat it as WRITE. Never pass an auto-approve flag on a dispatch declared READ-ONLY.</critical>
46
53
 
47
54
  ## Pre-flight (fail early > run in the wrong place)
48
55
  1. `<cli> --version` answers (CLI + credentials present). Missing → `BLOCKED`.
49
- 2. `git worktree list` — confirm the target worktree and that it is **not** the main checkout.
50
- 3. A **prompt/spec** exists (what the CLI receives). Common conventions: `.dw/spec/<slug>/codex-prompt.md`
56
+ 2. **Declare the mode** (WRITE or READ-ONLY) and pick the matching `AUTO`/`AUTO_READONLY` slot.
57
+ 3. WRITE only: `git worktree list` — confirm the target worktree and that it is **not** the main checkout.
58
+ 4. A **prompt/spec** exists (what the CLI receives). Common conventions: `.dw/spec/<slug>/codex-prompt.md`
51
59
  (dev-workflow), `PROMPT.md`, `TASK.md`, or a path the owner names. **Read it** — it is the scope/fence/gate.
52
60
 
61
+ ## Cold-start cost
62
+ A spawned CLI does not share the parent session's prompt cache and boots every configured MCP server first — on a
63
+ small task that startup can outweigh the task. **Default: pass the adapter's `NO_MCP` slot on every dispatch whose
64
+ prompt does not name an MCP capability.** Tool restriction, config trimming, and how to bind
65
+ `.dw/config/routing.json` are in `references/dispatch-tuning.md` — read it when a dispatch feels overpriced.
66
+
53
67
  ## Where to run — pick the VEHICLE (the main thread NEVER runs inline)
54
68
  The main thread **orchestrates**; execution goes to one of these:
55
69
  - **Workflow (PREFERRED)** — when there is a pipeline (CLI implements → evaluate 0–10 → **gate fan-out**). One
@@ -66,11 +80,12 @@ The main thread **orchestrates**; execution goes to one of these:
66
80
  ## Protocol
67
81
  1. **Resolve target.** Worktree + prompt path (from the user or inferred). Derive a `<slug>` (the spec slug or
68
82
  worktree tag) — it keys the durable audit log and the session sidecar.
69
- 2. **Model + effort — the parent SIZES the complexity and proposes.** Read the prompt and size it; **propose**
70
- model+effort with a one-line rationale (CLI-specific names live in the adapter table); the owner confirms or
71
- overrides; if told "you pick", decide by heuristic. Signals: number of tasks; fence breadth; sensitive surface
72
- (auth/security/tenant/migration/financial); "foundation/redesign/architectural" vs "mechanical fix/typo/docs";
73
- E2E/secure-audit in the gate. Start **one notch below the ceiling** and escalate on failure (see Escalation).
83
+ 2. **Model + effort — routing table first, size only what it misses.** `.dw/config/routing.json` is the default
84
+ source: commit type → tier (`by_commit_type`), raised if the fence matches `escalate_on_surface`, then tier →
85
+ `MODEL`/`EFFORT` for the brand. Costs no reasoning, reproducible. Absent, or no declared type? Size it: propose
86
+ with a one-line rationale (signals: task count, fence breadth, sensitive surface, architectural vs mechanical,
87
+ E2E/secure-audit in the gate); the owner confirms or overrides. Either way start **one notch below the
88
+ ceiling**. A tier that keeps escalating is a routing bug — name the entry that mis-sized it.
74
89
  3. **Set up the durable audit dir (OUTSIDE the worktree).** `<AUDIT>=<main-repo-or-home>/.dw/cli-run/` — it MUST
75
90
  survive `git worktree remove` (the worktree's `QA/` is wiped on merge). Files: `<AUDIT>/<slug>.log` (stream),
76
91
  `<AUDIT>/<slug>.session` (session-id sidecar), optionally `<AUDIT>/<slug>.last.md` (final message).
@@ -129,32 +144,21 @@ without reaching 9. Always show the score + a short per-criterion rationale —
129
144
  ceiling, **cite the specific gap** (the failing test, the file left out of fence), not just a label. Same
130
145
  evidence discipline as the five-axis rubric in `dw-review-rigor/references/self-eval-rubric.md`.
131
146
 
132
- ## Dual evaluation: the CLI auto-gates (max effort) → the parent re-gates (compares scores)
133
- The CLI **can't grade its own exam alone** — hence two layers. The CLI auto-gates cheaply (close to the work) and
134
- **the parent/orchestrator audits independently** and **compares the scores** (catches an inflated self-score). The
135
- parent is the dispatching session — possibly Claude itself; the re-gate is provider-neutral.
136
- 1. **CLI auto-gate (loop, MAX effort).** The prompt MUST instruct: after implementing, **run the SAME gate** and
137
- give a **self-score 0–10**; **fix and re-run while the self-score <9 or the gate isn't green**, at max effort.
138
- Stop at self-score ≥9 + green gate (or report `blockers`). The final report carries the self-score + per
139
- criterion.
140
- 2. **Parent re-gate (independent).** When the CLI declares pass, the parent/orchestrator **re-runs the SAME gate**
141
- (fan-out, prefer Workflow → `/workflows`) and gives its **own 0–10** — without trusting the self-score (a worker
142
- never signs off on its own exam, even when that worker is Claude).
143
- 3. **Compare + decide.** Parent ≥9 and small gap → **PASS** (ready for the owner's merge decision). Parent <9 OR a
144
- large gap (CLI overestimated) → re-execute: hand the gaps back via **session resume** (step 5, max effort) and
145
- repeat 1→2→3 until it converges. **The score that counts for acceptance is the parent's**; the self-score is
146
- signal + an inflation detector. Always record both scores + the gap in the Structured Return.
147
+ ## Dual evaluation: the worker auto-gates → the parent re-gates
148
+ A worker **can't grade its own exam alone** — hence two layers. (1) The worker loops at max effort until its own
149
+ score is ≥9 with a green gate. (2) The parent independently re-runs the SAME gate and scores it, without trusting
150
+ the self-score — **a worker never signs off on its own exam, even when that worker is Claude**. (3) The parent's
151
+ score is what counts; a large gap means the worker overestimated → hand the gaps back via session resume and
152
+ repeat. Record both scores and the gap. Prefer a **different brand** for the re-gate when one is installed; with
153
+ only one, re-gate in a separate dispatch at a different tier — never reuse the executing session. Full protocol:
154
+ `references/dispatch-tuning.md`.
147
155
 
148
156
  ## Graded escalation on failure
149
157
  If the score is low / the gate failed / the CLI didn't finish, **re-run the SAME task one notch up** — gradual, no
150
- giving up on the first stumble, no jumping to the top.
151
- - **Ladder (one at a time):** first **effort** low→medium→high→xhigh (adapter names them); exhausted, bump the
152
- **model** one tier and reset effort to high. Re-run and **re-score**.
153
- - **Continue vs restart:** coherent partial edits → **resume the session** (step 5, keeps context). Broken/dirty
154
- worktree → **reset first** (`git -C <worktree> reset --hard && git clean -fd`) and run fresh at the higher notch
155
- — don't stack error on error.
156
- - **Stop:** at **score ≥9** (ready for the gate) OR when **exhausted** (strongest model + max effort still below)
157
- → `BLOCKED` with evidence. Announce each notch; with autonomy, escalate to the ceiling yourself.
158
+ giving up on the first stumble, no jumping to the top. **Ladder:** effort first (`low`→`max`), then bump the model
159
+ one tier and reset effort. **Stop** at score ≥9, or `BLOCKED` with evidence when the ceiling is exhausted.
160
+ Announce each notch. Full ladder, continue-vs-restart rules, and how to read escalation as a routing-table signal:
161
+ `references/dispatch-tuning.md`.
158
162
 
159
163
  ## Detailed output → direct the next step
160
164
  The delivery must be **detailed enough for the parent to decide the next step** — not an opaque "done". When it
@@ -0,0 +1,85 @@
1
+ # Dispatch tuning — trim the cold start, bind the routing table
2
+
3
+ Read this when a dispatch feels disproportionately expensive for the size of the task, or when wiring
4
+ `.dw/config/routing.json` for the first time. The protocol in `SKILL.md` works without it.
5
+
6
+ ## Cold-start cost
7
+
8
+ A spawned CLI does not share the parent session's prompt cache and boots every configured MCP server before doing
9
+ any work. On a small task that startup can cost more than the task itself. This is the one real advantage an
10
+ in-session subagent keeps over a spawned CLI — so trim the spawn until the gap stops mattering.
11
+
12
+ | | Claude | Codex |
13
+ |---|---|---|
14
+ | No MCP servers | `--strict-mcp-config` (with no `--mcp-config`) | `-c mcp_servers='{}'` |
15
+ | Drop all user config | — | `--ignore-user-config` (blunt: also drops model defaults, so `MODEL` + `EFFORT` become mandatory) |
16
+ | Restrict tools | `--tools ""` disables all; `--allowedTools <list>` for a subset | `--sandbox read-only` |
17
+
18
+ **Default:** pass the MCP kill switch on every dispatch whose prompt does not name an MCP capability. If the
19
+ prompt needs docs lookup (`context7`) or browser automation (`playwright`), keep them and say so in the dispatch
20
+ rationale.
21
+
22
+ Verified against `claude` 2.1.220 and `codex-cli` 0.144.4. Re-check with `claude --help` / `codex exec --help`
23
+ after a CLI upgrade — these flags are not covered by either project's stability guarantees.
24
+
25
+ ## Binding `.dw/config/routing.json`
26
+
27
+ Resolution order for a dispatch:
28
+
29
+ 1. Read the task's Conventional-Commit type (already in `tasks.md` and in the atomic commit subject).
30
+ 2. `by_commit_type[<type>]` → tier name.
31
+ 3. If the fence or the task title matches any `escalate_on_surface.patterns`, override the tier upward.
32
+ 4. `tiers[<tier>][<brand>]` → concrete `MODEL` + `EFFORT`.
33
+ 5. Substitute into the adapter's `MODEL`/`EFFORT` slots.
34
+
35
+ If the file is absent, or the task has no declared type, fall back to the sizing heuristic in `SKILL.md` step 2.
36
+
37
+ `routing.json` is seeded once on init and never reconciled on update, because model availability differs per
38
+ account. Treat the shipped model ids as defaults to verify, not as a guarantee of access — a dispatch that fails
39
+ with an unknown-model error is a routing-table edit, not a protocol bug.
40
+
41
+ ## Dual evaluation in full
42
+
43
+ The CLI auto-gates cheaply (close to the work); the parent/orchestrator audits independently and compares the
44
+ scores, which is what catches an inflated self-score. The parent is the dispatching session — possibly Claude
45
+ itself; the re-gate is provider-neutral.
46
+
47
+ 1. **Worker auto-gate (loop, MAX effort).** The prompt MUST instruct: after implementing, **run the SAME gate**
48
+ and give a **self-score 0–10**; **fix and re-run while the self-score <9 or the gate isn't green**, at max
49
+ effort. Stop at self-score ≥9 + green gate (or report `blockers`). The final report carries the self-score plus
50
+ a per-criterion breakdown.
51
+ 2. **Parent re-gate (independent).** When the worker declares pass, the parent **re-runs the SAME gate** (fan-out,
52
+ prefer Workflow → `/workflows`) and gives its **own 0–10**, without trusting the self-score. Prefer a different
53
+ brand for this step when both are installed — a second vendor fails differently, which is the whole point of
54
+ the second layer. With one brand installed, re-gate in a separate dispatch at a different tier.
55
+ 3. **Compare + decide.** Parent ≥9 and small gap → **PASS** (ready for the owner's merge decision). Parent <9 OR a
56
+ large gap (worker overestimated) → re-execute: hand the gaps back via **session resume** at max effort and
57
+ repeat 1→2→3 until it converges. **The score that counts for acceptance is the parent's**; the self-score is
58
+ signal plus an inflation detector. Always record both scores and the gap in the Structured Return.
59
+
60
+ ## The escalation ladder in full
61
+
62
+ If the score is low, the gate failed, or the CLI didn't finish, re-run the SAME task one notch up — gradual, no
63
+ giving up on the first stumble, no jumping to the top.
64
+
65
+ - **Ladder (one at a time):** first **effort** `low`→`medium`→`high`→`xhigh`(→`max` on Claude; the adapter names
66
+ the levels it supports); once exhausted, bump the **model** one tier and reset effort to `high`. Re-run and
67
+ **re-score**.
68
+ - **Continue vs restart:** coherent partial edits → **resume the session** (keeps its context and the files it
69
+ already touched). Broken or dirty worktree → **reset first**
70
+ (`git -C <worktree> reset --hard && git clean -fd`) and run fresh at the higher notch. Don't stack error on
71
+ error.
72
+ - **Stop:** at **score ≥9** (ready for the owner's gate), OR when **exhausted** (strongest model at max effort
73
+ still below the bar) → `BLOCKED` with evidence. Announce each notch; with autonomy, escalate to the ceiling
74
+ yourself rather than asking at every step.
75
+
76
+ ## Reading escalation as a routing signal
77
+
78
+ A task that routinely needs two or more notches above what the table assigned is telling you the table is wrong
79
+ for that commit type. Report which
80
+ entry mis-sized it in the Structured Return, so the next edit to `routing.json` is grounded in a real run rather
81
+ than a guess.
82
+
83
+ The reverse also matters and is easier to miss: a tier that never escalates may be over-provisioned. If `feat`
84
+ work consistently lands at score ≥9 on the first try, try the tier below it before assuming the current one is
85
+ load-bearing.
@@ -80,7 +80,7 @@ Detail in `references/atomic-commits.md` (deviation entry format) and `reference
80
80
  Every task ends with exactly one git commit:
81
81
 
82
82
  ```
83
- feat(<scope>): <task title> (RF-XX)
83
+ feat(<scope>): <task title> (#<task-id>)
84
84
 
85
85
  <one-line summary of what this commit delivers>
86
86
 
@@ -89,9 +89,11 @@ feat(<scope>): <task title> (RF-XX)
89
89
  - Tests added/updated: <list>
90
90
  - Deviations: <link to deviations.md entry, if any>
91
91
 
92
- Closes RF-XX (partial — see tasks.md).
92
+ Closes <REQ-ID> (partial — see tasks.md).
93
93
  ```
94
94
 
95
+ `<REQ-ID>` is copied verbatim from `tasks.md` — `FR-N.M` in English projects, `RF-N.M` in Portuguese ones. This skill is shared by both; never hardcode a prefix.
96
+
95
97
  The commit message format is consistent across waves so `/dw-generate-pr` can build a clean PR body. See `references/atomic-commits.md`.
96
98
 
97
99
  ## De-Sloppify Pass (optional cleanup task)
@@ -88,7 +88,7 @@ For each wave:
88
88
  Format (per `references/atomic-commits.md`):
89
89
 
90
90
  ```
91
- <type>(<scope>): <task title> (RF-XX)
91
+ <type>(<scope>): <task title> (#<task-id>)
92
92
 
93
93
  <one-line summary>
94
94
 
@@ -97,9 +97,11 @@ Format (per `references/atomic-commits.md`):
97
97
  - Tests added/updated: <list>
98
98
  - Deviations: <link or "none">
99
99
 
100
- Closes RF-XX (partial — full close on tasks.md completion).
100
+ Closes <REQ-ID> (partial — full close on tasks.md completion).
101
101
  ```
102
102
 
103
+ `<REQ-ID>` is copied verbatim from `tasks.md` (`FR-N.M` in English projects, `RF-N.M` in Portuguese ones) — never assume a prefix.
104
+
103
105
  Type from Conventional Commits (`feat`, `fix`, `refactor`, `test`, `docs`, `chore`). Scope from task domain. Run `git add -A && git commit` once.
104
106
 
105
107
  ### Step 5: Deviation handling (when implementation diverges from plan)
@@ -50,7 +50,7 @@ Then verify each level against the actual plan files.
50
50
 
51
51
  - `prd_path` (from spawn prompt): `.dw/spec/prd-<slug>/`
52
52
  - Reads:
53
- - `<prd_path>/prd.md` — the goal, requirements (RF-XX), acceptance criteria
53
+ - `<prd_path>/prd.md` — the goal, requirements (`<REQ-ID>`, see Dimension 1), acceptance criteria
54
54
  - `<prd_path>/techspec.md` — architecture decisions
55
55
  - `<prd_path>/tasks.md` — the plan to verify
56
56
  - `<prd_path>/<NN>_task.md` — per-task detail
@@ -62,15 +62,17 @@ Then verify each level against the actual plan files.
62
62
 
63
63
  ### Dimension 1: Requirement Coverage
64
64
 
65
- **Question:** Does every PRD requirement (RF-XX) have at least one task addressing it?
65
+ <critical>Requirement IDs are language-dependent. English projects number them `FR-N.M`; Portuguese projects `RF-N.M`. This skill is shared by both — NEVER hardcode a prefix. Read the IDs verbatim from `prd.md` and match those exact strings in `tasks.md`. `<REQ-ID>` below stands for whichever form the project uses.</critical>
66
+
67
+ **Question:** Does every PRD requirement have at least one task addressing it?
66
68
 
67
69
  **Process:**
68
- 1. Extract every numbered requirement from `prd.md`
69
- 2. For each RF-XX, search `tasks.md` for tasks tagged with that RF
70
+ 1. Extract every numbered requirement ID from `prd.md` verbatim (`FR-1.1`, `RF-1.1`, …)
71
+ 2. For each `<REQ-ID>`, search `tasks.md` for tasks tagged with that exact ID
70
72
  3. List uncovered requirements
71
73
 
72
- **Pass:** every RF has at least one task.
73
- **Revise:** ≥1 RF has no task; the planner missed it.
74
+ **Pass:** every requirement has at least one task.
75
+ **Revise:** ≥1 requirement has no task; the planner missed it.
74
76
  **Block:** the PRD has no requirements at all (plan can't verify).
75
77
 
76
78
  ### Dimension 2: Task Completeness
@@ -174,7 +176,7 @@ After running all 6 dimensions:
174
176
 
175
177
  ### REVISE — Dimension 1: Requirement Coverage
176
178
 
177
- - **RF-04** ("user can reset password via email link") has no task. Suggested: add task between 05 and 06.
179
+ - **FR-4.1** ("user can reset password via email link") has no task. Suggested: add task between 05 and 06.
178
180
 
179
181
  ### REVISE — Dimension 2: Task Completeness
180
182
 
@@ -7,7 +7,7 @@ Every task in a phase commits exactly once. This drives traceability (a task's d
7
7
  Strict format. No deviations.
8
8
 
9
9
  ```
10
- <type>(<scope>): <task title> (RF-XX)
10
+ <type>(<scope>): <task title> (#<task-id>)
11
11
 
12
12
  <one-line summary>
13
13
 
@@ -16,9 +16,11 @@ Strict format. No deviations.
16
16
  - Tests added/updated: <comma-separated list, or "none">
17
17
  - Deviations: <link to deviations.md entry, or "none">
18
18
 
19
- Closes RF-XX (partial — full close on tasks.md completion).
19
+ Closes <REQ-ID> (partial — full close on tasks.md completion).
20
20
  ```
21
21
 
22
+ <critical>`<REQ-ID>` is the requirement ID exactly as `prd.md` and `tasks.md` write it. English projects use `FR-N.M`; Portuguese projects use `RF-N.M`. This skill is shared by both — copy the ID verbatim, never assume a prefix.</critical>
23
+
22
24
  ### Field rules
23
25
 
24
26
  **`<type>`** — Conventional Commits:
@@ -36,7 +38,7 @@ Closes RF-XX (partial — full close on tasks.md completion).
36
38
 
37
39
  **`<task title>`** — copy from `tasks.md` task line. Imperative, concise. "Add login endpoint", not "Added login endpoint" or "Adding login endpoint".
38
40
 
39
- **`(RF-XX)`** — the requirement this task closes. If the task contributes to multiple, list the primary; mention the others in the body.
41
+ **`(#<task-id>)`** — the task ID from `tasks.md` (`#1.0`, `#2.3`). This is what makes the commit findable from the task index — `/dw-run` writes the resulting SHA back into the `tasks.md` Commit column.
40
42
 
41
43
  **`<one-line summary>`** — one sentence answering "what does this commit deliver?". Different from the title (which is the task title); this is the OUTCOME.
42
44
 
@@ -44,12 +46,14 @@ Closes RF-XX (partial — full close on tasks.md completion).
44
46
 
45
47
  **Deviations link** — `.dw/spec/prd-<slug>/deviations.md#deviation-03-1` if the task triggered a Rule 1 or 2 deviation.
46
48
 
47
- **Closes line** — `Closes RF-XX (partial — full close on tasks.md completion)` because one task usually doesn't fully close a requirement; the whole phase does. Final task in the phase changes "(partial — ...)" to "(full)".
49
+ **Closes line** — `Closes <REQ-ID> (partial — full close on tasks.md completion)` because one task usually doesn't fully close a requirement; the whole phase does. Final task in the phase changes "(partial — ...)" to "(full)". If the task closes more than one requirement, list them comma-separated.
48
50
 
49
51
  ## Examples
50
52
 
53
+ Both examples come from an English project (`FR-`); a Portuguese project writes `RF-` in the same slots.
54
+
51
55
  ```
52
- feat(auth): wire JWT middleware to all /api/* routes (RF-04)
56
+ feat(auth): wire JWT middleware to all /api/* routes (#4.0)
53
57
 
54
58
  Authenticated routes now reject requests without valid Bearer tokens.
55
59
 
@@ -58,11 +62,11 @@ Authenticated routes now reject requests without valid Bearer tokens.
58
62
  - Tests added/updated: src/middleware/auth.test.ts (12 cases — happy path, expired, malformed, missing)
59
63
  - Deviations: none
60
64
 
61
- Closes RF-04 (partial — full close on tasks.md completion).
65
+ Closes FR-4.1 (partial — full close on tasks.md completion).
62
66
  ```
63
67
 
64
68
  ```
65
- test(orders): add integration tests for order creation flow (RF-07)
69
+ test(orders): add integration tests for order creation flow (#7.0)
66
70
 
67
71
  Covers happy path, payment failure, inventory mismatch.
68
72
 
@@ -71,7 +75,7 @@ Covers happy path, payment failure, inventory mismatch.
71
75
  - Tests added/updated: tests/integration/orders.test.ts (8 cases)
72
76
  - Deviations: .dw/spec/prd-checkout-v2/deviations.md#deviation-08-1
73
77
 
74
- Closes RF-07 (partial — full close on tasks.md completion).
78
+ Closes FR-7.2 (partial — full close on tasks.md completion).
75
79
  ```
76
80
 
77
81
  ## Verification before commit
@@ -10,12 +10,14 @@ Mid-execution discovery of plan flaws is expensive: context burned, partial comm
10
10
 
11
11
  ### 1. Requirement Coverage
12
12
 
13
- **Goal:** every PRD requirement (RF-XX) has at least one task addressing it.
13
+ **Goal:** every PRD requirement (`<REQ-ID>`) has at least one task addressing it.
14
+
15
+ <critical>`<REQ-ID>` is language-dependent: `FR-N.M` in English projects, `RF-N.M` in Portuguese ones. This skill is shared by both — extract the IDs verbatim from `prd.md` and match those exact strings downstream; never hardcode a prefix.</critical>
14
16
 
15
17
  **Steps:**
16
- 1. Extract numbered requirements from `prd.md`. Pattern: `### RF-NN` or `**RF-NN:**` or numbered list under "Functional Requirements".
17
- 2. Build `Set<RF>` of expected requirements.
18
- 3. Scan `tasks.md` and `<NN>_task.md` files for `Closes RF-XX` / `RF: RF-XX` / `Requirement: RF-XX` markers.
18
+ 1. Extract numbered requirements from `prd.md`. Pattern: `### <REQ-ID>` or `**<REQ-ID>:**` or numbered list under "Functional Requirements" / "Requisitos Funcionais".
19
+ 2. Build `Set<REQ-ID>` of expected requirements.
20
+ 3. Scan `tasks.md` and `<NN>_task.md` files for `Closes <REQ-ID>` / the `FRs`/`RFs` table column / `Requirement: <REQ-ID>` markers.
19
21
  4. Compute `uncovered = expected - addressed`.
20
22
 
21
23
  **Pass:** `uncovered` is empty.
@@ -1,23 +0,0 @@
1
- # MailHog — DEV-ONLY email capture (NEVER sends real mail)
2
- # This is the dev-workflow DEFAULT for email-in-dev so the project cannot accidentally email real users.
3
- # Env vars: MAILHOG_SMTP_PORT (default 1025), MAILHOG_UI_PORT (default 8025)
4
- # Application-side SMTP config:
5
- # SMTP_HOST=mailhog ; SMTP_PORT=1025 ; SMTP_USER= ; SMTP_PASSWORD= ; SMTP_SECURE=false
6
-
7
- mailhog:
8
- image: mailhog/mailhog:latest
9
- restart: unless-stopped
10
- ports:
11
- - "${MAILHOG_SMTP_PORT:-1025}:1025"
12
- - "${MAILHOG_UI_PORT:-8025}:8025"
13
- healthcheck:
14
- test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:8025"]
15
- interval: 10s
16
- timeout: 3s
17
- retries: 5
18
- start_period: 5s
19
-
20
- # Prod notes:
21
- # - MailHog must NEVER ship to prod. Replace with a real SMTP relay or transactional email API
22
- # (SendGrid, Resend, Postmark, SES) when promoting docker-compose.dev.yml to prod.
23
- # - Open the UI at http://localhost:8025 to inspect captured mail; nothing leaves the host.