@hegemonart/get-design-done 1.14.5 → 1.14.8

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 (41) hide show
  1. package/.claude-plugin/marketplace.json +7 -2
  2. package/.claude-plugin/plugin.json +11 -2
  3. package/CHANGELOG.md +109 -0
  4. package/README.md +28 -0
  5. package/SKILL.md +2 -0
  6. package/agents/design-executor.md +41 -0
  7. package/agents/design-figma-writer.md +61 -1
  8. package/agents/design-start-writer.md +221 -0
  9. package/connections/figma.md +10 -0
  10. package/hooks/first-run-nudge.sh +82 -0
  11. package/hooks/gdd-bash-guard.js +49 -0
  12. package/hooks/gdd-decision-injector.js +196 -0
  13. package/hooks/gdd-mcp-circuit-breaker.js +140 -0
  14. package/hooks/gdd-protected-paths.js +114 -0
  15. package/hooks/hooks.json +44 -0
  16. package/package.json +1 -1
  17. package/reference/cycle-handoff-preamble.md +22 -0
  18. package/reference/figma-sandbox.md +19 -0
  19. package/reference/mcp-budget.default.json +13 -0
  20. package/reference/meta-rules.md +66 -0
  21. package/reference/protected-paths.default.json +18 -0
  22. package/reference/registry.json +35 -0
  23. package/reference/registry.schema.json +52 -0
  24. package/reference/retrieval-contract.md +30 -0
  25. package/reference/schemas/mcp-budget.schema.json +21 -0
  26. package/reference/schemas/protected-paths.schema.json +19 -0
  27. package/reference/shared-preamble.md +6 -57
  28. package/reference/start-interview.md +84 -0
  29. package/scripts/build-intel.cjs +20 -0
  30. package/scripts/injection-patterns.cjs +42 -1
  31. package/scripts/lib/blast-radius.cjs +97 -0
  32. package/scripts/lib/dangerous-patterns.cjs +118 -0
  33. package/scripts/lib/detect-ui-root.cjs +187 -0
  34. package/scripts/lib/glob-match.cjs +57 -0
  35. package/scripts/lib/reference-registry.cjs +101 -0
  36. package/scripts/lib/start-findings-engine.cjs +405 -0
  37. package/skills/pause/SKILL.md +3 -0
  38. package/skills/progress/SKILL.md +2 -0
  39. package/skills/reflect/SKILL.md +2 -0
  40. package/skills/resume/SKILL.md +3 -0
  41. package/skills/start/SKILL.md +166 -0
@@ -0,0 +1,405 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Top-3 findings engine for /gdd:start (Phase 14.7-03).
5
+ //
6
+ // Deterministic, read-only, no LLM, no child_process. Reads the detected UI root,
7
+ // runs a bank of regex-based detectors, applies the D-02 safe-fix rubric to pick
8
+ // exactly one `best_first_proof`, and returns the shape {findings, bestFirstProofId, partial}.
9
+ //
10
+ // Budget tiers (wall-clock cap):
11
+ // fast -> 90_000 ms
12
+ // balanced -> 180_000 ms
13
+ // thorough -> 300_000 ms
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const BUDGET_MS = { fast: 90_000, balanced: 180_000, thorough: 300_000 };
19
+ const UI_EXT = new Set(['.tsx', '.jsx', '.ts', '.js', '.svelte', '.vue', '.css', '.scss']);
20
+ const MAX_FILES = 400;
21
+
22
+ function isCommentLine(ln) {
23
+ const t = ln.trimStart();
24
+ return t.startsWith('//') || t.startsWith('/*') || t.startsWith('*') || t.startsWith('<!--');
25
+ }
26
+
27
+ /* --------------------------- detector registry --------------------------- */
28
+
29
+ // Each detector returns an array of partial findings (without `id`/`blastRadius` assigned).
30
+ // Shape: {category, title, file, line, severity, evidence, visibleDelta, ambiguous, crossFile}
31
+
32
+ const DETECTORS = [
33
+ {
34
+ category: 'transition-all',
35
+ applies: (ext) => ['.tsx', '.jsx', '.ts', '.js', '.svelte', '.vue', '.css', '.scss'].includes(ext),
36
+ run(file, text) {
37
+ const out = [];
38
+ const lines = text.split('\n');
39
+ for (let i = 0; i < lines.length; i += 1) {
40
+ const ln = lines[i];
41
+ if (isCommentLine(ln)) continue;
42
+ if (/transition:\s*all|transition-property:\s*all/.test(ln)) {
43
+ out.push({
44
+ category: 'transition-all',
45
+ title: 'CSS `transition: all` applied',
46
+ file,
47
+ line: i + 1,
48
+ severity: 'minor',
49
+ evidence: ln.trim().slice(0, 160),
50
+ visibleDelta: true,
51
+ ambiguous: false,
52
+ crossFile: false,
53
+ });
54
+ } else if (/\bclassName\s*=\s*["'`][^"'`]*\btransition\b(?!-|\[)/.test(ln)) {
55
+ out.push({
56
+ category: 'transition-all',
57
+ title: 'Tailwind bare `transition` (implicitly all)',
58
+ file,
59
+ line: i + 1,
60
+ severity: 'minor',
61
+ evidence: ln.trim().slice(0, 160),
62
+ visibleDelta: true,
63
+ ambiguous: false,
64
+ crossFile: false,
65
+ });
66
+ }
67
+ }
68
+ return out;
69
+ },
70
+ },
71
+ {
72
+ category: 'will-change-all',
73
+ applies: () => true,
74
+ run(file, text) {
75
+ const out = [];
76
+ const lines = text.split('\n');
77
+ for (let i = 0; i < lines.length; i += 1) {
78
+ if (isCommentLine(lines[i])) continue;
79
+ if (/will-change:\s*all/.test(lines[i])) {
80
+ out.push({
81
+ category: 'will-change-all',
82
+ title: '`will-change: all` GPU hint',
83
+ file,
84
+ line: i + 1,
85
+ severity: 'minor',
86
+ evidence: lines[i].trim().slice(0, 160),
87
+ visibleDelta: false,
88
+ ambiguous: false,
89
+ crossFile: false,
90
+ });
91
+ }
92
+ }
93
+ return out;
94
+ },
95
+ },
96
+ {
97
+ category: 'tinted-image-outline',
98
+ applies: (ext) => ['.tsx', '.jsx', '.ts', '.js', '.svelte', '.vue'].includes(ext),
99
+ run(file, text) {
100
+ const out = [];
101
+ const lines = text.split('\n');
102
+ for (let i = 0; i < lines.length; i += 1) {
103
+ if (isCommentLine(lines[i])) continue;
104
+ if (/<img[^>]*outline-(?:slate|zinc|neutral|gray|stone)-\d+/.test(lines[i])) {
105
+ out.push({
106
+ category: 'tinted-image-outline',
107
+ title: 'Tinted outline on <img>',
108
+ file,
109
+ line: i + 1,
110
+ severity: 'minor',
111
+ evidence: lines[i].trim().slice(0, 160),
112
+ visibleDelta: true,
113
+ ambiguous: false,
114
+ crossFile: false,
115
+ });
116
+ }
117
+ }
118
+ return out;
119
+ },
120
+ },
121
+ {
122
+ category: 'scale-on-press-drift',
123
+ applies: (ext) => ['.tsx', '.jsx', '.ts', '.js', '.svelte', '.vue', '.css', '.scss'].includes(ext),
124
+ run(file, text) {
125
+ const out = [];
126
+ const lines = text.split('\n');
127
+ // Canonical is 0.96 per Phase 15 decisions. 0.95 and 0.97 are drift signals.
128
+ for (let i = 0; i < lines.length; i += 1) {
129
+ if (isCommentLine(lines[i])) continue;
130
+ const m = /(?:active:scale-9[57]|scale\(0\.9[57]\))/.exec(lines[i]);
131
+ if (m) {
132
+ out.push({
133
+ category: 'scale-on-press-drift',
134
+ title: 'Scale-on-press drift from canonical 0.96',
135
+ file,
136
+ line: i + 1,
137
+ severity: 'minor',
138
+ evidence: lines[i].trim().slice(0, 160),
139
+ visibleDelta: true,
140
+ ambiguous: false,
141
+ crossFile: false,
142
+ });
143
+ }
144
+ }
145
+ return out;
146
+ },
147
+ },
148
+ {
149
+ category: 'same-radius-nested',
150
+ applies: (ext) => ['.tsx', '.jsx', '.ts', '.js', '.svelte', '.vue'].includes(ext),
151
+ run(file, text) {
152
+ const out = [];
153
+ // Multiline: Tailwind parent rounded-X wrapping a child with the same rounded-X.
154
+ // Cheap approximation — single-line scan for parent+child pattern on same line.
155
+ const lines = text.split('\n');
156
+ for (let i = 0; i < lines.length; i += 1) {
157
+ if (isCommentLine(lines[i])) continue;
158
+ const m = /(rounded-[a-z0-9-]+)[^"'`]*["'`][^>]*>\s*<[^>]*\1/.exec(lines[i]);
159
+ if (m) {
160
+ out.push({
161
+ category: 'same-radius-nested',
162
+ title: 'Same border-radius on nested surfaces',
163
+ file,
164
+ line: i + 1,
165
+ severity: 'minor',
166
+ evidence: lines[i].trim().slice(0, 160),
167
+ visibleDelta: true,
168
+ ambiguous: false,
169
+ crossFile: false,
170
+ });
171
+ }
172
+ }
173
+ return out;
174
+ },
175
+ },
176
+ {
177
+ category: 'missing-reduced-motion-guard',
178
+ applies: (ext) => ['.tsx', '.jsx', '.ts', '.js'].includes(ext),
179
+ run(file, text) {
180
+ if (!/framer-motion/.test(text)) return [];
181
+ if (/useReducedMotion\s*\(/.test(text)) return [];
182
+ // One finding per file (not per line) — this is a file-level omission.
183
+ const lineIdx = text.split('\n').findIndex((ln) => /framer-motion/.test(ln));
184
+ return [
185
+ {
186
+ category: 'missing-reduced-motion-guard',
187
+ title: 'framer-motion imported without `useReducedMotion()` guard',
188
+ file,
189
+ line: Math.max(1, lineIdx + 1),
190
+ severity: 'minor',
191
+ evidence: 'framer-motion imported; no `useReducedMotion()` reference in file',
192
+ visibleDelta: false,
193
+ ambiguous: false,
194
+ crossFile: true,
195
+ },
196
+ ];
197
+ },
198
+ },
199
+ {
200
+ category: 'non-root-font-smoothing',
201
+ applies: (ext) => ['.css', '.scss'].includes(ext),
202
+ run(file, text) {
203
+ const out = [];
204
+ const lines = text.split('\n');
205
+ // Find `-webkit-font-smoothing:` inside a block that isn't html/body/:root
206
+ let currentSelector = '';
207
+ for (let i = 0; i < lines.length; i += 1) {
208
+ const open = /^([^{]+?)\s*\{/.exec(lines[i]);
209
+ if (open) currentSelector = open[1].trim();
210
+ if (/-webkit-font-smoothing:/.test(lines[i])) {
211
+ if (!/^(?:html|body|:root)\b/.test(currentSelector)) {
212
+ out.push({
213
+ category: 'non-root-font-smoothing',
214
+ title: '`-webkit-font-smoothing` set outside html/body/:root',
215
+ file,
216
+ line: i + 1,
217
+ severity: 'minor',
218
+ evidence: lines[i].trim().slice(0, 160),
219
+ visibleDelta: false,
220
+ ambiguous: false,
221
+ crossFile: false,
222
+ });
223
+ }
224
+ }
225
+ }
226
+ return out;
227
+ },
228
+ },
229
+ ];
230
+
231
+ /* --------------------------- scoring rubric ---------------------------- */
232
+
233
+ // Phase 14.7 D-02 — a finding qualifies for /gdd:fast if all five hold.
234
+ function qualifiesAsSafeFix(f) {
235
+ // 1. single file
236
+ const singleFile = !f.crossFile;
237
+ // 2. ≤2 affected selectors (proxy: not a cross-file finding + non-ambiguous)
238
+ const selectorsOk = true; // engine emits ≤2 lines per file per detector by design
239
+ // 3. no shared-token migration — approximated by the category allowlist below
240
+ const TOKEN_SAFE_CATEGORIES = new Set([
241
+ 'transition-all',
242
+ 'will-change-all',
243
+ 'tinted-image-outline',
244
+ 'scale-on-press-drift',
245
+ 'same-radius-nested',
246
+ 'non-root-font-smoothing',
247
+ ]);
248
+ const tokenOk = TOKEN_SAFE_CATEGORIES.has(f.category);
249
+ // 4. not ambiguous
250
+ const unambiguous = !f.ambiguous;
251
+ // 5. likely visible delta
252
+ const visible = !!f.visibleDelta;
253
+ return singleFile && selectorsOk && tokenOk && unambiguous && visible;
254
+ }
255
+
256
+ function scoreFinding(f, painHint) {
257
+ const sevWeight = { major: 1.0, minor: 0.7, info: 0.4 }[f.severity] || 0.5;
258
+ const visibility = f.visibleDelta ? 1.0 : 0.5;
259
+ const blast = f.crossFile ? 0.6 : 1.0; // more blast => lower score
260
+ let score = sevWeight * visibility * blast;
261
+ if (painHint && matchesPainHint(f, painHint)) score *= 1.2;
262
+ return score;
263
+ }
264
+
265
+ function matchesPainHint(f, hint) {
266
+ const h = String(hint).toLowerCase();
267
+ const map = {
268
+ motion: ['transition-all', 'scale-on-press-drift', 'missing-reduced-motion-guard', 'will-change-all'],
269
+ animation: ['transition-all', 'scale-on-press-drift', 'missing-reduced-motion-guard', 'will-change-all'],
270
+ a11y: ['missing-reduced-motion-guard'],
271
+ accessibility: ['missing-reduced-motion-guard'],
272
+ color: ['tinted-image-outline'],
273
+ radius: ['same-radius-nested'],
274
+ corners: ['same-radius-nested'],
275
+ typography: ['non-root-font-smoothing'],
276
+ font: ['non-root-font-smoothing'],
277
+ };
278
+ for (const key of Object.keys(map)) {
279
+ if (h.includes(key) && map[key].includes(f.category)) return true;
280
+ }
281
+ return false;
282
+ }
283
+
284
+ function pickBestFirstProof(findings, painHint) {
285
+ const candidates = findings.filter(qualifiesAsSafeFix);
286
+ if (candidates.length === 0) return null;
287
+ const ranked = [...candidates].sort((a, b) => {
288
+ const sb = scoreFinding(b, painHint);
289
+ const sa = scoreFinding(a, painHint);
290
+ if (sb !== sa) return sb - sa;
291
+ // tiebreak by file length, then alphabetical
292
+ if (a.file.length !== b.file.length) return a.file.length - b.file.length;
293
+ return a.file.localeCompare(b.file);
294
+ });
295
+ return ranked[0].id;
296
+ }
297
+
298
+ /* --------------------------- file walker --------------------------- */
299
+
300
+ function walkUiFiles(root, maxFiles, deadline) {
301
+ const result = [];
302
+ const stack = [root];
303
+ while (stack.length && result.length < maxFiles) {
304
+ if (Date.now() > deadline) return { files: result, partial: true };
305
+ const dir = stack.pop();
306
+ let entries;
307
+ try {
308
+ entries = fs.readdirSync(dir, { withFileTypes: true });
309
+ } catch {
310
+ continue;
311
+ }
312
+ for (const e of entries) {
313
+ if (e.name.startsWith('.') || e.name === 'node_modules') continue;
314
+ const full = path.join(dir, e.name);
315
+ if (e.isDirectory()) stack.push(full);
316
+ else if (e.isFile() && UI_EXT.has(path.extname(e.name))) {
317
+ result.push(full);
318
+ if (result.length >= maxFiles) break;
319
+ }
320
+ }
321
+ }
322
+ return { files: result, partial: false };
323
+ }
324
+
325
+ /* --------------------------- public scan() --------------------------- */
326
+
327
+ function scan({ root, budget = 'balanced', painHint = '', rootCwd }) {
328
+ if (!root) return { findings: [], bestFirstProofId: null, partial: false };
329
+ const budgetMs = BUDGET_MS[budget] || BUDGET_MS.balanced;
330
+ const deadline = Date.now() + budgetMs;
331
+ const absRoot = path.isAbsolute(root) ? root : path.resolve(rootCwd || process.cwd(), root);
332
+ const { files, partial: walkPartial } = walkUiFiles(absRoot, MAX_FILES, deadline);
333
+
334
+ const raw = [];
335
+ let timedOut = walkPartial;
336
+ for (const f of files) {
337
+ if (Date.now() > deadline) {
338
+ timedOut = true;
339
+ break;
340
+ }
341
+ let text;
342
+ try {
343
+ text = fs.readFileSync(f, 'utf8');
344
+ } catch {
345
+ continue;
346
+ }
347
+ const ext = path.extname(f);
348
+ const rel = path.relative(rootCwd || process.cwd(), f).split(path.sep).join('/');
349
+ for (const det of DETECTORS) {
350
+ if (!det.applies(ext)) continue;
351
+ raw.push(...det.run(rel, text));
352
+ }
353
+ }
354
+
355
+ // Assign stable IDs after sorting by category+file+line for determinism.
356
+ raw.sort((a, b) => {
357
+ if (a.category !== b.category) return a.category.localeCompare(b.category);
358
+ if (a.file !== b.file) return a.file.localeCompare(b.file);
359
+ return a.line - b.line;
360
+ });
361
+
362
+ // Cap raw at 10 for upstream consumers; pick top 3 for the report.
363
+ const capped = raw.slice(0, 10).map((f, i) => ({ ...f, id: `R${i + 1}`, blastRadius: f.crossFile ? 'cross-file' : 'single-file' }));
364
+ const topThree = rankTopThree(capped, painHint).map((f, i) => ({ ...f, id: `F${i + 1}` }));
365
+ const bestFirstProofId = pickBestFirstProof(topThree, painHint);
366
+
367
+ return {
368
+ findings: topThree,
369
+ bestFirstProofId,
370
+ partial: timedOut,
371
+ inspected: { files: files.length, root: absRoot },
372
+ };
373
+ }
374
+
375
+ function rankTopThree(findings, painHint) {
376
+ const sorted = [...findings].sort((a, b) => {
377
+ const sb = scoreFinding(b, painHint);
378
+ const sa = scoreFinding(a, painHint);
379
+ if (sb !== sa) return sb - sa;
380
+ if (a.file !== b.file) return a.file.localeCompare(b.file);
381
+ return a.line - b.line;
382
+ });
383
+ return sorted.slice(0, 3);
384
+ }
385
+
386
+ module.exports = { scan, qualifiesAsSafeFix, pickBestFirstProof };
387
+
388
+ /* --------------------------- CLI --------------------------- */
389
+
390
+ if (require.main === module) {
391
+ const args = process.argv.slice(2);
392
+ const opts = { root: null, budget: 'balanced', painHint: '' };
393
+ for (let i = 0; i < args.length; i += 1) {
394
+ const a = args[i];
395
+ if (a === '--root') opts.root = args[++i];
396
+ else if (a === '--budget') opts.budget = args[++i];
397
+ else if (a === '--pain') opts.painHint = args[++i];
398
+ }
399
+ if (!opts.root) {
400
+ process.stderr.write('usage: start-findings-engine --root <path> [--budget fast|balanced|thorough] [--pain "<hint>"]\n');
401
+ process.exit(2);
402
+ }
403
+ const result = scan({ ...opts, rootCwd: process.cwd() });
404
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
405
+ }
@@ -5,6 +5,9 @@ argument-hint: "[context note]"
5
5
  tools: Read, Write, AskUserQuestion
6
6
  ---
7
7
 
8
+ @reference/retrieval-contract.md
9
+ @reference/cycle-handoff-preamble.md
10
+
8
11
  # /gdd:pause
9
12
 
10
13
  Captures enough state that a killed or stopped session can resume cleanly via `/gdd:resume`.
@@ -5,6 +5,8 @@ argument-hint: "[--forensic]"
5
5
  tools: Read, Bash, Grep, Glob
6
6
  ---
7
7
 
8
+ @reference/retrieval-contract.md
9
+
8
10
  # /gdd:progress
9
11
 
10
12
  **Role:** Show current position in the pipeline and recommend the next action. With `--forensic`, run a 6-check integrity audit.
@@ -5,6 +5,8 @@ argument-hint: "[--dry-run] [--cycle <slug>]"
5
5
  tools: Read, Write, Task
6
6
  ---
7
7
 
8
+ @reference/retrieval-contract.md
9
+
8
10
  # /gdd:reflect
9
11
 
10
12
  Run `design-reflector` on demand against the current (or specified) cycle. Produces `.design/reflections/<cycle-slug>.md` with numbered improvement proposals. Every proposal requires explicit user review — nothing is auto-applied.
@@ -5,6 +5,9 @@ argument-hint: ""
5
5
  tools: Read, Write, Bash, Glob
6
6
  ---
7
7
 
8
+ @reference/retrieval-contract.md
9
+ @reference/cycle-handoff-preamble.md
10
+
8
11
  # /gdd:resume
9
12
 
10
13
  Inverse of `/gdd:pause`. Reads the handoff file, prints a clear "you were here" summary, and routes to the next command.
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: start
3
+ description: "First-Run Proof Path — one command that scans your UI code and returns one concrete first fix. Leaf command, no STATE.md writes, no pipeline entry. Writes .design/START-REPORT.md and exits."
4
+ argument-hint: "[--budget <fast|balanced|thorough>] [--skip-interview] [--dismiss-nudge]"
5
+ tools: Read, Grep, Glob, Bash, Write, Task
6
+ ---
7
+
8
+ # Get Design Done — /gdd:start
9
+
10
+ **Role:** the canonical 0→1 proof path. A new user runs `/gdd:start`, answers five short questions, and receives `.design/START-REPORT.md` with three concrete findings in the user's own code, one `best_first_proof` selected by a deterministic rubric, and a single next command to run.
11
+
12
+ **Non-goals** (do not do any of these):
13
+
14
+ - Do NOT write or mutate `.design/STATE.md`.
15
+ - Do NOT enter the pipeline state machine.
16
+ - Do NOT modify source code.
17
+ - Do NOT auto-install MCPs or run `/gdd:connections`.
18
+ - Do NOT capture before/after screenshots — that belongs to the full pipeline.
19
+
20
+ ---
21
+
22
+ ## When to use
23
+
24
+ - First time opening a repo with the get-design-done plugin installed.
25
+ - The user wants a single proof-of-value pass without committing to the pipeline.
26
+
27
+ ## When NOT to use
28
+
29
+ - `.design/STATE.md` already exists — route to `/gdd:progress` instead.
30
+ - User asked for a full audit — route to `/gdd:scan`.
31
+ - User asked to fix a specific file — route to `/gdd:fast`.
32
+
33
+ ---
34
+
35
+ ## Arguments
36
+
37
+ | Flag | Effect |
38
+ |------|--------|
39
+ | `--budget fast` | 90-second wall-clock cap on the findings scan. Skips thorough detectors. |
40
+ | `--budget balanced` *(default)* | 3-minute wall-clock cap. All detectors, bounded file walk. |
41
+ | `--budget thorough` | 5-minute wall-clock cap. Used only when the user opts in. |
42
+ | `--skip-interview` | Skip the 5-question interview; use sane defaults (pain=unspecified, area=detected, budget=balanced, framework=detected, figma=skip). |
43
+ | `--dismiss-nudge` | Touch `~/.claude/gdd-nudge-dismissed` and exit. Does not run the scan. |
44
+
45
+ ---
46
+
47
+ ## Step 0 — Dismiss-only shortcut
48
+
49
+ If invoked with `--dismiss-nudge`:
50
+
51
+ 1. `touch ~/.claude/gdd-nudge-dismissed` (Windows: equivalent). Ignore errors silently.
52
+ 2. Print exactly: `Nudge dismissed. Delete ~/.claude/gdd-nudge-dismissed to re-enable.`
53
+ 3. Exit with `## START COMPLETE` marker.
54
+
55
+ Do not proceed to any other step.
56
+
57
+ ---
58
+
59
+ ## Step 1 — Detect UI root
60
+
61
+ Run the detector:
62
+
63
+ ```bash
64
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/detect-ui-root.cjs" "$(pwd)"
65
+ ```
66
+
67
+ Capture the JSON output. Branches:
68
+
69
+ - `kind: "backend-only"` → print the frontend-only diagnostic below, write nothing, exit with `## START COMPLETE`. The diagnostic copy is:
70
+ > `/gdd:start` is for frontend codebases. This repo looks backend-only (detected `<framework>`). The plugin can still help with design references and component libraries imported by your clients — but there is no UI surface here to scan. Exiting without creating `.design/`.
71
+ - `kind: null` (no package.json, no UI dir) → print a short "Nothing recognizable here — point me at a frontend repo and try again." and exit.
72
+ - Any other `kind` → proceed with `detected.path` as the scan root.
73
+
74
+ ---
75
+
76
+ ## Step 2 — Run the 5-question interview
77
+
78
+ Read `reference/start-interview.md` for the exact question copy, defaults, and validation rules.
79
+
80
+ If `--skip-interview`, skip this step and use the defaults documented in that file.
81
+
82
+ Otherwise, ask the five questions in order using `AskUserQuestion`:
83
+
84
+ 1. Pain point (text, required, single-line cap 120 chars)
85
+ 2. Target area confirmation (detected path)
86
+ 3. Budget / latency preference (enum: fast / balanced / thorough)
87
+ 4. Framework + design-system confirmation (from detection)
88
+ 5. Figma / canvas workflow (enum: figma / canvas / neither / skip)
89
+
90
+ Any early exit at Q1 → abort with a one-line pointer to `/gdd:scan`.
91
+
92
+ Store the answers + detection result in `.design/.start-context.json`:
93
+
94
+ ```json
95
+ {
96
+ "schema_version": "1.0",
97
+ "detected": { "kind": "...", "path": "...", "framework": "...", "design_system": "...", "confidence": 0.85 },
98
+ "interview": { "pain": "...", "target_area": "...", "budget": "balanced", "framework_confirmed": true, "design_system_confirmed": true, "figma_workflow": "skip" },
99
+ "generated_at": "<ISO-8601>"
100
+ }
101
+ ```
102
+
103
+ `.design/` is created here for the first time. `.design/STATE.md` is NOT written.
104
+
105
+ ---
106
+
107
+ ## Step 3 — Scan findings
108
+
109
+ Run the findings engine:
110
+
111
+ ```bash
112
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/start-findings-engine.cjs" \
113
+ --root "<detected.path>" \
114
+ --budget "<budget>" \
115
+ --pain "<pain_point>"
116
+ ```
117
+
118
+ Capture the JSON. The output carries at most three findings, each with stable IDs `F1`..`F3`, plus `bestFirstProofId` (may be null).
119
+
120
+ Append the engine output to `.design/.start-context.json` under a `scan` key.
121
+
122
+ ---
123
+
124
+ ## Step 4 — Spawn the writer
125
+
126
+ Dispatch `Task` with:
127
+
128
+ - `subagent_type: design-start-writer`
129
+ - `description: "Write .design/START-REPORT.md"`
130
+ - `prompt:` a short instruction pointing the agent at `.design/.start-context.json` and asking it to emit the report per its Output contract. Include a reminder that it must produce exactly 7 H2 sections plus the JSON block, and must not write `STATE.md`.
131
+
132
+ Wait for the agent to complete. The agent writes `.design/START-REPORT.md`.
133
+
134
+ ---
135
+
136
+ ## Step 5 — Print the handoff
137
+
138
+ Read the final line of `.design/START-REPORT.md` to capture the suggested command.
139
+
140
+ Print exactly (one line, no emoji):
141
+
142
+ ```
143
+ Report written to .design/START-REPORT.md. Next: run <suggested_command> to see the first proof.
144
+ ```
145
+
146
+ If `bestFirstProofId` was null, the suggested command is `/gdd:brief` (the default fallback).
147
+
148
+ Emit `## START COMPLETE` and exit.
149
+
150
+ ---
151
+
152
+ ## Failure handling
153
+
154
+ Every error path exits with `## START COMPLETE` and a one-line pointer. Do not half-write files: if the writer agent fails, keep `.design/.start-context.json` and tell the user they can rerun. Do not delete `.design/` unless it was empty before the run.
155
+
156
+ ---
157
+
158
+ ## Do Not
159
+
160
+ - Do not write or mutate `.design/STATE.md`.
161
+ - Do not modify source code.
162
+ - Do not auto-install MCPs or write to `.design/config.json`.
163
+ - Do not take more than the budgeted wall-clock — let the engine truncate findings rather than hang.
164
+ - Do not invent findings — the findings engine output is the sole source of truth.
165
+
166
+ ## START COMPLETE