@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.
- package/.claude-plugin/marketplace.json +1 -0
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +305 -0
- package/README.md +7 -5
- package/lib/agents.js +6 -1
- package/lib/build-plugin.js +4 -1
- package/lib/constants.js +4 -2
- package/lib/init.js +14 -0
- package/lib/install-state.js +8 -4
- package/lib/validate.js +21 -0
- package/package.json +2 -1
- package/scaffold/agents/core/dw-build-fixer.md +0 -1
- package/scaffold/agents/core/dw-code-explorer.md +0 -1
- package/scaffold/agents/core/dw-code-reviewer.md +0 -1
- package/scaffold/agents/core/dw-planner.md +0 -1
- package/scaffold/agents/core/dw-test-author.md +0 -1
- package/scaffold/agents/csharp/dw-csharp-reviewer.md +0 -1
- package/scaffold/agents/csharp/dw-dotnet-build-fixer.md +0 -1
- package/scaffold/agents/frontend/dw-qa-runner.md +0 -1
- package/scaffold/agents/python/dw-python-build-fixer.md +0 -1
- package/scaffold/agents/python/dw-python-reviewer.md +0 -1
- package/scaffold/agents/rust/dw-rust-build-fixer.md +0 -1
- package/scaffold/agents/rust/dw-rust-reviewer.md +0 -1
- package/scaffold/agents/security/dw-security-reviewer.md +0 -1
- package/scaffold/agents/security/dw-silent-failure-hunter.md +0 -1
- package/scaffold/agents/typescript/dw-typescript-build-fixer.md +0 -1
- package/scaffold/agents/typescript/dw-typescript-reviewer.md +0 -1
- package/scaffold/config/routing.json +60 -0
- package/scaffold/en/commands/dw-brainstorm.md +12 -8
- package/scaffold/en/commands/dw-bugfix.md +4 -2
- package/scaffold/en/commands/dw-claude-run.md +5 -1
- package/scaffold/en/commands/dw-codex-run.md +5 -1
- package/scaffold/en/commands/dw-dockerize.md +14 -7
- package/scaffold/en/commands/dw-generate-pr.md +13 -0
- package/scaffold/en/commands/dw-new-project.md +14 -13
- package/scaffold/en/commands/dw-plan.md +2 -2
- package/scaffold/en/commands/dw-refactor.md +1 -0
- package/scaffold/en/commands/dw-review.md +33 -2
- package/scaffold/en/commands/dw-run.md +2 -1
- package/scaffold/en/commands/dw-triage.md +220 -0
- package/scaffold/en/templates/idea-onepager.md +28 -0
- package/scaffold/en/templates/project-onepager.md +6 -5
- package/scaffold/en/templates/task-template.md +2 -1
- package/scaffold/en/templates/tasks-template.md +11 -8
- package/scaffold/en/templates/triage-needs-info-template.md +23 -0
- package/scaffold/en/templates/triage-out-of-scope-template.md +36 -0
- package/scaffold/en/templates/triage-record-template.md +88 -0
- package/scaffold/pt-br/commands/dw-autopilot.md +81 -81
- package/scaffold/pt-br/commands/dw-brainstorm.md +12 -9
- package/scaffold/pt-br/commands/dw-bugfix.md +4 -2
- package/scaffold/pt-br/commands/dw-claude-run.md +23 -19
- package/scaffold/pt-br/commands/dw-codex-run.md +22 -18
- package/scaffold/pt-br/commands/dw-context-budget.md +10 -10
- package/scaffold/pt-br/commands/dw-copilot-run.md +20 -20
- package/scaffold/pt-br/commands/dw-dockerize.md +106 -99
- package/scaffold/pt-br/commands/dw-find-skills.md +52 -52
- package/scaffold/pt-br/commands/dw-generate-pr.md +13 -0
- package/scaffold/pt-br/commands/dw-goal.md +41 -41
- package/scaffold/pt-br/commands/dw-harness-audit.md +11 -11
- package/scaffold/pt-br/commands/dw-intel.md +70 -70
- package/scaffold/pt-br/commands/dw-learn.md +24 -24
- package/scaffold/pt-br/commands/dw-new-project.md +93 -92
- package/scaffold/pt-br/commands/dw-open-design.md +53 -53
- package/scaffold/pt-br/commands/dw-opportunities.md +45 -45
- package/scaffold/pt-br/commands/dw-pause.md +39 -39
- package/scaffold/pt-br/commands/dw-plan.md +3 -3
- package/scaffold/pt-br/commands/dw-refactor.md +31 -30
- package/scaffold/pt-br/commands/dw-resume.md +22 -22
- package/scaffold/pt-br/commands/dw-review.md +33 -2
- package/scaffold/pt-br/commands/dw-run.md +2 -1
- package/scaffold/pt-br/commands/dw-skill-health.md +10 -10
- package/scaffold/pt-br/commands/dw-subtask-complete.md +4 -4
- package/scaffold/pt-br/commands/dw-subtask-start.md +4 -4
- package/scaffold/pt-br/commands/dw-triage.md +220 -0
- package/scaffold/pt-br/templates/bugfix-summary-template.md +20 -20
- package/scaffold/pt-br/templates/concerns-template.md +20 -20
- package/scaffold/pt-br/templates/idea-onepager.md +28 -0
- package/scaffold/pt-br/templates/project-onepager.md +35 -34
- package/scaffold/pt-br/templates/state-template.md +13 -13
- package/scaffold/pt-br/templates/task-template.md +2 -1
- package/scaffold/pt-br/templates/tasks-template.md +11 -8
- package/scaffold/pt-br/templates/triage-needs-info-template.md +23 -0
- package/scaffold/pt-br/templates/triage-out-of-scope-template.md +36 -0
- package/scaffold/pt-br/templates/triage-record-template.md +88 -0
- package/scaffold/scripts/hooks/git-guardrails.mjs +41 -24
- package/scaffold/skills/docker-compose-recipes/SKILL.md +2 -4
- package/scaffold/skills/docker-compose-recipes/references/compose-composition.md +2 -2
- package/scaffold/skills/docker-compose-recipes/references/env-conventions.md +16 -9
- package/scaffold/skills/docker-compose-recipes/references/healthcheck-patterns.md +0 -1
- package/scaffold/skills/docker-compose-recipes/references/prod-vs-dev.md +7 -2
- package/scaffold/skills/docker-compose-recipes/services/elasticsearch.yml +3 -3
- package/scaffold/skills/docker-compose-recipes/services/meilisearch.yml +2 -2
- package/scaffold/skills/docker-compose-recipes/services/minio.yml +8 -4
- package/scaffold/skills/docker-compose-recipes/services/mysql.yml +4 -4
- package/scaffold/skills/docker-compose-recipes/services/postgres.yml +2 -2
- package/scaffold/skills/docker-compose-recipes/services/rabbitmq.yml +3 -3
- package/scaffold/skills/docker-compose-recipes/services/typesense.yml +3 -3
- package/scaffold/skills/dw-cli-run/SKILL.md +42 -38
- package/scaffold/skills/dw-cli-run/references/dispatch-tuning.md +85 -0
- package/scaffold/skills/dw-debug-protocol/SKILL.md +2 -0
- package/scaffold/skills/dw-debug-protocol/references/six-step-triage.md +60 -1
- package/scaffold/skills/dw-execute-phase/SKILL.md +4 -2
- package/scaffold/skills/dw-execute-phase/agents/executor.md +4 -2
- package/scaffold/skills/dw-execute-phase/agents/plan-checker.md +9 -7
- package/scaffold/skills/dw-execute-phase/references/atomic-commits.md +12 -8
- package/scaffold/skills/dw-execute-phase/references/plan-verification.md +6 -4
- package/scaffold/skills/dw-execute-phase/references/wave-coordination.md +14 -14
- package/scaffold/skills/dw-grilling/SKILL.md +17 -7
- package/scaffold/skills/dw-grilling/references/decision-tree.md +30 -7
- package/scaffold/skills/dw-simplification/references/deep-modules.md +50 -0
- package/scaffold/skills/dw-testing-discipline/SKILL.md +3 -1
- package/scaffold/skills/dw-testing-discipline/references/tdd-loop.md +83 -0
- package/scaffold/skills/docker-compose-recipes/services/mailhog.yml +0 -23
- 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
|
-
|
|
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
|
|
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(
|
|
48
|
+
payload = JSON.parse(input);
|
|
58
49
|
} catch {
|
|
59
|
-
return
|
|
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
|
|
56
|
+
if (!command) return null;
|
|
66
57
|
|
|
67
58
|
for (const rule of DANGEROUS) {
|
|
68
59
|
if (rule.re.test(command)) {
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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`, `
|
|
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
|
|
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
|
|
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
|
-
|
|
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`, `
|
|
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
|
-
|
|
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}@
|
|
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
|
-
|
|
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
|
|
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 (`
|
|
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_*`, `
|
|
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
|
-
###
|
|
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
|
-
-
|
|
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
|
|
11
|
+
ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:?Set ELASTIC_PASSWORD in .env}
|
|
12
12
|
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
|
|
13
13
|
ports:
|
|
14
|
-
- "
|
|
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
|
|
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
|
|
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
|
-
- "
|
|
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:
|
|
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
|
|
15
|
+
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?Set MINIO_ROOT_PASSWORD in .env}
|
|
12
16
|
ports:
|
|
13
|
-
- "
|
|
14
|
-
- "
|
|
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
|
|
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
|
|
12
|
+
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?Set MYSQL_PASSWORD in .env}
|
|
13
13
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
|
|
14
14
|
ports:
|
|
15
|
-
- "
|
|
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
|
|
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
|
|
10
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
|
|
11
11
|
POSTGRES_DB: ${POSTGRES_DB:-app}
|
|
12
12
|
ports:
|
|
13
|
-
- "
|
|
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
|
|
10
|
+
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:?Set RABBITMQ_PASSWORD in .env}
|
|
11
11
|
ports:
|
|
12
|
-
- "
|
|
13
|
-
- "
|
|
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:
|
|
6
|
+
image: typesense/typesense:30.2
|
|
7
7
|
restart: unless-stopped
|
|
8
|
-
command: --data-dir /data --api-key=${TYPESENSE_API_KEY
|
|
8
|
+
command: --data-dir /data --api-key=${TYPESENSE_API_KEY:?Set TYPESENSE_API_KEY in .env} --enable-cors
|
|
9
9
|
ports:
|
|
10
|
-
- "
|
|
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
|
-
| `
|
|
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
|
-
##
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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.
|
|
50
|
-
3.
|
|
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 —
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
(
|
|
73
|
-
E2E/secure-audit in the gate
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
-
|
|
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
|