@brunosps00/dev-workflow 1.5.0 → 1.7.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 (34) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +2 -2
  3. package/lib/constants.js +6 -4
  4. package/lib/install-deps.js +22 -1
  5. package/lib/install-open-design.js +89 -0
  6. package/package.json +4 -2
  7. package/scaffold/en/commands/dw-adr.md +49 -11
  8. package/scaffold/en/commands/dw-analyze-project.md +5 -0
  9. package/scaffold/en/commands/dw-brainstorm.md +103 -32
  10. package/scaffold/en/commands/dw-claude-run.md +8 -5
  11. package/scaffold/en/commands/dw-help.md +3 -1
  12. package/scaffold/en/commands/dw-open-design.md +324 -0
  13. package/scaffold/en/commands/dw-plan.md +34 -5
  14. package/scaffold/en/templates/adr-template.md +8 -4
  15. package/scaffold/en/templates/idea-onepager.md +44 -2
  16. package/scaffold/pt-br/commands/dw-adr.md +49 -11
  17. package/scaffold/pt-br/commands/dw-analyze-project.md +5 -0
  18. package/scaffold/pt-br/commands/dw-brainstorm.md +105 -32
  19. package/scaffold/pt-br/commands/dw-claude-run.md +8 -5
  20. package/scaffold/pt-br/commands/dw-help.md +3 -1
  21. package/scaffold/pt-br/commands/dw-open-design.md +323 -0
  22. package/scaffold/pt-br/commands/dw-plan.md +34 -5
  23. package/scaffold/pt-br/templates/adr-template.md +8 -4
  24. package/scaffold/pt-br/templates/idea-onepager.md +44 -2
  25. package/scaffold/scripts/open-design/capture-current.mjs +170 -0
  26. package/scaffold/scripts/open-design/gate-prototype.mjs +223 -0
  27. package/scaffold/skill-registry.json +25 -1
  28. package/scaffold/skills/dw-cli-run/SKILL.md +10 -8
  29. package/scaffold/skills/dw-domain-modeling/SKILL.md +84 -0
  30. package/scaffold/skills/dw-domain-modeling/references/adr-policy.md +48 -0
  31. package/scaffold/skills/dw-domain-modeling/references/glossary-format.md +66 -0
  32. package/scaffold/skills/dw-grilling/SKILL.md +98 -0
  33. package/scaffold/skills/dw-grilling/references/decision-tree.md +56 -0
  34. package/scaffold/skills/dw-grilling/references/interview-loop.md +52 -0
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ function parseArgs(argv) {
8
+ const result = {};
9
+ for (let index = 0; index < argv.length; index += 1) {
10
+ const token = argv[index];
11
+ if (!token.startsWith("--")) continue;
12
+ const [rawKey, inlineValue] = token.slice(2).split("=", 2);
13
+ const key = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
14
+ if (inlineValue !== undefined) {
15
+ result[key] = inlineValue;
16
+ continue;
17
+ }
18
+ const next = argv[index + 1];
19
+ if (!next || next.startsWith("--")) {
20
+ result[key] = true;
21
+ continue;
22
+ }
23
+ result[key] = next;
24
+ index += 1;
25
+ }
26
+ return result;
27
+ }
28
+
29
+ async function importFirefox() {
30
+ try {
31
+ return (await import("@playwright/test")).firefox;
32
+ } catch {
33
+ try {
34
+ return (await import("playwright")).firefox;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+ }
40
+
41
+ function slugify(value) {
42
+ return (
43
+ String(value)
44
+ .toLowerCase()
45
+ .replace(/\.html?$/, "")
46
+ .replace(/[^a-z0-9]+/g, "-")
47
+ .replace(/^-+|-+$/g, "")
48
+ .slice(0, 70) || "prototype"
49
+ );
50
+ }
51
+
52
+ function addResult(results, name, pass, details = "") {
53
+ results.push({ name, pass: Boolean(pass), details });
54
+ }
55
+
56
+ async function structuralChecks(page, html, results) {
57
+ const data = await page.evaluate(() => {
58
+ const visibleText = document.body ? document.body.innerText : "";
59
+ const hasDialogRole = Boolean(document.querySelector('[role="dialog"], [aria-modal="true"], [aria-labelledby]'));
60
+ const title = document.title.trim();
61
+ return { visibleText, hasDialogRole, title };
62
+ });
63
+
64
+ addResult(results, "title", data.title.length > 0, data.title || "missing document title");
65
+ addResult(results, "aberto-param", /\baberto\b/.test(html), "source contains aberto");
66
+ addResult(results, "replace-state", /replaceState/.test(html), "source contains replaceState");
67
+ addResult(results, "dialog-aria", data.hasDialogRole, "role=dialog, aria-modal, or aria-labelledby present");
68
+ addResult(results, "skeleton-state", /skeleton|carregando|loading/i.test(html + "\n" + data.visibleText));
69
+ addResult(results, "empty-state", /empty|vazio|sem\s+(resultado|dados|itens|registros)/i.test(html + "\n" + data.visibleText));
70
+ addResult(results, "error-state", /error|erro|falha|failed|retry|tentar novamente/i.test(html + "\n" + data.visibleText));
71
+ }
72
+
73
+ async function visibleDialog(page, selector) {
74
+ const selected = selector ? page.locator(selector).first() : page.locator('[role="dialog"], [aria-modal="true"]').first();
75
+ try {
76
+ return (await selected.count()) > 0 && (await selected.isVisible());
77
+ } catch {
78
+ return false;
79
+ }
80
+ }
81
+
82
+ async function clickFirstOpenable(page, rowSelector) {
83
+ const selector =
84
+ rowSelector ||
85
+ '[data-open], [data-id], tbody tr, [role="row"], [role="button"], article, li button, li, button';
86
+ const candidates = page.locator(selector);
87
+ const count = Math.min(await candidates.count(), 20);
88
+ for (let index = 0; index < count; index += 1) {
89
+ const candidate = candidates.nth(index);
90
+ try {
91
+ if (!(await candidate.isVisible())) continue;
92
+ await candidate.click({ timeout: 1500 });
93
+ return true;
94
+ } catch {
95
+ // Try the next plausible opener.
96
+ }
97
+ }
98
+ return false;
99
+ }
100
+
101
+ async function behavioralChecks(page, baseUrl, args, results) {
102
+ const dialogSelector = args.dialogSelector ? String(args.dialogSelector) : "";
103
+ if (!(await visibleDialog(page, dialogSelector))) {
104
+ const clicked = await clickFirstOpenable(page, args.rowSelector ? String(args.rowSelector) : "");
105
+ await page.waitForTimeout(300);
106
+ addResult(results, "open-drawer-click", clicked && (await visibleDialog(page, dialogSelector)), "first row/openable opens dialog or drawer");
107
+ } else {
108
+ addResult(results, "open-drawer-click", true, "dialog already visible");
109
+ }
110
+
111
+ const deepLinkId = args.deepLinkId ? String(args.deepLinkId) : "";
112
+ if (!deepLinkId) {
113
+ addResult(results, "deep-link-opens", false, "missing --deep-link-id");
114
+ addResult(results, "esc-clears-param", false, "missing --deep-link-id");
115
+ return;
116
+ }
117
+
118
+ const url = new URL(baseUrl);
119
+ url.searchParams.set("aberto", deepLinkId);
120
+ await page.goto(url.toString(), { waitUntil: "load" });
121
+ await page.waitForTimeout(500);
122
+
123
+ const expectedText = String(args.expectedText || deepLinkId);
124
+ const deepLinkData = await page.evaluate((text) => {
125
+ const bodyText = document.body ? document.body.innerText : "";
126
+ const hasDialog = Boolean(document.querySelector('[role="dialog"], [aria-modal="true"]'));
127
+ return {
128
+ hasDialog,
129
+ hasText: bodyText.toLowerCase().includes(text.toLowerCase()),
130
+ search: window.location.search,
131
+ };
132
+ }, expectedText);
133
+ addResult(
134
+ results,
135
+ "deep-link-opens",
136
+ deepLinkData.search.includes(`aberto=${encodeURIComponent(deepLinkId)}`) && deepLinkData.hasDialog && deepLinkData.hasText,
137
+ `expected visible text: ${expectedText}`,
138
+ );
139
+
140
+ await page.keyboard.press("Escape");
141
+ await page.waitForTimeout(500);
142
+ const searchAfterEsc = await page.evaluate(() => window.location.search);
143
+ addResult(results, "esc-clears-param", !new URLSearchParams(searchAfterEsc).has("aberto"), searchAfterEsc || "empty search");
144
+ }
145
+
146
+ async function captureScreenshots(browser, fileUrl, outDir, slug) {
147
+ const shots = [];
148
+ for (const scheme of ["light", "dark"]) {
149
+ const context = await browser.newContext({
150
+ colorScheme: scheme,
151
+ viewport: { width: 1440, height: 1000 },
152
+ });
153
+ const page = await context.newPage();
154
+ await page.goto(fileUrl, { waitUntil: "load" });
155
+ await page.evaluate((theme) => {
156
+ document.documentElement.setAttribute("data-theme", theme);
157
+ }, scheme);
158
+ await page.waitForTimeout(300);
159
+ const shotPath = path.join(outDir, `${slug}-${scheme}.png`);
160
+ await page.screenshot({ path: shotPath, fullPage: true });
161
+ shots.push({ colorScheme: scheme, path: shotPath });
162
+ await context.close();
163
+ }
164
+ return shots;
165
+ }
166
+
167
+ async function main() {
168
+ const args = parseArgs(process.argv.slice(2));
169
+ if (!args.file) {
170
+ throw new Error("Missing required argument: --file <prototype.html>");
171
+ }
172
+
173
+ const filePath = path.resolve(String(args.file));
174
+ if (!fs.existsSync(filePath)) {
175
+ throw new Error(`Prototype file not found: ${filePath}`);
176
+ }
177
+
178
+ const firefox = await importFirefox();
179
+ if (!firefox) {
180
+ throw new Error("Playwright is not installed. Run: npm i -D @playwright/test && npx playwright install firefox");
181
+ }
182
+
183
+ const html = fs.readFileSync(filePath, "utf8");
184
+ const outDir = path.resolve(String(args.out || path.join(".dw", ".open-design", "gate", slugify(path.basename(filePath)))));
185
+ fs.mkdirSync(outDir, { recursive: true });
186
+ const baseUrl = pathToFileURL(filePath).toString();
187
+ const slug = slugify(path.basename(filePath));
188
+
189
+ const browser = await firefox.launch({ headless: true });
190
+ const results = [];
191
+ let screenshots = [];
192
+ try {
193
+ const context = await browser.newContext({ colorScheme: "light", viewport: { width: 1440, height: 1000 } });
194
+ const page = await context.newPage();
195
+ await page.goto(baseUrl, { waitUntil: "load" });
196
+ await structuralChecks(page, html, results);
197
+ await behavioralChecks(page, baseUrl, args, results);
198
+ await context.close();
199
+ screenshots = await captureScreenshots(browser, baseUrl, outDir, slug);
200
+ } finally {
201
+ await browser.close();
202
+ }
203
+
204
+ const failed = results.filter((result) => !result.pass);
205
+ const summary = {
206
+ file: filePath,
207
+ score: Number(((results.length - failed.length) / results.length * 10).toFixed(1)),
208
+ pass: failed.length === 0,
209
+ results,
210
+ screenshots,
211
+ };
212
+ fs.writeFileSync(path.join(outDir, `${slug}-gate.json`), JSON.stringify(summary, null, 2) + "\n", "utf8");
213
+ process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
214
+
215
+ if (failed.length > 0) {
216
+ process.exit(1);
217
+ }
218
+ }
219
+
220
+ main().catch((error) => {
221
+ process.stderr.write(`gate-prototype: ${error.message}\n`);
222
+ process.exit(1);
223
+ });
@@ -35,7 +35,7 @@
35
35
  "trigger": "Fire an AI CLI (Codex/Claude/Copilot) in a dedicated git worktree to implement a prepared prompt; resumable per-task session; STOP for the gate",
36
36
  "expected_output": "Audit-logged run with a 0-10 dual evaluation, a resumable session-id sidecar, and a STOP-for-gate Structured Return.",
37
37
  "load_policy": "lazy-references",
38
- "context_limit": 18000,
38
+ "context_limit": 19000,
39
39
  "bundled": true
40
40
  },
41
41
  {
@@ -60,6 +60,30 @@
60
60
  "context_limit": 12000,
61
61
  "bundled": true
62
62
  },
63
+ {
64
+ "name": "dw-domain-modeling",
65
+ "kind": "protocol",
66
+ "tier": "specialty",
67
+ "owner": "dw-brainstorm,dw-analyze-project",
68
+ "trigger": "Native Grill domain vocabulary: challenge fuzzy/overloaded terms, cross-check against code, persist a canonical glossary to .dw/domain",
69
+ "expected_output": "Canonical terms with discouraged synonyms persisted lazily to .dw/domain (single or multi-context), or fuzzy-term findings.",
70
+ "load_policy": "lazy-references",
71
+ "context_limit": 12000,
72
+ "exportable": false,
73
+ "bundled": true
74
+ },
75
+ {
76
+ "name": "dw-grilling",
77
+ "kind": "protocol",
78
+ "tier": "specialty",
79
+ "owner": "dw-brainstorm,dw-plan",
80
+ "trigger": "Native Grill decision-tree interview: sharpen a plan/PRD to shared understanding, exactly one decision per turn with a recommended answer",
81
+ "expected_output": "A dependency-ordered decision tree, resolved decisions with recommendations, and an explicit shared-understanding gate.",
82
+ "load_policy": "lazy-references",
83
+ "context_limit": 12000,
84
+ "exportable": false,
85
+ "bundled": true
86
+ },
63
87
  {
64
88
  "name": "dw-debug-protocol",
65
89
  "kind": "protocol",
@@ -129,19 +129,21 @@ without reaching 9. Always show the score + a short per-criterion rationale —
129
129
  ceiling, **cite the specific gap** (the failing test, the file left out of fence), not just a label. Same
130
130
  evidence discipline as the five-axis rubric in `dw-review-rigor/references/self-eval-rubric.md`.
131
131
 
132
- ## Dual evaluation: the CLI auto-gates (MAX effort) → Claude re-gates (compares scores)
132
+ ## Dual evaluation: the CLI auto-gates (max effort) → the parent re-gates (compares scores)
133
133
  The CLI **can't grade its own exam alone** — hence two layers. The CLI auto-gates cheaply (close to the work) and
134
- **Claude audits independently** and **compares the scores** (catches an inflated self-score).
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.
135
136
  1. **CLI auto-gate (loop, MAX effort).** The prompt MUST instruct: after implementing, **run the SAME gate** and
136
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.
137
138
  Stop at self-score ≥9 + green gate (or report `blockers`). The final report carries the self-score + per
138
139
  criterion.
139
- 2. **Claude re-gate (independent).** When the CLI declares pass, the parent **re-runs the SAME gate** (fan-out,
140
- prefer Workflow → `/workflows`) and gives its **own 0–10** — without trusting the self-score.
141
- 3. **Compare + decide.** Claude ≥9 and small gap → **PASS** (ready for the owner's merge decision). Claude <9 OR a
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
142
144
  large gap (CLI overestimated) → re-execute: hand the gaps back via **session resume** (step 5, max effort) and
143
- repeat 1→2→3 until it converges. **The score that counts for acceptance is Claude's**; the self-score is signal
144
- + an inflation detector. Always record both scores + the gap in the Structured Return.
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.
145
147
 
146
148
  ## Graded escalation on failure
147
149
  If the score is low / the gate failed / the CLI didn't finish, **re-run the SAME task one notch up** — gradual, no
@@ -199,7 +201,7 @@ The stream ends with the adapter's `USAGE` block (token counts). Extract it from
199
201
  right worktree, ready for the gate; `FINDINGS` = ran, score 6–8 or caveats (uncommitted/out-of-fence/partial);
200
202
  `BLOCKED` = missing CLI/credential/worktree/prompt/adapter table, or escalation exhausted without reaching 9;
201
203
  `NOT_APPLICABLE` = no worktree+prompt → still planning.
202
- - **Score:** **Claude's score** (0–10 — counts for acceptance, bar ≥9) + the **CLI self-score** + the **gap**;
204
+ - **Score:** **the parent's score** (0–10 — counts for acceptance, bar ≥9) + the **CLI self-score** + the **gap**;
203
205
  per-criterion rationale (conformance/gate/completeness/hygiene/quality). Large gap = inflated self-score.
204
206
  - **Scope:** worktree + branch + prompt used; CLI + model + effort + sandbox/auto mode; escalation notches taken.
205
207
  - **Evidence:** path of the durable audit log + final-message file; stream JSONL excerpt; `git status`/diff of the
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: dw-domain-modeling
3
+ description: "Internal domain-vocabulary protocol for the native Grill flow. Loaded by /dw-brainstorm (grill mode) and referenced by /dw-analyze-project to keep canonical vocabulary: challenge fuzzy or overloaded terms, propose precise canonical names, stress-test with concrete edge-case scenarios, cross-check terms against the code, and persist resolved terms safely and lazily into .dw/domain (glossary for a single context, context-map plus per-context files for many). Rare ADR policy. Pipeline-internal, never exported, never implements code."
4
+ allowed-tools:
5
+ - Read
6
+ - Grep
7
+ - Glob
8
+ - Write
9
+ ---
10
+
11
+ # dw-domain-modeling — canonical vocabulary, kept in .dw/domain
12
+
13
+ The vocabulary layer of the native Grill flow. It keeps the language of the domain sharp: one canonical term per
14
+ concept, fuzzy language challenged, overloaded terms split, every term cross-checked against the code. Resolved
15
+ terms persist to `.dw/domain/**` — **never** to root `CONTEXT.md` and **never** into the auto-generated
16
+ `.dw/rules/` module files (those describe what the code IS; the glossary is human-curated vocabulary).
17
+
18
+ > **Internal protocol.** Loaded by `/dw-brainstorm --mode=grill`; read and linked (never regenerated) by
19
+ > `/dw-analyze-project`. Not standalone, not exported to the à-la-carte plugin.
20
+
21
+ ## The disciplines
22
+
23
+ 1. **Canonical vocabulary — one term per concept.** When several words compete for the same idea, pick the best
24
+ and record the rest as discouraged synonyms. The glossary's job is to end the debate, not catalogue it.
25
+ 2. **Challenge fuzzy / overloaded terms.** When the user says "the user thing" or uses one word for two
26
+ concepts (`User` the account vs `User` the person), stop and propose precise names. Don't pretend to
27
+ understand — push back until the term is sharp.
28
+ 3. **Stress-test with concrete scenarios.** Force precision with edge cases: *"A guest checks out without an
29
+ account — is that a Customer or a Visitor?"* Vague answers become the next grill question.
30
+ 4. **Cross-check against the code.** Before recording a term, confirm it against the codebase. Surface
31
+ contradictions with the file and line: *"You call it `Subscription`, but `src/billing/plan.ts:12` models it
32
+ as `Plan`. Which is canonical?"* Don't argue from generalities.
33
+ 5. **Persist safely, lazily, and only when authorized.** Create a `.dw/domain/**` file only **after** a term is
34
+ resolved AND the Grill flow has write authorization. No speculative files; no writing an unresolved term.
35
+ 6. **Route single vs multi context.** One bounded context → one flat `.dw/domain/glossary.md`. Multiple contexts
36
+ (the same word means different things in billing vs shipping) → `.dw/domain/context-map.md` plus one
37
+ `.dw/domain/contexts/<slug>.md` per context. Full routing + format: `references/glossary-format.md`.
38
+ 7. **ADR rarely, and only on approval.** Vocabulary decisions are not ADRs. Reach for an ADR only on the
39
+ three-criterion test, and only with separate explicit user approval — see `references/adr-policy.md`.
40
+
41
+ ## Native artifacts
42
+
43
+ | Path | When |
44
+ |---|---|
45
+ | `.dw/domain/glossary.md` | Single bounded context — the default. |
46
+ | `.dw/domain/context-map.md` | Two or more contexts — names the contexts and the terms that cross or clash. |
47
+ | `.dw/domain/contexts/<slug>.md` | Per-context glossary, one file per context. |
48
+
49
+ All created **lazily**: the directory and file appear only when the first term in that context is resolved and
50
+ authorization exists. `/dw-analyze-project` **reads and links** these when present and must **preserve** them —
51
+ never regenerate or overwrite `.dw/domain/**`.
52
+
53
+ ## What a glossary entry is (and is not)
54
+
55
+ A definition is **one or two sentences** that say what the domain term **is**, plus a short list of discouraged
56
+ synonyms. It **excludes** implementation details (classes, files, tables), requirements ("must load in 200ms"),
57
+ scratch notes, and general programming concepts (timeouts, retries, DTOs) — even when the project uses them
58
+ heavily. The test: *is this concept unique to this domain, or general programming?* Only the former belongs.
59
+ Format and examples: `references/glossary-format.md`.
60
+
61
+ ## Anti-patterns
62
+
63
+ - Writing vocabulary into `.dw/rules/<module>.md` or a root `CONTEXT.md` instead of `.dw/domain/**`.
64
+ - Creating a glossary file for a term that is not yet resolved, or before write authorization exists.
65
+ - Definitions that describe the code ("a `Repository` class in `src/`") instead of the concept.
66
+ - Listing every synonym as equal — pick one canonical term, discourage the rest.
67
+ - Splitting into multiple contexts prematurely — a single flat glossary is right until a word genuinely clashes
68
+ across contexts.
69
+ - Regenerating or overwriting `.dw/domain/**` from `/dw-analyze-project`.
70
+
71
+ ## Structured Return
72
+
73
+ - **Status:** `PASS` · `FINDINGS` · `BLOCKED` · `NOT_APPLICABLE` — `PASS` when the terms in scope are resolved,
74
+ cross-checked against the code, and (when authorized) persisted to the correct `.dw/domain/**` path; `FINDINGS`
75
+ when terms remain fuzzy, a code contradiction is open, or authorization is missing so nothing was written;
76
+ `BLOCKED` when the caller supplied no vocabulary to resolve or the code cross-check cannot run;
77
+ `NOT_APPLICABLE` when there is no domain vocabulary at stake.
78
+ - **Scope:** the terms examined, single- vs multi-context routing, and the target `.dw/domain/**` path(s).
79
+ - **Evidence:** the code cross-checks (file:line) and scenarios used to sharpen each term.
80
+ - **Artifacts:** the glossary / context-map / per-context files written or proposed, with paths.
81
+ - **Decisions:** each canonical term chosen, the discouraged synonyms, and the routing decision.
82
+ - **Risks:** unresolved fuzziness, open code contradictions, or vocabulary written without authorization.
83
+ - **Next Step:** resolve the remaining terms, request write authorization, or hand the canonical vocabulary back
84
+ to the grill / one-pager.
@@ -0,0 +1,48 @@
1
+ # ADR policy — rare, three-criterion, approval-gated
2
+
3
+ Most decisions surfaced during a grill are **not** ADRs. Vocabulary choices, scope calls, and reversible
4
+ decisions belong in the glossary or the one-pager, not the ADR log. An ADR is for the rare decision that is
5
+ expensive to unwind and non-obvious to a future reader.
6
+
7
+ ## The three-criterion test (all must hold)
8
+
9
+ | Criterion | Test |
10
+ |---|---|
11
+ | **Hard to reverse** | If we change this in 6 months, does it cost meaningfully more than a day — a migration, a re-architecture, a contract change? |
12
+ | **Surprising without context** | Would a competent new contributor reasonably reach a *different* decision, absent this record? |
13
+ | **Genuine trade-off** | Was there a real alternative we considered and chose against — a rejection worth remembering? |
14
+
15
+ If any criterion fails, **do not** write an ADR. Record the decision where it belongs (glossary, one-pager, PRD)
16
+ and move on. ADR-ing every casual decision turns the log into noise.
17
+
18
+ ## Approval is always separate
19
+
20
+ Even when all three criteria hold, **creating an ADR needs a separate, explicit user approval.** A resolved grill
21
+ decision authorizes the glossary/alignment update — it does **not** authorize an ADR. Offer the ADR, state which
22
+ criteria it meets, and wait for the user to approve before anything is written.
23
+
24
+ ## Routing — repo vs prd scope
25
+
26
+ ADRs are created via `/dw-adr`, which routes by `--scope`:
27
+
28
+ - **`--scope=repo`** → `.dw/adrs/adr-NNN.md`. Repo-wide decisions, and any decision made **before a PRD exists**
29
+ (a greenfield grill naturally produces repo-scoped ADRs).
30
+ - **`--scope=prd`** → `.dw/spec/<prd>/adrs/adr-NNN.md`. Decisions bound to a specific active PRD.
31
+
32
+ Default resolution when scope is unspecified (`--scope=` is always authoritative): the uniquely **active** PRD →
33
+ `prd`; no active PRD → `repo`; several active PRDs (ambiguous) → **ask**, don't guess. `NNN` is sequential within
34
+ the chosen scope's ADR directory.
35
+
36
+ **Active vs historical PRD** — deterministic and conservative, reading only evidence the repo already records
37
+ (never guessing): a PRD directory is a candidate only when it is **not terminal** (its `prd.md` status is not
38
+ shipped/merged/delivered/done/archived/superseded/cancelled). Choose the active one by precedence — an **explicit
39
+ target** (named PRD or `PRD_PATH`) → the **active session** (`.dw/STATE.md`) → the **current `feat/prd-<slug>`
40
+ branch**. If more than one non-terminal PRD survives with no disambiguating signal, **ask**. The authoritative
41
+ rule lives in the `/dw-adr` "Active PRD" section.
42
+
43
+ ## What qualifies
44
+
45
+ Architectural shape, integration patterns between components, high-lock-in technology choices, bounded-context
46
+ boundaries, deliberate deviations from convention, non-visible constraints, and non-obvious rejected
47
+ alternatives. A vocabulary decision — "we call it `Tenant`, not `Org`" — does **not** qualify; it is a glossary
48
+ entry.
@@ -0,0 +1,66 @@
1
+ # Glossary format and context routing
2
+
3
+ Where domain vocabulary lives, what an entry looks like, and how to decide between a single flat glossary and a
4
+ multi-context map.
5
+
6
+ ## Paths (created lazily)
7
+
8
+ | Path | Purpose |
9
+ |---|---|
10
+ | `.dw/domain/glossary.md` | The single-context default. One flat glossary for the whole project. |
11
+ | `.dw/domain/context-map.md` | Multi-context index: names each bounded context and the terms that cross or clash between them. |
12
+ | `.dw/domain/contexts/<slug>.md` | One glossary per bounded context (`<slug>` = kebab-case context name). |
13
+
14
+ Never write vocabulary to root `CONTEXT.md` or to `.dw/rules/<module>.md`. The `.dw/rules/` files are
15
+ auto-generated analysis of what the code IS; the glossary is human-curated vocabulary and must survive
16
+ `/dw-analyze-project` runs untouched.
17
+
18
+ Create files only when a term is **resolved** and the Grill flow has **write authorization**. Do not scaffold
19
+ empty files or write an unresolved term.
20
+
21
+ ## Entry format
22
+
23
+ Each term is a short entry:
24
+
25
+ ```markdown
26
+ ### Order
27
+ A customer's committed request to buy one or more items, in one of: pending, paid, shipped, delivered, refunded.
28
+
29
+ _Avoid:_ Purchase, Cart, Transaction
30
+ ```
31
+
32
+ Rules for an entry:
33
+
34
+ - **One or two sentences.** Define what the term **is**, not what a class does. If it needs a paragraph, the
35
+ concept is probably two concepts — split it.
36
+ - **`_Avoid:_` line** lists the discouraged synonyms. Pick one canonical term; every competing word goes here so
37
+ the debate does not reopen.
38
+ - **Exclude** implementation detail (files, classes, tables, endpoints), requirements/NFRs, scratch notes, and
39
+ general programming concepts (timeout, retry, DTO, cache) even if heavily used.
40
+ - **Inclusion test:** *is this concept unique to this domain, or is it general programming?* Only the former is a
41
+ glossary term. `Order`, `Tenant`, `Dunning` — yes. `Timeout`, `Repository`, `Idempotency key` — no.
42
+
43
+ Group entries under `##` subheadings when natural clusters emerge; a flat list of `###` terms is fine for a small
44
+ glossary.
45
+
46
+ ## Single vs multi context — how to route
47
+
48
+ Start with **one flat `.dw/domain/glossary.md`**. It is the right default and stays right until a word genuinely
49
+ means different things in different parts of the system.
50
+
51
+ Split to multi-context only when a term **clashes across bounded contexts** — e.g. `Account` means a login in
52
+ Identity but a ledger in Billing. Then:
53
+
54
+ 1. Create `.dw/domain/context-map.md`. List each context (Identity, Billing, Shipping…), a one-line description,
55
+ and the **shared or clashing terms** with the context each meaning belongs to.
56
+ 2. Move each context's terms into `.dw/domain/contexts/<slug>.md` (e.g. `contexts/billing.md`).
57
+ 3. In the context map, note translations: "Billing `Account` ≈ Identity `Organization`, not Identity `Account`."
58
+
59
+ Do not split pre-emptively. A single clean glossary beats three thin ones. When in doubt, stay single-context and
60
+ record the tension as a grill decision rather than forking files.
61
+
62
+ ## Interaction with /dw-analyze-project
63
+
64
+ `/dw-analyze-project` must, when `.dw/domain/**` exists: read it, link it from `.dw/rules/index.md` (so
65
+ downstream commands find the canonical vocabulary), and **preserve it verbatim** — never regenerate, merge, or
66
+ overwrite. The analyzer owns `.dw/rules/**`; the Grill flow owns `.dw/domain/**`.
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: dw-grilling
3
+ description: "Internal decision-tree interview protocol for the native Grill flow. Loaded by /dw-brainstorm (grill mode) and /dw-plan to stress-test a plan or PRD to shared understanding: build a dependency-ordered decision tree, ask exactly ONE unresolved decision per turn with a recommended answer plus rationale and a real alternative, discover facts from the repo/docs instead of asking, leave decisions to the user, and gate alignment on explicit confirmation. Pipeline-internal, never exported, never implements code."
4
+ allowed-tools:
5
+ - Read
6
+ - Grep
7
+ - Glob
8
+ ---
9
+
10
+ # dw-grilling — interview to shared understanding, one decision at a time
11
+
12
+ A relentless-but-disciplined interview that sharpens a plan/PRD until author and reviewer reach a
13
+ **shared understanding** — the alignment layer of the native Grill flow. It walks the design tree branch by
14
+ branch, resolving dependencies one at a time. It does not brainstorm options and it does not implement; it
15
+ converges decisions. Persistence (glossary, one-pager, PRD) is delegated — see `dw-domain-modeling` for
16
+ vocabulary and the calling command for the idea one-pager / PRD.
17
+
18
+ > **Internal protocol.** Loaded by `/dw-brainstorm --mode=grill` and by `/dw-plan` when it consumes an aligned
19
+ > handoff. Not a standalone skill, not exported to the à-la-carte plugin. If no calling command supplied the
20
+ > Grill context (target artifact + write authorization state), return `NOT_APPLICABLE`.
21
+
22
+ ## Hard rules (non-negotiable)
23
+
24
+ 1. **Exactly one unresolved decision per turn, then wait.** Never dump a list of questions — a batch is
25
+ bewildering and lets the reviewer skim instead of decide. Ask, wait, integrate the answer, ask the next.
26
+ 2. **Every question carries a recommendation.** Structure: *evidence (when available) → the decision →
27
+ recommended answer → one-line rationale → a meaningful alternative and its trade-off.* A question with no
28
+ recommended answer is not ready to ask. See `references/interview-loop.md`.
29
+ 3. **Facts are discovered, decisions are asked.** If the answer can be found in the codebase, rules, intel,
30
+ or docs, look it up and state it — do not ask the user a question the repo already answers. Only genuine
31
+ **decisions** (choices with no single correct answer) go to the user.
32
+ 4. **Decisions belong to the user.** Recommend, argue, push back on vague language — but the user chooses.
33
+ Never answer your own decision question and move on.
34
+ 5. **No implementation, ever.** Grill never edits source code. It produces alignment, not commits.
35
+ 6. **Alignment is gated on explicit confirmation.** Do not declare shared understanding (or `status: aligned`)
36
+ until every dependency branch is resolved, glossary/code contradictions are closed, no blocking decision
37
+ remains, AND the user explicitly confirms. Otherwise persist a draft/paused state and stop.
38
+
39
+ ## Before the first question — build the decision tree
40
+
41
+ Inspect project facts and open questions, then order them by dependency (never ask a downstream decision before
42
+ its blocker is resolved):
43
+
44
+ 1. Read what already exists: `.dw/rules/`, `.dw/intel/` (via `/dw-intel` when present), `.dw/constitution.md`,
45
+ `.dw/domain/**` (glossary / context-map), current PRDs/TechSpecs, recent git activity.
46
+ 2. Extract the open decisions the plan/PRD implies. Fold in vocabulary tension surfaced by `dw-domain-modeling`
47
+ (fuzzy or overloaded terms are decisions too).
48
+ 3. Order them into a **dependency tree**: a node that constrains others comes first. Record it so a later turn
49
+ (or `/dw-plan`) can resume. Full method + the shared-understanding gate: `references/decision-tree.md`.
50
+
51
+ ## The loop
52
+
53
+ For each unresolved node, in dependency order:
54
+
55
+ 1. **Discover** every fact the node needs (repo/docs). State the facts and their source.
56
+ 2. **Frame the decision** and give the recommended answer + rationale + one real alternative/trade-off.
57
+ 3. **Ask** — one decision, then wait.
58
+ 4. **Integrate** the answer. A resolved answer authorizes the matching glossary/alignment update (delegated to
59
+ `dw-domain-modeling`; the caller persists to the one-pager/PRD). ADR creation still needs a separate explicit
60
+ approval — never auto-create one.
61
+ 5. **Re-walk** the tree: the answer may unlock or reshape downstream nodes.
62
+
63
+ When the tree is exhausted and contradictions are closed, present the shared-understanding summary and ask the
64
+ user to confirm. Only then is the session `aligned`.
65
+
66
+ ## Allowed writes (only when the caller authorized them)
67
+
68
+ Grill is read-first. The only stateful writes the flow may make, and only after the corresponding decision is
69
+ resolved and write authorization exists, are: `.dw/domain/**` (via `dw-domain-modeling`), the active
70
+ `.dw/spec/ideas/<slug>.md`, an explicitly active PRD/TechSpec **after showing the proposed change**, and routed
71
+ ADR paths (after separate approval). Never source code. If authorization is absent, keep interviewing and report
72
+ what you *would* persist.
73
+
74
+ ## Anti-patterns
75
+
76
+ - Asking two or more decisions in one turn — the batch defeats the protocol.
77
+ - Asking a question the codebase already answers — that is a fact, look it up.
78
+ - Recommending nothing ("what do you want to do about X?") — always bring a recommended answer.
79
+ - Declaring alignment without the user's explicit confirmation.
80
+ - Writing any artifact before its decision is resolved and authorization exists.
81
+ - Generating an option matrix here — that is `option-matrix`, a separate and mutually exclusive phase.
82
+
83
+ ## Structured Return
84
+
85
+ - **Status:** `PASS` · `FINDINGS` · `BLOCKED` · `NOT_APPLICABLE` — `PASS` when the tree is fully resolved,
86
+ contradictions closed, and the user explicitly confirmed shared understanding (session `aligned`); `FINDINGS`
87
+ when decisions were resolved but blocking questions, unclosed contradictions, or a pending confirmation remain
88
+ (draft/paused); `BLOCKED` when the caller supplied no target artifact or write-authorization state, or the
89
+ interview cannot proceed; `NOT_APPLICABLE` when invoked without Grill context (no plan/PRD to sharpen).
90
+ - **Scope:** the artifact under grill (one-pager / PRD / TechSpec), the decision tree size, and how many nodes
91
+ are resolved vs open.
92
+ - **Evidence:** the facts discovered from repo/rules/intel/docs (with sources) that were used instead of asking.
93
+ - **Artifacts:** decision tree, resolved decisions with the recommended answer and the user's choice, and any
94
+ authorized `.dw/domain/**` / one-pager / PRD updates (with paths).
95
+ - **Decisions:** each resolved decision, the alternative rejected, and why.
96
+ - **Risks:** open decisions, unclosed glossary/code contradictions, and any decision made without full evidence.
97
+ - **Next Step:** either "user to confirm shared understanding" (with the remaining nodes) or "aligned — hand off
98
+ to /dw-plan" — never implement here.
@@ -0,0 +1,56 @@
1
+ # Decision tree — build, order, gate, resume
2
+
3
+ Grill is not a flat questionnaire. It is a walk down a **dependency-ordered decision tree**, so an upstream
4
+ decision is never asked after the downstream decision it constrains.
5
+
6
+ ## Build the tree (before the first question)
7
+
8
+ 1. **Gather open decisions.** Read the plan/PRD and the project's durable facts (`.dw/rules/`, `.dw/intel/` via
9
+ `/dw-intel`, `.dw/constitution.md`, `.dw/domain/**`, current PRDs/TechSpecs, recent git activity). List every
10
+ decision the artifact leaves open. Fold in vocabulary tension from `dw-domain-modeling` — an overloaded or
11
+ fuzzy term is a decision node.
12
+ 2. **Separate facts from decisions.** Discard anything the repo already answers (those are facts to state, not
13
+ ask). What remains is the decision set.
14
+ 3. **Draw dependencies.** For each decision, mark which other decisions must be settled first. "Is refund a
15
+ state or a flag?" blocks "which events transition into it?". A node that constrains others is a **parent**.
16
+ 4. **Topologically order.** Ask parents before children. Independent branches can be walked in any order, but
17
+ within a branch always resolve the blocker first.
18
+
19
+ Record the tree (nodes, dependencies, resolved/open) so the session — or a later `/dw-plan` handoff — can resume
20
+ exactly where it stopped.
21
+
22
+ ## Walk the tree
23
+
24
+ Resolve nodes in dependency order using the per-turn contract (`interview-loop.md`). After each answer, **re-walk
25
+ the tree**: a resolved parent may add, remove, or reshape child nodes. New tension discovered mid-walk becomes a
26
+ new node, inserted in dependency order — not asked immediately if a blocker is still open.
27
+
28
+ ## The shared-understanding gate
29
+
30
+ Declare alignment (and let the caller set the one-pager to `status: aligned`) only when **all** hold:
31
+
32
+ - Every dependency branch is resolved — no open decision node remains.
33
+ - Every glossary/code contradiction surfaced during the grill is closed (a canonical term chosen, or the code
34
+ discrepancy explained).
35
+ - No blocking open decision remains (a non-blocking "revisit later" may be recorded as a remaining decision).
36
+ - The user has **explicitly confirmed** shared understanding — not "sounds good", but an explicit yes to the
37
+ summary of resolved decisions and canonical vocabulary.
38
+
39
+ Present a shared-understanding summary before asking for confirmation: the resolved decisions, the canonical
40
+ terms, and the remaining (non-blocking) decisions with their owners.
41
+
42
+ ## Paused / draft state
43
+
44
+ If the gate is not met — blocking decision open, contradiction unclosed, or the user not yet confirming — **do
45
+ not** declare alignment. Persist a draft/paused state:
46
+
47
+ - Keep the one-pager (or PRD) at its draft/paused status with the decision tree, resolved decisions, and the
48
+ remaining open nodes recorded (the caller performs the write, only if authorized).
49
+ - Return `FINDINGS` with the open nodes as the Next Step, so the next turn resumes the same tree.
50
+
51
+ ## Handoff to /dw-plan
52
+
53
+ An `aligned` session is a handoff contract: the resolved product decisions, their evidence, and the canonical
54
+ vocabulary links. `/dw-plan` consumes this **without re-asking** the resolved decisions — it credits the aligned
55
+ evidence in its coverage matrices and asks only decisions the Grill did not cover (technical decisions may still
56
+ surface during TechSpec). Never re-litigate a decision the user already settled in the grill.