@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/lib/constants.js +6 -4
- package/lib/install-deps.js +22 -1
- package/lib/install-open-design.js +89 -0
- package/package.json +4 -2
- package/scaffold/en/commands/dw-adr.md +49 -11
- package/scaffold/en/commands/dw-analyze-project.md +5 -0
- package/scaffold/en/commands/dw-brainstorm.md +103 -32
- package/scaffold/en/commands/dw-claude-run.md +8 -5
- package/scaffold/en/commands/dw-help.md +3 -1
- package/scaffold/en/commands/dw-open-design.md +324 -0
- package/scaffold/en/commands/dw-plan.md +34 -5
- package/scaffold/en/templates/adr-template.md +8 -4
- package/scaffold/en/templates/idea-onepager.md +44 -2
- package/scaffold/pt-br/commands/dw-adr.md +49 -11
- package/scaffold/pt-br/commands/dw-analyze-project.md +5 -0
- package/scaffold/pt-br/commands/dw-brainstorm.md +105 -32
- package/scaffold/pt-br/commands/dw-claude-run.md +8 -5
- package/scaffold/pt-br/commands/dw-help.md +3 -1
- package/scaffold/pt-br/commands/dw-open-design.md +323 -0
- package/scaffold/pt-br/commands/dw-plan.md +34 -5
- package/scaffold/pt-br/templates/adr-template.md +8 -4
- package/scaffold/pt-br/templates/idea-onepager.md +44 -2
- package/scaffold/scripts/open-design/capture-current.mjs +170 -0
- package/scaffold/scripts/open-design/gate-prototype.mjs +223 -0
- package/scaffold/skill-registry.json +25 -1
- package/scaffold/skills/dw-cli-run/SKILL.md +10 -8
- package/scaffold/skills/dw-domain-modeling/SKILL.md +84 -0
- package/scaffold/skills/dw-domain-modeling/references/adr-policy.md +48 -0
- package/scaffold/skills/dw-domain-modeling/references/glossary-format.md +66 -0
- package/scaffold/skills/dw-grilling/SKILL.md +98 -0
- package/scaffold/skills/dw-grilling/references/decision-tree.md +56 -0
- 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":
|
|
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 (
|
|
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
|
-
**
|
|
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. **
|
|
140
|
-
prefer Workflow → `/workflows`) and gives its **own 0–10** — without trusting the self-score
|
|
141
|
-
|
|
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
|
|
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:** **
|
|
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.
|