@brunosps00/dev-workflow 1.7.1 → 2.1.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 (114) hide show
  1. package/.claude-plugin/marketplace.json +1 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +305 -0
  4. package/README.md +7 -5
  5. package/lib/agents.js +6 -1
  6. package/lib/build-plugin.js +4 -1
  7. package/lib/constants.js +4 -2
  8. package/lib/init.js +14 -0
  9. package/lib/install-state.js +8 -4
  10. package/lib/validate.js +21 -0
  11. package/package.json +2 -1
  12. package/scaffold/agents/core/dw-build-fixer.md +0 -1
  13. package/scaffold/agents/core/dw-code-explorer.md +0 -1
  14. package/scaffold/agents/core/dw-code-reviewer.md +0 -1
  15. package/scaffold/agents/core/dw-planner.md +0 -1
  16. package/scaffold/agents/core/dw-test-author.md +0 -1
  17. package/scaffold/agents/csharp/dw-csharp-reviewer.md +0 -1
  18. package/scaffold/agents/csharp/dw-dotnet-build-fixer.md +0 -1
  19. package/scaffold/agents/frontend/dw-qa-runner.md +0 -1
  20. package/scaffold/agents/python/dw-python-build-fixer.md +0 -1
  21. package/scaffold/agents/python/dw-python-reviewer.md +0 -1
  22. package/scaffold/agents/rust/dw-rust-build-fixer.md +0 -1
  23. package/scaffold/agents/rust/dw-rust-reviewer.md +0 -1
  24. package/scaffold/agents/security/dw-security-reviewer.md +0 -1
  25. package/scaffold/agents/security/dw-silent-failure-hunter.md +0 -1
  26. package/scaffold/agents/typescript/dw-typescript-build-fixer.md +0 -1
  27. package/scaffold/agents/typescript/dw-typescript-reviewer.md +0 -1
  28. package/scaffold/config/routing.json +60 -0
  29. package/scaffold/en/commands/dw-brainstorm.md +12 -8
  30. package/scaffold/en/commands/dw-bugfix.md +4 -2
  31. package/scaffold/en/commands/dw-claude-run.md +5 -1
  32. package/scaffold/en/commands/dw-codex-run.md +5 -1
  33. package/scaffold/en/commands/dw-dockerize.md +14 -7
  34. package/scaffold/en/commands/dw-generate-pr.md +13 -0
  35. package/scaffold/en/commands/dw-new-project.md +14 -13
  36. package/scaffold/en/commands/dw-plan.md +2 -2
  37. package/scaffold/en/commands/dw-refactor.md +1 -0
  38. package/scaffold/en/commands/dw-review.md +33 -2
  39. package/scaffold/en/commands/dw-run.md +2 -1
  40. package/scaffold/en/commands/dw-triage.md +220 -0
  41. package/scaffold/en/templates/idea-onepager.md +28 -0
  42. package/scaffold/en/templates/project-onepager.md +6 -5
  43. package/scaffold/en/templates/task-template.md +2 -1
  44. package/scaffold/en/templates/tasks-template.md +11 -8
  45. package/scaffold/en/templates/triage-needs-info-template.md +23 -0
  46. package/scaffold/en/templates/triage-out-of-scope-template.md +36 -0
  47. package/scaffold/en/templates/triage-record-template.md +88 -0
  48. package/scaffold/pt-br/commands/dw-autopilot.md +81 -81
  49. package/scaffold/pt-br/commands/dw-brainstorm.md +12 -9
  50. package/scaffold/pt-br/commands/dw-bugfix.md +4 -2
  51. package/scaffold/pt-br/commands/dw-claude-run.md +23 -19
  52. package/scaffold/pt-br/commands/dw-codex-run.md +22 -18
  53. package/scaffold/pt-br/commands/dw-context-budget.md +10 -10
  54. package/scaffold/pt-br/commands/dw-copilot-run.md +20 -20
  55. package/scaffold/pt-br/commands/dw-dockerize.md +106 -99
  56. package/scaffold/pt-br/commands/dw-find-skills.md +52 -52
  57. package/scaffold/pt-br/commands/dw-generate-pr.md +13 -0
  58. package/scaffold/pt-br/commands/dw-goal.md +41 -41
  59. package/scaffold/pt-br/commands/dw-harness-audit.md +11 -11
  60. package/scaffold/pt-br/commands/dw-intel.md +70 -70
  61. package/scaffold/pt-br/commands/dw-learn.md +24 -24
  62. package/scaffold/pt-br/commands/dw-new-project.md +93 -92
  63. package/scaffold/pt-br/commands/dw-open-design.md +53 -53
  64. package/scaffold/pt-br/commands/dw-opportunities.md +45 -45
  65. package/scaffold/pt-br/commands/dw-pause.md +39 -39
  66. package/scaffold/pt-br/commands/dw-plan.md +3 -3
  67. package/scaffold/pt-br/commands/dw-refactor.md +31 -30
  68. package/scaffold/pt-br/commands/dw-resume.md +22 -22
  69. package/scaffold/pt-br/commands/dw-review.md +33 -2
  70. package/scaffold/pt-br/commands/dw-run.md +2 -1
  71. package/scaffold/pt-br/commands/dw-skill-health.md +10 -10
  72. package/scaffold/pt-br/commands/dw-subtask-complete.md +4 -4
  73. package/scaffold/pt-br/commands/dw-subtask-start.md +4 -4
  74. package/scaffold/pt-br/commands/dw-triage.md +220 -0
  75. package/scaffold/pt-br/templates/bugfix-summary-template.md +20 -20
  76. package/scaffold/pt-br/templates/concerns-template.md +20 -20
  77. package/scaffold/pt-br/templates/idea-onepager.md +28 -0
  78. package/scaffold/pt-br/templates/project-onepager.md +35 -34
  79. package/scaffold/pt-br/templates/state-template.md +13 -13
  80. package/scaffold/pt-br/templates/task-template.md +2 -1
  81. package/scaffold/pt-br/templates/tasks-template.md +11 -8
  82. package/scaffold/pt-br/templates/triage-needs-info-template.md +23 -0
  83. package/scaffold/pt-br/templates/triage-out-of-scope-template.md +36 -0
  84. package/scaffold/pt-br/templates/triage-record-template.md +88 -0
  85. package/scaffold/scripts/hooks/git-guardrails.mjs +41 -24
  86. package/scaffold/skills/docker-compose-recipes/SKILL.md +2 -4
  87. package/scaffold/skills/docker-compose-recipes/references/compose-composition.md +2 -2
  88. package/scaffold/skills/docker-compose-recipes/references/env-conventions.md +16 -9
  89. package/scaffold/skills/docker-compose-recipes/references/healthcheck-patterns.md +0 -1
  90. package/scaffold/skills/docker-compose-recipes/references/prod-vs-dev.md +7 -2
  91. package/scaffold/skills/docker-compose-recipes/services/elasticsearch.yml +3 -3
  92. package/scaffold/skills/docker-compose-recipes/services/meilisearch.yml +2 -2
  93. package/scaffold/skills/docker-compose-recipes/services/minio.yml +8 -4
  94. package/scaffold/skills/docker-compose-recipes/services/mysql.yml +4 -4
  95. package/scaffold/skills/docker-compose-recipes/services/postgres.yml +2 -2
  96. package/scaffold/skills/docker-compose-recipes/services/rabbitmq.yml +3 -3
  97. package/scaffold/skills/docker-compose-recipes/services/typesense.yml +3 -3
  98. package/scaffold/skills/dw-cli-run/SKILL.md +42 -38
  99. package/scaffold/skills/dw-cli-run/references/dispatch-tuning.md +85 -0
  100. package/scaffold/skills/dw-debug-protocol/SKILL.md +2 -0
  101. package/scaffold/skills/dw-debug-protocol/references/six-step-triage.md +60 -1
  102. package/scaffold/skills/dw-execute-phase/SKILL.md +4 -2
  103. package/scaffold/skills/dw-execute-phase/agents/executor.md +4 -2
  104. package/scaffold/skills/dw-execute-phase/agents/plan-checker.md +9 -7
  105. package/scaffold/skills/dw-execute-phase/references/atomic-commits.md +12 -8
  106. package/scaffold/skills/dw-execute-phase/references/plan-verification.md +6 -4
  107. package/scaffold/skills/dw-execute-phase/references/wave-coordination.md +14 -14
  108. package/scaffold/skills/dw-grilling/SKILL.md +17 -7
  109. package/scaffold/skills/dw-grilling/references/decision-tree.md +30 -7
  110. package/scaffold/skills/dw-simplification/references/deep-modules.md +50 -0
  111. package/scaffold/skills/dw-testing-discipline/SKILL.md +3 -1
  112. package/scaffold/skills/dw-testing-discipline/references/tdd-loop.md +83 -0
  113. package/scaffold/skills/docker-compose-recipes/services/mailhog.yml +0 -23
  114. package/scaffold/skills/docker-compose-recipes/services/postgres-pgvector.yml +0 -31
@@ -14,13 +14,17 @@
14
14
  * error allows the command, so a hook bug never blocks the user.
15
15
  */
16
16
 
17
- const DANGEROUS = [
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+
20
+ export const DANGEROUS = [
18
21
  { re: /\bgit\s+push\b[^\n|&;]*(--force\b|--force-with-lease\b|\s-f\b)/, why: 'force push rewrites remote history' },
19
22
  { re: /\bgit\s+push\b[^\n|&;]*(--delete\b|\s-d\b|\s:\S)/, why: 'deletes a remote branch' },
20
23
  { re: /\bgit\s+reset\b[^\n|&;]*--hard\b/, why: 'hard reset discards uncommitted work and rewrites the branch' },
21
24
  { re: /\bgit\s+clean\b[^\n|&;]*-[a-z]*f/, why: 'git clean -f permanently deletes untracked files' },
22
25
  { re: /\bgit\s+branch\b[^\n|&;]*\s-D\b/, why: 'force-deletes a branch that may be unmerged' },
23
26
  { re: /\bgit\s+checkout\b[^\n|&;]*\s(--\s+\.|\.\s*$)/, why: 'discards all local changes in the working tree' },
27
+ { re: /\bgit\s+restore\b[^\n|&;]*\s(--\s+)?\.(?=\s*(?:$|[|&;]))/, why: 'discards all local changes in the working tree' },
24
28
  ];
25
29
 
26
30
  function readStdin() {
@@ -38,42 +42,55 @@ function allow() {
38
42
  process.exit(0);
39
43
  }
40
44
 
41
- function deny(reason) {
42
- process.stdout.write(
43
- JSON.stringify({
44
- hookSpecificOutput: {
45
- hookEventName: 'PreToolUse',
46
- permissionDecision: 'deny',
47
- permissionDecisionReason: reason,
48
- },
49
- }) + '\n',
50
- );
51
- process.exit(0);
52
- }
53
-
54
- async function main() {
45
+ export function evaluatePayload(input) {
55
46
  let payload;
56
47
  try {
57
- payload = JSON.parse(await readStdin());
48
+ payload = JSON.parse(input);
58
49
  } catch {
59
- return allow(); // fail open
50
+ return null; // fail open
60
51
  }
61
52
 
62
53
  const command = payload && payload.tool_input && typeof payload.tool_input.command === 'string'
63
54
  ? payload.tool_input.command
64
55
  : '';
65
- if (!command) return allow();
56
+ if (!command) return null;
66
57
 
67
58
  for (const rule of DANGEROUS) {
68
59
  if (rule.re.test(command)) {
69
- return deny(
70
- `Blocked by dev-workflow git guardrails: ${rule.why}. ` +
71
- `If this is intentional, run it yourself outside the agent, or adjust .claude/settings.json hooks.`,
72
- );
60
+ return {
61
+ hookSpecificOutput: {
62
+ hookEventName: 'PreToolUse',
63
+ permissionDecision: 'deny',
64
+ permissionDecisionReason:
65
+ `Blocked by dev-workflow git guardrails: ${rule.why}. ` +
66
+ `If this is intentional, run it yourself outside the agent, or adjust .claude/settings.json hooks.`,
67
+ },
68
+ };
73
69
  }
74
70
  }
75
71
 
76
- return allow();
72
+ return null;
73
+ }
74
+
75
+ function deny(reason) {
76
+ process.stdout.write(
77
+ JSON.stringify({
78
+ hookSpecificOutput: {
79
+ hookEventName: 'PreToolUse',
80
+ permissionDecision: 'deny',
81
+ permissionDecisionReason: reason,
82
+ },
83
+ }) + '\n',
84
+ );
85
+ process.exit(0);
86
+ }
87
+
88
+ async function main() {
89
+ const result = evaluatePayload(await readStdin());
90
+ if (!result) return allow();
91
+ return deny(result.hookSpecificOutput.permissionDecisionReason);
77
92
  }
78
93
 
79
- main().catch(() => allow());
94
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
95
+ main().catch(() => allow());
96
+ }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: docker-compose-recipes
3
- description: Use for docker-compose service blocks (postgres, pgvector, redis, mailpit, minio, meilisearch, jaeger, traefik). Invoked by /dw-new-project, /dw-dockerize, or when composing dev/prod compose files.
3
+ description: Use for docker-compose service blocks (postgres, redis, mailpit, minio, meilisearch, jaeger, traefik). Invoked by /dw-new-project, /dw-dockerize, or when composing dev/prod compose files.
4
4
  allowed-tools:
5
5
  - Read
6
6
  - Write
@@ -49,14 +49,12 @@ Each row points to `services/<name>.yml`.
49
49
  | Service | Use | Default port(s) | Recipe |
50
50
  |---------|-----|-----------------|--------|
51
51
  | Postgres 16 (alpine) | Relational DB | `5432` | `services/postgres.yml` |
52
- | PostgreSQL 18 + pgvector 0.8.2 | Relational + vector DB | `5432` | `services/postgres-pgvector.yml` |
53
52
  | MySQL 8 | Relational DB | `3306` | `services/mysql.yml` |
54
53
  | Redis 7 (alpine) | Cache, pub/sub, BullMQ backend | `6379` | `services/redis.yml` |
55
54
  | Memcached 1.6 (alpine) | Cache | `11211` | `services/memcached.yml` |
56
55
  | RabbitMQ 3 (management-alpine) | Message broker + UI | `5672`, UI `15672` | `services/rabbitmq.yml` |
57
56
  | LocalStack | AWS-compatible local (S3, SQS, SNS, DynamoDB) | `4566` | `services/localstack.yml` |
58
57
  | Mailpit | Default email-in-dev (capture only, never sends) | SMTP `1025`, UI `8025` | `services/mailpit.yml` |
59
- | MailHog | Legacy email capture alternative | SMTP `1025`, UI `8025` | `services/mailhog.yml` |
60
58
  | smtp4dev | Windows-friendly SMTP capture | SMTP `2525`, UI `5000` | `services/smtp4dev.yml` |
61
59
  | MinIO | S3-compatible local | API `9000`, UI `9001` | `services/minio.yml` |
62
60
  | Meilisearch | Search engine (lightweight) | `7700` | `services/meilisearch.yml` |
@@ -82,7 +80,7 @@ Each row points to `services/<name>.yml`.
82
80
 
83
81
  ## Inspired by
84
82
 
85
- Hand-curated by dev-workflow. Service defaults follow upstream documentation (Docker Hub `postgres`, `pgvector/pgvector`, `redis`, `axllent/mailpit`, `minio/minio`, `getmeili/meilisearch`, `jaegertracing/all-in-one`, `traefik`). Healthcheck patterns adapted from `docker-library/healthcheck` and the official compose docs.
83
+ Hand-curated by dev-workflow. Service defaults follow upstream documentation (Docker Hub `postgres`, `redis`, `axllent/mailpit`, `minio/minio`, `getmeili/meilisearch`, `jaegertracing/all-in-one`, `traefik`). Healthcheck patterns adapted from `docker-library/healthcheck` and the official compose docs.
86
84
 
87
85
  ## Structured Return
88
86
 
@@ -4,7 +4,7 @@ This file describes how `/dw-new-project` and `/dw-dockerize` merge the standalo
4
4
 
5
5
  ## The merge algorithm
6
6
 
7
- 1. **Pick the selected services.** From the interview answers (or from stack detection in `dw-dockerize`), get the list of services to include. Example: `[postgres-pgvector, mailpit, minio]`.
7
+ 1. **Pick the selected services.** From the interview answers (or from stack detection in `dw-dockerize`), get the list of services to include. Example: `[postgres, mailpit, minio]`.
8
8
  2. **Read each `services/<name>.yml`** as a standalone fragment. Each file declares ONE top-level service block (e.g., `postgres:`) plus comments at the bottom describing the volumes block to merge.
9
9
  3. **Concatenate under a single `services:` map.** Indent each block under `services:` in the final file. Order does not matter for compose, but for readability sort by tier: storage → cache/queue → search → email → observability → proxy.
10
10
  4. **Collect the volumes.** Each recipe's comments list the named volumes it needs. Aggregate them under a top-level `volumes:` block. Example:
@@ -42,7 +42,7 @@ This file describes how `/dw-new-project` and `/dw-dockerize` merge the standalo
42
42
  services:
43
43
  # --- storage tier ---
44
44
  postgres:
45
- # ... contents from services/postgres-pgvector.yml
45
+ # ... contents from services/postgres.yml
46
46
 
47
47
  # --- cache tier ---
48
48
  redis:
@@ -1,12 +1,18 @@
1
1
  # Env var conventions
2
2
 
3
- All recipes reference env vars via `${VAR:-default}` so a project's `.env` (or `.env.example`) is the single source of truth.
3
+ Recipes reference non-secret env vars via `${VAR:-default}` — ports, users, database names. **Every credential uses `${VAR:?error}` instead**, so Compose refuses to start when the value is missing rather than falling back to a value published in this repository. That applies to `postgres`, `mysql`, `elasticsearch`, `meilisearch`, `minio`, and `rabbitmq`; `test/compose-recipe-safety.test.js` enforces it across every recipe.
4
+
5
+ Authenticated services also publish on `127.0.0.1:` only. Docker's `"HOST:CONTAINER"` short form binds every host interface, which turns a local dev credential into a network-reachable one.
6
+
7
+ A project's `.env` is the local source of truth; `.env.example` documents required keys without supplying reusable credentials.
8
+
9
+ [source: https://docs.docker.com/reference/compose-file/interpolation/, version: Compose Specification, retrieved: 2026-07-15]
4
10
 
5
11
  ## Naming pattern
6
12
 
7
13
  - **Service-scoped** vars use the upstream image's convention: `POSTGRES_USER`, `MYSQL_ROOT_PASSWORD`, `MEILI_MASTER_KEY`, etc. This avoids surprises when reading the image's documentation.
8
14
  - **Application-scoped** vars use a generic name that spans implementations: `DATABASE_URL`, `REDIS_URL`, `SMTP_HOST`, `AWS_ENDPOINT_URL`. The application reads these, not the service-specific ones.
9
- - **Port overrides** use `<SERVICE>_PORT` (or `<SERVICE>_<ROLE>_PORT` when there are several): `POSTGRES_PORT`, `MAILHOG_SMTP_PORT`, `MAILHOG_UI_PORT`, `RABBITMQ_PORT`, `RABBITMQ_UI_PORT`.
15
+ - **Port overrides** use `<SERVICE>_PORT` (or `<SERVICE>_<ROLE>_PORT` when there are several): `POSTGRES_PORT`, `MAILPIT_SMTP_PORT`, `MAILPIT_UI_PORT`, `RABBITMQ_PORT`, `RABBITMQ_UI_PORT`.
10
16
 
11
17
  ## Mapping between service vars and app vars
12
18
 
@@ -15,21 +21,22 @@ When a project uses Postgres, the `.env.example` should declare BOTH the service
15
21
  ```dotenv
16
22
  # Postgres (consumed by the postgres service)
17
23
  POSTGRES_USER=app
18
- POSTGRES_PASSWORD=app
24
+ # Required: generate a unique value in .env before running Compose.
25
+ POSTGRES_PASSWORD=
19
26
  POSTGRES_DB=app
20
27
  POSTGRES_PORT=5432
21
28
 
22
- # Application-side connection string
23
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
29
+ # Application-side connection string for apps running on the host
30
+ DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}
24
31
  ```
25
32
 
26
33
  Compose performs variable substitution in `.env` references. When the application also runs in Compose, use service DNS such as `postgres`; when applications run on the host, the committed example must use `localhost` and the published host port instead.
27
34
 
28
- For the PostgreSQL + pgvector recipe, use the same connection variables. The image provides the extension binaries, but `CREATE EXTENSION IF NOT EXISTS vector` belongs in the application's reviewed migrations, not container startup scripts.
35
+ If a project needs the `vector` extension, there is no bundled recipe for it — use a separately reviewed image or a managed Postgres, with the same connection variables above. `CREATE EXTENSION IF NOT EXISTS vector` belongs in the application's reviewed migrations, never in a container startup script.
29
36
 
30
37
  ## What goes in `.env.example` vs `.env`
31
38
 
32
- - `.env.example` — committed. Holds DEFAULTS that are safe for dev and template values for prod.
39
+ - `.env.example` — committed. Holds non-secret defaults and empty required secret keys; it never contains a working database password.
33
40
  - `.env` — gitignored. Real values for the user's local machine.
34
41
 
35
42
  Never commit a `.env`. Always commit a `.env.example`.
@@ -43,11 +50,11 @@ Never commit a `.env`. Always commit a `.env.example`.
43
50
  | Redis URL | `REDIS_URL` | `REDIS_PASSWORD` (if set) |
44
51
  | RabbitMQ URL | `AMQP_URL` | `RABBITMQ_USER`, `RABBITMQ_PASSWORD` |
45
52
  | AWS-compatible | `AWS_ENDPOINT_URL`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | LocalStack: `test`/`test`. MinIO: `MINIO_ROOT_USER`, `MINIO_ROOT_PASSWORD` |
46
- | SMTP | `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_SECURE` | Service hostname (`mailhog`/`mailpit`/`smtp4dev`) + recipe's SMTP port |
53
+ | SMTP | `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_SECURE` | Service hostname (`mailpit`/`smtp4dev`) + recipe's SMTP port |
47
54
  | OTel | `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://jaeger:4318` |
48
55
 
49
56
  ## Reserved names
50
57
 
51
58
  The recipes use these env var names. Do NOT reuse them for unrelated config:
52
59
 
53
- `POSTGRES_*`, `MYSQL_*`, `MEILI_*`, `MINIO_*`, `RABBITMQ_*`, `MAILHOG_*`, `MAILPIT_*`, `SMTP4DEV_*`, `JAEGER_*`, `TRAEFIK_*`, `LOCALSTACK_*`, `ELASTIC_*`, `TYPESENSE_*`, `MEMCACHED_*`, `REDIS_*`.
60
+ `POSTGRES_*`, `MYSQL_*`, `MEILI_*`, `MINIO_*`, `RABBITMQ_*`, `MAILPIT_*`, `SMTP4DEV_*`, `JAEGER_*`, `TRAEFIK_*`, `LOCALSTACK_*`, `ELASTIC_*`, `TYPESENSE_*`, `MEMCACHED_*`, `REDIS_*`.
@@ -12,7 +12,6 @@ Every recipe ships a healthcheck. App services that depend on infra MUST gate st
12
12
  | Memcached | `echo stats | nc -w 1 localhost 11211 \| grep -q uptime` | Memcached has no native ping; stats command is the cheapest readiness signal |
13
13
  | RabbitMQ | `rabbitmq-diagnostics ping` | Built-in; takes ~30s to be ready, hence `start_period: 30s` |
14
14
  | LocalStack | `curl -sf http://localhost:4566/_localstack/health \| grep -q running` | Internal endpoint reports per-service readiness |
15
- | MailHog | `wget --spider http://localhost:8025` | UI port responds when SMTP is also ready |
16
15
  | Mailpit | `wget --spider http://localhost:8025` | UI port responds when SMTP is also ready |
17
16
  | smtp4dev | `wget --spider http://localhost:80` | UI on internal port 80 |
18
17
  | MinIO | `curl -f http://localhost:9000/minio/health/live` | Documented liveness endpoint |
@@ -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
@@ -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.
@@ -32,6 +32,8 @@ See `references/stop-the-line.md` for when this rule bends.
32
32
 
33
33
  Without a deterministic reproduction, you cannot know whether your "fix" worked. The reproduction is the hypothesis test. Even a flaky bug needs a *reproducible-enough* test (e.g., "fails 8/10 runs") before fixing.
34
34
 
35
+ For non-trivial bugs, or after a first fix fails, Step 1 must establish the red-capable feedback loop contract in `references/six-step-triage.md` before theorizing: one loop, deterministic enough, fast, agent-runnable, and observed red. If the loop needs human action, use the HITL template there instead of continuing blind.
36
+
35
37
  If you cannot reproduce: see `references/non-reproducible-strategy.md`. Don't fix on guess.
36
38
 
37
39
  ### 3. Find the root cause, not the nearest cause