@hegemonart/get-design-done 1.14.7 → 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.
@@ -5,14 +5,14 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "Get Design Done — 5-stage agent-orchestrated design pipeline with 9 connections, handoff-first workflow, bidirectional Figma write-back, 22+ specialized agents, queryable knowledge layer (intel store, dependency analysis, learnings extraction), and a self-improvement loop (reflector, frontmatter + budget feedback, global-skills layer). Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows) and release automation (auto-tag + GitHub Release + release-time smoke test).",
8
- "version": "1.14.7"
8
+ "version": "1.14.8"
9
9
  },
10
10
  "plugins": [
11
11
  {
12
12
  "name": "get-design-done",
13
13
  "source": "./",
14
14
  "description": "Agent-orchestrated 5-stage design pipeline: Brief → Explore → Plan → Design → Verify. 22+ specialized agents, 9 connections (Figma, Refero, Preview, Storybook, Chromatic, Figma Writer, Graphify, Pinterest, Claude Design), Claude Design handoff, bidirectional Figma write-back, and a queryable intel store (.design/intel/) for dependency and learnings queries. Standalone commands: style, darkmode, compare, figma-write, graphify, handoff, analyze-dependencies, skill-manifest, extract-learnings. Embeds NNG heuristics, WCAG thresholds, typographic systems, motion framework, and anti-pattern catalog. Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows) and release automation. Optimization layer (v1.0.4.1, retroactive): gdd-router + gdd-cache-manager skills, PreToolUse budget-enforcer hook, tier-aware agent frontmatter, lazy checker gates, streaming synthesizer, /gdd:warm-cache + /gdd:optimize commands, and cost telemetry at .design/telemetry/costs.jsonl — targeting 50-70% per-task token-cost reduction with no quality-floor regression.",
15
- "version": "1.14.7",
15
+ "version": "1.14.8",
16
16
  "author": {
17
17
  "name": "hegemonart"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "get-design-done",
3
3
  "short_name": "gdd",
4
- "version": "1.14.7",
4
+ "version": "1.14.8",
5
5
  "description": "Agent-orchestrated 5-stage design pipeline: Brief → Explore → Plan → Design → Verify. 22+ specialized agents, 9 connections (Figma, Refero, Preview, Storybook, Chromatic, Figma Writer, Graphify, Pinterest, Claude Design), handoff-first workflow via Claude Design bundles, bidirectional Figma write-back (annotations, Code Connect), queryable intel store (`.design/intel/`) for O(1) design surface lookups, and self-improvement loop (reflector agent, frontmatter + budget feedback, global-skills layer at `~/.claude/gdd/global-skills/`). Standalone commands: style, darkmode, compare, figma-write, graphify, handoff, analyze-dependencies, skill-manifest, extract-learnings, reflect, apply-reflections. Embeds NNG heuristics, WCAG thresholds, typographic systems, motion framework, and anti-pattern catalog. Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows, lint + schema + frontmatter + stale-ref + shellcheck + gitleaks + injection-scan + blocking size-budget) and release automation (auto-tag + GitHub Release + release-time smoke test). Optimization layer (v1.0.4.1, retroactive): gdd-router + gdd-cache-manager skills, PreToolUse budget-enforcer hook, tier-aware agent frontmatter, lazy checker gates, streaming synthesizer, /gdd:warm-cache + /gdd:optimize commands, and cost telemetry at .design/telemetry/costs.jsonl — targeting 50-70% per-task token-cost reduction with no quality-floor regression.",
6
6
  "author": {
7
7
  "name": "hegemonart",
@@ -52,7 +52,11 @@
52
52
  "schema-validation",
53
53
  "cost-optimization",
54
54
  "cache-aware",
55
- "budget"
55
+ "budget",
56
+ "onboarding",
57
+ "first-run",
58
+ "demo",
59
+ "proof-path"
56
60
  ],
57
61
  "skills": ["./skills/"]
58
62
  }
package/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@ All notable changes to get-design-done are documented here. Versions follow [sem
4
4
 
5
5
  ---
6
6
 
7
+ ## [1.14.8] — 2026-04-24
8
+
9
+ ### Added — Phase 14.7 First-Run Proof Path
10
+
11
+ A new user can now install the plugin, run one command, and see GDD inspect their own UI code in under five minutes — with a concrete "first fix" pointer on the way out.
12
+
13
+ - **`/gdd:start` skill** (`skills/start/SKILL.md`) — leaf command with a locked 5-question interview (`reference/start-interview.md`) that collects pain hint, target-area confirmation, budget preference, framework/design-system confirmation, and visual-workflow selection. Writes only `.design/START-REPORT.md` and a temporary `.design/.start-context.json`; never mutates `STATE.md`, `config.json`, or source files.
14
+ - **`detect-ui-root` helper** (`scripts/lib/detect-ui-root.cjs`) — deterministic priority-ordered detector that identifies the user's UI surface across `packages/ui/src/`, `apps/*/components/`, Next.js app-router `app/components/`, Vite `src/components/`, CRA `src/components/`, root `components/`, and Svelte/Remix `src/routes/`. Backend-only repos get a clean diagnostic and exit with zero `.design/` footprint.
15
+ - **`start-findings-engine` helper** (`scripts/lib/start-findings-engine.cjs`) — read-only scanner with seven regex-based detectors (transition-all, will-change-all, tinted-image-outline, scale-on-press-drift, same-radius-nested, missing-reduced-motion-guard, non-root-font-smoothing), budget-bounded walk (fast / balanced / thorough), and a deterministic **safe-fix rubric** that picks exactly one `best_first_proof` per report.
16
+ - **`design-start-writer` agent** (`agents/design-start-writer.md`) — Haiku-tier writer with `allowed-write-paths: [.design/START-REPORT.md]`. Output contract locks seven H2 sections (`What I inspected`, `Three findings`, `Best first proof`, `Suggested next command`, `Visual Proof Readiness`, `Full pipeline path`, `Connections / writeback optional`) plus one trailing machine-readable JSON block that future `/gdd:fast` / `/gdd:do` invocations can consume.
17
+ - **First-run nudge** (`hooks/first-run-nudge.sh`) — SessionStart hook that surfaces one restrained line pointing at `/gdd:start` only when `.design/config.json` is absent, no dismissal flag exists, and no active pipeline stage is in progress. Per-install dismissal lives at `~/.claude/gdd-nudge-dismissed`. Silent-on-failure posture inherited from Phase 13.3.
18
+ - **Regression fixtures** at `test-fixture/baselines/phase-14.7/` (context-input.json, expected-report-shape.md) and `test-fixture/src/ui-detection/` covering Next.js, Vite, CRA, Remix, two monorepo shapes, backend-only, and empty-repo paths.
19
+ - **Plugin keywords** extended with `onboarding`, `first-run`, `demo`, `proof-path`.
20
+
21
+ ### Non-breaking
22
+
23
+ Phase 15 target version unchanged (`v1.15.0`). `v1.14.6` remains reserved for Phase 14.6 (test-coverage-completion); this release does not block or reshape that phase.
24
+
25
+ ### Scope notes
26
+
27
+ - The first-run report **recommends** commands; it never auto-applies fixes. `/gdd:fast` suggestions are printed as ready-to-run text.
28
+ - `/gdd:do` is intentionally **not** surfaced as a suggested command in v1.14.8 (revisit at Phase 15 per Phase 14.7 D-05).
29
+
30
+ ---
31
+
7
32
  ## [1.14.7] — 2026-04-24
8
33
 
9
34
  ### Phase 14.6 — Test Coverage Completion (Phase 12 Wave C closeout)
package/README.md CHANGED
@@ -101,6 +101,16 @@ That's it. The installer writes a `get-design-done` marketplace entry and enable
101
101
 
102
102
  On first Claude Code launch after install, a `SessionStart` bootstrap hook provisions the companion reference library `~/.claude/libs/awesome-design-md` (idempotent — subsequent sessions run `git pull --ff-only`).
103
103
 
104
+ ### First run — `/gdd:start` *(v1.14.7+)*
105
+
106
+ ```
107
+ /gdd:start
108
+ ```
109
+
110
+ Run this once in any frontend repo. The skill asks five short questions, scans your `components/` directory (with framework-aware fallback for `src/components/`, `app/components/`, `packages/ui/`, `apps/*/components/`), and writes `.design/START-REPORT.md` with three concrete findings — each with file:line evidence — plus one `best_first_proof` and a single suggested next command. Takes ≤5 minutes, never touches source code, and never enters the pipeline state machine.
111
+
112
+ A one-line SessionStart nudge surfaces `/gdd:start` in fresh repos; run `/gdd:start --dismiss-nudge` to silence it per install.
113
+
104
114
  ### Non-interactive install (CI, Docker, scripts)
105
115
 
106
116
  ```bash
@@ -525,6 +535,7 @@ All commands use the `/gdd:` namespace.
525
535
 
526
536
  | Command | What it does |
527
537
  |---------|--------------|
538
+ | `/gdd:start [--budget] [--skip-interview] [--dismiss-nudge]` | First-Run Proof Path — scans UI code, emits `.design/START-REPORT.md`, never enters pipeline state |
528
539
  | `/gdd:new-project [--name]` | Initialize project — PROJECT.md + STATE.md + cycle-1 |
529
540
  | `/gdd:new-cycle [<goal>]` | Start new design cycle |
530
541
  | `/gdd:complete-cycle [<note>]` | Archive cycle to `.design/archive/cycle-N/` |
package/SKILL.md CHANGED
@@ -45,6 +45,7 @@ Each stage produces artifacts in `.design/` inside the current project.
45
45
  | `pause [context]` | `get-design-done:gdd-pause` | Write session handoff to `.design/HANDOFF.md` |
46
46
  | `resume` | `get-design-done:gdd-resume` | Restore session context from `.design/HANDOFF.md` and route to next step |
47
47
  | **Lifecycle** | | |
48
+ | `start [--budget <t>] [--skip-interview] [--dismiss-nudge]` | `get-design-done:start` | First-Run Proof Path — scans UI code, returns one concrete first fix. No STATE.md writes. |
48
49
  | `new-project [--name <n>]` | `get-design-done:gdd-new-project` | Initialize project — PROJECT.md + STATE.md + cycle-1 |
49
50
  | `new-cycle [<goal>]` | `get-design-done:gdd-new-cycle` | Start a new design cycle; writes `.design/CYCLES.md` entry |
50
51
  | `complete-cycle [<note>]` | `get-design-done:gdd-complete-cycle` | Archive cycle artifacts to `.design/archive/cycle-N/`; reset STATE.md |
@@ -205,6 +206,7 @@ If `$ARGUMENTS` is a stage or command name — invoke it directly, no state chec
205
206
  /gdd:pause → Skill("get-design-done:gdd-pause")
206
207
  /gdd:resume → Skill("get-design-done:gdd-resume")
207
208
  # --- Lifecycle ---
209
+ /gdd:start → Skill("get-design-done:start") # leaf command, no STATE.md
208
210
  /gdd:new-project → Skill("get-design-done:gdd-new-project")
209
211
  /gdd:new-cycle → Skill("get-design-done:gdd-new-cycle")
210
212
  /gdd:complete-cycle → Skill("get-design-done:gdd-complete-cycle")
@@ -0,0 +1,221 @@
1
+ ---
2
+ name: design-start-writer
3
+ description: "Writes .design/START-REPORT.md — 7 fixed sections plus a machine-readable JSON block. Consumes the findings-engine output, interview answers, and detection result from .design/.start-context.json. Never writes STATE.md. Parameter-free: reads the context JSON path from the prompt and emits the report."
4
+ tools: Read, Write, Grep, Glob
5
+ color: green
6
+
7
+ model: haiku
8
+ default-tier: haiku
9
+ tier-rationale: "Formatting + light synthesis over a bounded ~3KB input; Haiku is the correct tier per Phase 10.1 D-14 (Haiku = writers/formatters with fixed schemas)."
10
+
11
+ parallel-safe: always
12
+ typical-duration-seconds: 10
13
+ reads-only: false
14
+ writes:
15
+ - ".design/START-REPORT.md"
16
+
17
+ allowed-read-paths:
18
+ - ".design/.start-context.json"
19
+ - ".design/START-REPORT.md"
20
+ allowed-write-paths:
21
+ - ".design/START-REPORT.md"
22
+ ---
23
+
24
+ ## Role
25
+
26
+ Write `.design/START-REPORT.md` for `/gdd:start`. The report is the single artifact the user sees after a first-run scan. It must feel like GDD understood the project, not like it printed a generic checklist. One best_first_proof, one suggested next command, no ambiguity.
27
+
28
+ ---
29
+
30
+ ## Required Reading
31
+
32
+ - `.design/.start-context.json` — produced by `skills/start/SKILL.md` before spawning this agent. Contains detection result, interview answers, and the findings-engine output.
33
+
34
+ ## Inputs
35
+
36
+ ```json
37
+ {
38
+ "schema_version": "1.0",
39
+ "detected": {
40
+ "kind": "next-app-router | src-components | monorepo-ui-pkg | ...",
41
+ "path": "relative/path/to/components",
42
+ "framework": "next | vite | cra | ...",
43
+ "design_system": "tailwind | css-modules | ...",
44
+ "confidence": 0.85
45
+ },
46
+ "interview": {
47
+ "pain": "free text or empty",
48
+ "target_area": "relative/path",
49
+ "budget": "fast | balanced | thorough",
50
+ "framework_confirmed": true,
51
+ "design_system_confirmed": true,
52
+ "figma_workflow": "figma | canvas | neither | skip"
53
+ },
54
+ "scan": {
55
+ "findings": [{"id":"F1","category":"transition-all","title":"...","file":"...","line":123,"severity":"minor","evidence":"...","visibleDelta":true,"blastRadius":"single-file"}],
56
+ "bestFirstProofId": "F1",
57
+ "partial": false,
58
+ "inspected": {"files": 42, "root": "..."}
59
+ },
60
+ "generated_at": "2026-04-24T01:00:00Z"
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Output contract
67
+
68
+ Write `.design/START-REPORT.md` exactly matching this shape. **All seven H2 sections must be present, in this order, even if empty.** The JSON block at the very end is mandatory.
69
+
70
+ ```markdown
71
+ # GDD First-Run Report
72
+
73
+ > Generated <generated_at> by `/gdd:start`. This report does not start a pipeline cycle — it is a 0→1 proof path. Run the suggested next command to continue.
74
+
75
+ ## What I inspected
76
+
77
+ - **UI root:** `<detected.path>` (`<detected.kind>`, confidence `<detected.confidence>`)
78
+ - **Framework:** `<detected.framework>` — <one-sentence confirmation or override note>
79
+ - **Design system:** `<detected.design_system>` — <one-sentence note>
80
+ - **Files scanned:** `<scan.inspected.files>`
81
+ - **Pain hint:** <`interview.pain` or "none given">
82
+ - **Budget:** `<interview.budget>` <"(timed out — partial scan)" if `scan.partial`>
83
+
84
+ ## Three findings
85
+
86
+ <For each finding F1..F3, emit:>
87
+
88
+ ### <Fn> — <title>
89
+
90
+ **Severity:** `<severity>` · **Evidence:** `<file>:<line>` · **Blast radius:** `<blastRadius>`
91
+
92
+ <one-sentence plain-English rationale pointing at the evidence line>
93
+
94
+ **Fix sketch:** <one-sentence concrete fix — e.g., "Replace `transition` with `transition-transform` on the wrapper.">
95
+
96
+ <End per-finding block>
97
+
98
+ ## Best first proof
99
+
100
+ **Pick:** `<bestFirstProofId>` — <re-state the finding title>
101
+
102
+ <one paragraph: why this one. Cite the rubric condition that tipped the pick — single-file, non-ambiguous, visible delta, no token migration, low blast radius. If `bestFirstProofId` is null, write: "No finding qualified for a single-command fix under the safe-fix rubric — the report recommends the pipeline entry point below instead.">
103
+
104
+ ## Suggested next command
105
+
106
+ ```bash
107
+ <exact command — one of:>
108
+ /gdd:fast "<concrete description of the single fix>"
109
+ /gdd:brief
110
+ /gdd:scan
111
+ ```
112
+
113
+ <one-line rationale: why this command and not the others.>
114
+
115
+ ## Visual Proof Readiness
116
+
117
+ | Surface | Status | Unlock |
118
+ |---------|--------|--------|
119
+ | Preview MCP | <ok \| unconfigured \| unavailable> | <`/gdd:connections preview` or "already configured"> |
120
+ | Storybook | <…> | <…> |
121
+ | Figma | <…> | <`/gdd:connections figma` or "already configured"> |
122
+ | Canvas (.canvas) | <…> | <…> |
123
+
124
+ <one-line note if `interview.figma_workflow` picked a specific surface — nudge toward that one first.>
125
+
126
+ ## Full pipeline path
127
+
128
+ If you want more than a single fix, the full pipeline would do this on this project: `/gdd:brief` to capture the design problem, `/gdd:explore` to inventory your components and interview for context, `/gdd:plan` to decompose the work, `/gdd:design` to execute, and `/gdd:verify` to score the result. The pipeline writes `.design/STATE.md` and runs across a real design cycle.
129
+
130
+ ## Connections / writeback optional
131
+
132
+ If you want to push design decisions back into Figma, paper.design, pencil.dev, or a Claude Design handoff bundle, run `/gdd:connections` to wire up the surfaces. Writeback is never required — the pipeline runs code-first by default.
133
+
134
+ ---
135
+
136
+ ```json
137
+ {
138
+ "schema_version": "1.0",
139
+ "generated_at": "<ISO-8601>",
140
+ "detected": {...copy verbatim from context...},
141
+ "findings": [...copy findings array with id, title, file, line, severity, category, blast_radius...],
142
+ "best_first_proof": "<bestFirstProofId or null>",
143
+ "suggested_command": { "kind": "fast|brief|scan", "text": "<exact command>" },
144
+ "visual_proof_readiness": { "preview": "...", "storybook": "...", "figma": "...", "canvas": "..." }
145
+ }
146
+ ```
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Section-by-section rules
152
+
153
+ ### What I inspected
154
+
155
+ - Always list the six bullets. Mark "(timed out — partial scan)" only when `scan.partial === true`.
156
+ - Never invent fields — only surface what is in the context JSON.
157
+
158
+ ### Three findings
159
+
160
+ - Emit up to three entries. If fewer than three findings exist, emit what exists and add one italic note below: `> Only <N> finding raised — the engine did not hit its cap.`
161
+ - Every finding block includes the evidence file:line. No finding may omit file:line.
162
+ - Fix sketch is one concrete sentence — not a general principle, not a tutorial.
163
+
164
+ ### Best first proof
165
+
166
+ - Reference `bestFirstProofId` exactly. If it is null, write the fallback paragraph.
167
+ - Cite which rubric conditions the winning finding satisfies.
168
+
169
+ ### Suggested next command
170
+
171
+ - If `bestFirstProofId` is non-null → emit `/gdd:fast "<task>"` where `<task>` is a one-line description tied to the finding's fix sketch.
172
+ - If `bestFirstProofId` is null AND there are findings → emit `/gdd:brief` with rationale "the findings need a design decision the rubric cannot make for you."
173
+ - If no findings at all → emit `/gdd:scan` with rationale "this codebase looks healthy at first glance — a full audit confirms."
174
+
175
+ ### Visual Proof Readiness
176
+
177
+ - Always include all four rows. Unknown surfaces default to `unconfigured`.
178
+ - Check `interview.figma_workflow` — if the user picked `figma`, `canvas`, or `neither`, phrase the unlock line to match.
179
+
180
+ ### Full pipeline path
181
+
182
+ - Keep the paragraph short — one sentence of what the pipeline does plus the command sequence. Do not rephrase per run.
183
+
184
+ ### Connections / writeback optional
185
+
186
+ - Keep the paragraph short. Never assert which surface is best; point at `/gdd:connections`.
187
+
188
+ ---
189
+
190
+ ## JSON block
191
+
192
+ The JSON block at the bottom is the contract future `/gdd:fast` / `/gdd:do` invocations will consume. Shape:
193
+
194
+ ```json
195
+ {
196
+ "schema_version": "1.0",
197
+ "generated_at": "ISO-8601",
198
+ "detected": { "root", "kind", "framework", "design_system", "confidence" },
199
+ "findings": [{ "id", "title", "file", "line", "severity", "category", "blast_radius" }],
200
+ "best_first_proof": "F1" | null,
201
+ "suggested_command": { "kind": "fast" | "brief" | "scan", "text": "/gdd:fast \"...\"" },
202
+ "visual_proof_readiness": { "preview", "storybook", "figma", "canvas" }
203
+ }
204
+ ```
205
+
206
+ - Finding IDs stay stable `F1`..`F3`.
207
+ - `text` is always a ready-to-run command, single-line.
208
+ - All string values are JSON-safe — escape embedded quotes.
209
+
210
+ ---
211
+
212
+ ## Do Not
213
+
214
+ - Do not write `.design/STATE.md`, `.design/config.json`, or any source file.
215
+ - Do not invent findings that are not in the context JSON.
216
+ - Do not re-score or re-rank findings — the engine already picked `bestFirstProofId` deterministically.
217
+ - Do not add marketing prose, emojis, or playful copy.
218
+ - Do not emit more than three findings.
219
+ - Do not omit any of the seven H2 sections — even empty, they must exist for downstream regression fixtures.
220
+
221
+ ## START-WRITER COMPLETE
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env bash
2
+ # get-design-done — first-run nudge (Phase 14.7)
3
+ # SessionStart hook. Silent-on-failure by policy: exits 0 on every error path.
4
+ # Prints exactly one restrained line pointing at /gdd:start when all gates pass,
5
+ # and nothing otherwise.
6
+
7
+ set -u # intentionally no -e: we want to fall through to exit 0
8
+
9
+ # Silent logger — writes nothing by default. Set GDD_NUDGE_DEBUG=1 to enable stderr.
10
+ log() {
11
+ if [ "${GDD_NUDGE_DEBUG:-0}" = "1" ]; then
12
+ printf '[gdd first-run-nudge] %s\n' "$*" >&2
13
+ fi
14
+ }
15
+
16
+ DESIGN_DIR="$(pwd)/.design"
17
+ STATE="${DESIGN_DIR}/STATE.md"
18
+ CONFIG="${DESIGN_DIR}/config.json"
19
+ DISMISS_FLAG="${HOME:-$USERPROFILE}/.claude/gdd-nudge-dismissed"
20
+
21
+ # Gate 1 — repo already has GDD state, suppress.
22
+ has_design_state() {
23
+ [ -f "${CONFIG}" ] || [ -f "${STATE}" ]
24
+ }
25
+
26
+ # Gate 2 — per-install dismissal flag.
27
+ is_dismissed() {
28
+ [ -f "${DISMISS_FLAG}" ]
29
+ }
30
+
31
+ # Gate 3 — STATE.md stage belongs to an active pipeline window.
32
+ # Inherits the shape used by Phase 13.3 update-check.sh.
33
+ read_state_stage() {
34
+ [ -f "${STATE}" ] || { printf ''; return; }
35
+ grep -E '^stage:' "${STATE}" 2>/dev/null | head -n1 | \
36
+ sed -E 's/^stage:[[:space:]]*"?([^"[:space:]]+)"?.*/\1/'
37
+ }
38
+
39
+ is_active_stage() {
40
+ local s
41
+ s="$(read_state_stage)"
42
+ case "${s}" in
43
+ plan|design|verify|executing|discussing) return 0 ;;
44
+ *) return 1 ;;
45
+ esac
46
+ }
47
+
48
+ # Gate 4 — recent session history has a gdd:* command. We cannot reliably read
49
+ # session history from a hook in all runtimes; when the signal is unavailable,
50
+ # treat it as "unknown → not suppressed". This preserves the nudge's
51
+ # usefulness without creating false suppression.
52
+ has_recent_gdd_command() {
53
+ # Placeholder: no portable transcript path exposed to SessionStart hooks today.
54
+ # Keep the function for future wiring; for now always returns non-zero (unknown).
55
+ return 1
56
+ }
57
+
58
+ # MANDATORY sourcing guard: unit tests source this script to test the helper
59
+ # functions without executing the main flow. Non-negotiable.
60
+ if [ "${BASH_SOURCE[0]}" = "$0" ]; then
61
+ if has_design_state; then
62
+ log "design state present — suppress"
63
+ exit 0
64
+ fi
65
+ if is_dismissed; then
66
+ log "dismissal flag present — suppress"
67
+ exit 0
68
+ fi
69
+ if is_active_stage; then
70
+ log "active stage — suppress"
71
+ exit 0
72
+ fi
73
+ if has_recent_gdd_command; then
74
+ log "recent gdd:* command detected — suppress"
75
+ exit 0
76
+ fi
77
+ # All gates passed — emit the locked one-line nudge.
78
+ printf 'Tip: run /gdd:start to let GDD inspect this codebase and suggest one first fix.\n'
79
+ exit 0
80
+ fi
81
+ # When sourced (BASH_SOURCE != $0), fall through with function definitions loaded
82
+ # and without side effects.
package/hooks/hooks.json CHANGED
@@ -16,6 +16,14 @@
16
16
  "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/update-check.sh\""
17
17
  }
18
18
  ]
19
+ },
20
+ {
21
+ "hooks": [
22
+ {
23
+ "type": "command",
24
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/first-run-nudge.sh\""
25
+ }
26
+ ]
19
27
  }
20
28
  ],
21
29
  "PreToolUse": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hegemonart/get-design-done",
3
- "version": "1.14.7",
3
+ "version": "1.14.8",
4
4
  "description": "A Claude Code plugin for systematic design improvement",
5
5
  "author": "Hegemon",
6
6
  "homepage": "https://github.com/hegemonart/get-design-done",
@@ -29,6 +29,7 @@
29
29
  { "name": "model-tiers", "path": "reference/model-tiers.md", "type": "data", "description": "Per-agent default tier map and rationale" },
30
30
  { "name": "figma-sandbox", "path": "reference/figma-sandbox.md", "type": "defaults", "description": "Four Figma plugin-sandbox pitfalls encoded as hard rules" },
31
31
  { "name": "mcp-budget.default", "path": "reference/mcp-budget.default.json", "type": "defaults", "description": "Default MCP per-task budget (calls + consecutive-timeout thresholds)" },
32
- { "name": "protected-paths.default", "path": "reference/protected-paths.default.json","type": "defaults", "description": "Default glob list the plugin refuses to Edit/Write/mutate-via-Bash without override" }
32
+ { "name": "protected-paths.default", "path": "reference/protected-paths.default.json","type": "defaults", "description": "Default glob list the plugin refuses to Edit/Write/mutate-via-Bash without override" },
33
+ { "name": "start-interview", "path": "reference/start-interview.md", "type": "preamble", "description": "Locked 5-question interview consumed by /gdd:start (Phase 14.7) — pain, target-area, budget, framework/DS confirmation, Figma/canvas workflow" }
33
34
  ]
34
35
  }
@@ -0,0 +1,84 @@
1
+ # /gdd:start — Locked 5-Question Interview
2
+
3
+ **Purpose:** collect the minimum signal needed to steer the findings engine without slowing first-run completion past 30 seconds of interview wall-clock. Autodetectable dimensions collapse to a one-key confirmation; genuinely non-derivable dimensions are asked explicitly.
4
+
5
+ **Hard constraint:** v1.14.7 ships this fixed question set. Do not branch, re-order, or insert new questions without an explicit `/gsd-discuss-phase` override captured in a future DISCUSSION.md.
6
+
7
+ ---
8
+
9
+ ## Q1 — Pain point *(required, free text)*
10
+
11
+ **Prompt:**
12
+
13
+ > What's the one design issue you'd most like to see fixed first? One line, ≤120 chars. Examples: "buttons feel jittery on hover", "colors are inconsistent across forms", "mobile nav breaks on the hero", "a11y on our modal".
14
+
15
+ **Default when `--skip-interview`:** empty string (pain hint disabled; scorer runs without boost).
16
+
17
+ **Validation:** max 120 chars; trim leading/trailing whitespace. No charset restrictions — the hint is free text.
18
+
19
+ **Failure posture:** if the user aborts at Q1, abort the whole skill with a one-line pointer to `/gdd:scan`.
20
+
21
+ ---
22
+
23
+ ## Q2 — Target area confirmation *(autodetected, single select)*
24
+
25
+ **Prompt:**
26
+
27
+ > I detected your UI code at `<detected.path>` (`<detected.kind>`, confidence=`<detected.confidence>`). Use this surface? [Yes / choose another path / skip demo]
28
+
29
+ **Default when `--skip-interview`:** accept detected path as-is.
30
+
31
+ **Validation:** if the user picks "choose another path", ask for a repo-relative path and validate that at least one UI-extension file (.tsx/.jsx/.ts/.js/.svelte/.vue) exists inside. Re-prompt on failure. Two rejections → fall back to detected path with a warning.
32
+
33
+ **Failure posture:** if the user picks "skip demo", exit clean with `## START COMPLETE` and no `.design/` footprint.
34
+
35
+ ---
36
+
37
+ ## Q3 — Budget / latency preference *(enum, default=balanced)*
38
+
39
+ **Prompt:**
40
+
41
+ > Report speed vs thoroughness? [fast (≤90s, tokens-only) / balanced (≤3min, default) / thorough (≤5min, includes a11y + motion checks)]
42
+
43
+ **Default when `--skip-interview`:** `balanced`.
44
+
45
+ **Validation:** must be one of `fast | balanced | thorough`. Any other input → re-prompt once, then default to `balanced`.
46
+
47
+ **Failure posture:** n/a — this is non-blocking; defaulting is safe.
48
+
49
+ ---
50
+
51
+ ## Q4 — Framework + design-system confirmation *(autodetected, combined)*
52
+
53
+ **Prompt:**
54
+
55
+ > Detected framework=`<framework>`, design-system=`<design_system>`. Correct? [Yes / override framework / override design-system / skip (use detected)]
56
+
57
+ **Default when `--skip-interview`:** accept both detected values.
58
+
59
+ **Validation:** "override framework" → ask for a free-text label (`next | remix | vite | cra | svelte | vue | astro | solid | unknown`). "override design-system" → same, values (`tailwind | css-modules | vanilla-extract | styled | linaria | emotion | panda | unknown`). Invalid → default to `unknown`.
60
+
61
+ **Failure posture:** n/a — detection output is always available even if the user skips.
62
+
63
+ ---
64
+
65
+ ## Q5 — Figma / canvas workflow *(enum, non-blocking)*
66
+
67
+ **Prompt:**
68
+
69
+ > Do you work with visual references? [Figma / .canvas file / neither / skip]
70
+
71
+ **Default when `--skip-interview`:** `skip`.
72
+
73
+ **Validation:** enum; any other → `skip`.
74
+
75
+ **Effect:** result steers the `Visual Proof Readiness` section only — never gates the happy path or the findings engine. The writer uses this to decide whether to surface `/gdd:connections figma` or `.canvas` guidance.
76
+
77
+ ---
78
+
79
+ ## Do Not
80
+
81
+ - Do not ask for email, org name, team size, or any identity data.
82
+ - Do not ask more than five questions.
83
+ - Do not branch based on Q1 content (adaptive branching is explicitly deferred per the ROADMAP).
84
+ - Do not write STATE.md, config.json, or any source file as a side effect of the interview.
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Component-directory detection helper for /gdd:start (Phase 14.7-02).
5
+ // Deterministic, read-only, pure Node — no LLM, no subprocess, no interactive prompts.
6
+ //
7
+ // Contract:
8
+ // detectUiRoot(cwd) -> { kind, path, confidence, reason } | { kind: "backend-only", path: null, confidence, reason } | null
9
+ //
10
+ // `path` is always relative to cwd and uses forward slashes.
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ const UI_EXT = new Set(['.tsx', '.jsx', '.ts', '.js', '.svelte', '.vue']);
16
+ const BACKEND_DEPS = ['express', 'fastify', 'koa', '@nestjs/core', 'hapi', 'restify'];
17
+ const UI_DEPS = ['react', 'preact', 'vue', 'svelte', 'solid-js', '@remix-run/react', 'next'];
18
+
19
+ function readJsonSafe(p) {
20
+ try {
21
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function hasUiFiles(absDir) {
28
+ let stack;
29
+ try {
30
+ stack = [absDir];
31
+ } catch {
32
+ return false;
33
+ }
34
+ // Bounded BFS — stop at 3 levels deep or after 200 entries to keep fast.
35
+ let checked = 0;
36
+ let depth = 0;
37
+ const maxEntries = 200;
38
+ const maxDepth = 3;
39
+ while (stack.length && checked < maxEntries && depth < maxDepth) {
40
+ const next = [];
41
+ for (const dir of stack) {
42
+ let entries;
43
+ try {
44
+ entries = fs.readdirSync(dir, { withFileTypes: true });
45
+ } catch {
46
+ continue;
47
+ }
48
+ for (const e of entries) {
49
+ if (e.name.startsWith('.') || e.name === 'node_modules') continue;
50
+ const full = path.join(dir, e.name);
51
+ if (e.isFile()) {
52
+ if (UI_EXT.has(path.extname(e.name))) return true;
53
+ checked += 1;
54
+ if (checked >= maxEntries) return false;
55
+ } else if (e.isDirectory()) {
56
+ next.push(full);
57
+ }
58
+ }
59
+ }
60
+ stack = next;
61
+ depth += 1;
62
+ }
63
+ return false;
64
+ }
65
+
66
+ function firstExistingUiDir(cwd, relPath) {
67
+ const abs = path.join(cwd, relPath);
68
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) return null;
69
+ if (!hasUiFiles(abs)) return null;
70
+ return relPath.split(path.sep).join('/');
71
+ }
72
+
73
+ function firstMonorepoAppsComponents(cwd) {
74
+ const appsDir = path.join(cwd, 'apps');
75
+ if (!fs.existsSync(appsDir) || !fs.statSync(appsDir).isDirectory()) return null;
76
+ let children;
77
+ try {
78
+ children = fs.readdirSync(appsDir, { withFileTypes: true });
79
+ } catch {
80
+ return null;
81
+ }
82
+ for (const c of children) {
83
+ if (!c.isDirectory()) continue;
84
+ const rel = `apps/${c.name}/components`;
85
+ const abs = path.join(cwd, rel);
86
+ if (fs.existsSync(abs) && fs.statSync(abs).isDirectory() && hasUiFiles(abs)) {
87
+ return rel;
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+
93
+ function detectFramework(pkg) {
94
+ if (!pkg) return 'unknown';
95
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
96
+ if (deps.next) return 'next';
97
+ if (deps['@remix-run/react'] || deps['@remix-run/node']) return 'remix';
98
+ if (deps['react-scripts']) return 'cra';
99
+ if (deps.vite) return 'vite';
100
+ if (deps.svelte || deps['@sveltejs/kit']) return 'svelte';
101
+ if (deps.vue) return 'vue';
102
+ if (deps.solid) return 'solid';
103
+ if (deps.astro) return 'astro';
104
+ if (deps.react || deps.preact) return 'react';
105
+ return 'unknown';
106
+ }
107
+
108
+ function hasAnyDep(pkg, names) {
109
+ if (!pkg) return false;
110
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
111
+ return names.some((n) => Object.prototype.hasOwnProperty.call(deps, n));
112
+ }
113
+
114
+ function detectUiRoot(cwd) {
115
+ const pkgPath = path.join(cwd, 'package.json');
116
+ const pkg = readJsonSafe(pkgPath);
117
+
118
+ // Priority-ordered checks. First match wins.
119
+ const checks = [
120
+ { rel: 'packages/ui/src', kind: 'monorepo-ui-pkg', confidence: 0.95 },
121
+ { rel: null, custom: firstMonorepoAppsComponents, kind: 'monorepo-apps', confidence: 0.9 },
122
+ { rel: 'app/components', kind: 'next-app-router', confidence: 0.9 },
123
+ { rel: 'src/app/components', kind: 'next-app-router-src', confidence: 0.85 },
124
+ { rel: 'src/components', kind: 'src-components', confidence: 0.85 },
125
+ { rel: 'components', kind: 'root-components', confidence: 0.8 },
126
+ ];
127
+
128
+ for (const c of checks) {
129
+ const found = c.custom ? c.custom(cwd) : firstExistingUiDir(cwd, c.rel);
130
+ if (found) {
131
+ const framework = detectFramework(pkg);
132
+ return {
133
+ kind: c.kind,
134
+ path: found,
135
+ confidence: c.confidence,
136
+ reason: `Found UI files at ${found} (framework=${framework}, kind=${c.kind})`,
137
+ framework,
138
+ };
139
+ }
140
+ }
141
+
142
+ // Routes-based detection — Svelte/Remix sometimes colocate inside routes
143
+ const routesAbs = path.join(cwd, 'src/routes');
144
+ if (fs.existsSync(routesAbs) && fs.statSync(routesAbs).isDirectory() && hasUiFiles(routesAbs)) {
145
+ return {
146
+ kind: 'routes-based',
147
+ path: 'src/routes',
148
+ confidence: 0.7,
149
+ reason: 'Found UI files inline at src/routes — framework colocation pattern',
150
+ framework: detectFramework(pkg),
151
+ };
152
+ }
153
+
154
+ // Backend-only path — package.json present with only backend deps and no UI deps
155
+ if (pkg) {
156
+ const isBackend = hasAnyDep(pkg, BACKEND_DEPS) && !hasAnyDep(pkg, UI_DEPS);
157
+ if (isBackend) {
158
+ return {
159
+ kind: 'backend-only',
160
+ path: null,
161
+ confidence: 0.9,
162
+ reason: `Backend-only repo detected (deps: ${BACKEND_DEPS.filter((d) =>
163
+ Object.prototype.hasOwnProperty.call(
164
+ { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) },
165
+ d
166
+ )
167
+ ).join(', ')}) — frontend-only diagnostic applies`,
168
+ framework: 'backend',
169
+ };
170
+ }
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ module.exports = detectUiRoot;
177
+ module.exports.detectUiRoot = detectUiRoot;
178
+
179
+ if (require.main === module) {
180
+ const cwd = process.argv[2] || process.cwd();
181
+ const result = detectUiRoot(path.resolve(cwd));
182
+ if (result == null) {
183
+ process.stdout.write(JSON.stringify({ kind: null, path: null, confidence: 0, reason: 'No UI directory detected and no backend signal either.' }) + '\n');
184
+ } else {
185
+ process.stdout.write(JSON.stringify(result) + '\n');
186
+ }
187
+ }
@@ -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
+ }
@@ -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