jdi-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/AGENTS.md +209 -0
  2. package/ARCHITECTURE.md +210 -0
  3. package/COMMANDS.md +241 -0
  4. package/CREATE-EXAMPLE.md +385 -0
  5. package/CREATE.md +315 -0
  6. package/EXTENSION.md +141 -0
  7. package/LICENSE +21 -0
  8. package/MEMORY.md +471 -0
  9. package/PORTABILITY.md +438 -0
  10. package/README.md +789 -0
  11. package/bin/git-hooks/post-commit +16 -0
  12. package/bin/git-hooks/pre-commit +21 -0
  13. package/bin/jdi-build.ps1 +381 -0
  14. package/bin/jdi-build.sh +332 -0
  15. package/bin/jdi-doctor.ps1 +403 -0
  16. package/bin/jdi-doctor.sh +400 -0
  17. package/bin/jdi-install-caveman.ps1 +97 -0
  18. package/bin/jdi-install-caveman.sh +99 -0
  19. package/bin/jdi-install-playwright.ps1 +319 -0
  20. package/bin/jdi-install-playwright.sh +284 -0
  21. package/bin/jdi-install.ps1 +154 -0
  22. package/bin/jdi-install.sh +132 -0
  23. package/bin/jdi-uninstall.ps1 +309 -0
  24. package/bin/jdi-uninstall.sh +264 -0
  25. package/bin/jdi-update.ps1 +215 -0
  26. package/bin/jdi-update.sh +209 -0
  27. package/bin/jdi.js +460 -0
  28. package/bin/lib/jdi-monitor.ps1 +66 -0
  29. package/bin/lib/jdi-monitor.sh +74 -0
  30. package/bin/lib/jdi-truncate.ps1 +96 -0
  31. package/bin/lib/jdi-truncate.sh +99 -0
  32. package/bin/lib/ui.js +197 -0
  33. package/core/agents/jdi-adopter.md +465 -0
  34. package/core/agents/jdi-architect.md +894 -0
  35. package/core/agents/jdi-asker.md +153 -0
  36. package/core/agents/jdi-bootstrap.md +247 -0
  37. package/core/agents/jdi-planner.md +254 -0
  38. package/core/agents/jdi-researcher.md +303 -0
  39. package/core/commands/jdi-adopt.md +155 -0
  40. package/core/commands/jdi-bootstrap.md +81 -0
  41. package/core/commands/jdi-create.md +80 -0
  42. package/core/commands/jdi-discuss.md +80 -0
  43. package/core/commands/jdi-do.md +200 -0
  44. package/core/commands/jdi-loop.md +315 -0
  45. package/core/commands/jdi-new.md +131 -0
  46. package/core/commands/jdi-plan.md +73 -0
  47. package/core/commands/jdi-ship.md +146 -0
  48. package/core/commands/jdi-verify.md +159 -0
  49. package/core/skills/clean-code/SKILL.md +261 -0
  50. package/core/skills/dry/SKILL.md +150 -0
  51. package/core/skills/frontend-rules/SKILL.md +386 -0
  52. package/core/skills/frontend-validator/SKILL.md +567 -0
  53. package/core/skills/kiss/SKILL.md +178 -0
  54. package/core/skills/solid/SKILL.md +281 -0
  55. package/core/skills/yagni/SKILL.md +207 -0
  56. package/core/templates/agent.md +72 -0
  57. package/core/templates/doer-specialist.md +216 -0
  58. package/core/templates/reviewer-specialist.md +405 -0
  59. package/core/templates/skill.md +66 -0
  60. package/package.json +70 -0
  61. package/runtimes/antigravity/agents.md +74 -0
  62. package/runtimes/antigravity/skills/clean-code/SKILL.md +252 -0
  63. package/runtimes/antigravity/skills/dry/SKILL.md +141 -0
  64. package/runtimes/antigravity/skills/frontend-rules/SKILL.md +376 -0
  65. package/runtimes/antigravity/skills/frontend-validator/SKILL.md +559 -0
  66. package/runtimes/antigravity/skills/jdi-adopt/SKILL.md +155 -0
  67. package/runtimes/antigravity/skills/jdi-adopter/SKILL.md +436 -0
  68. package/runtimes/antigravity/skills/jdi-architect/SKILL.md +872 -0
  69. package/runtimes/antigravity/skills/jdi-asker/SKILL.md +125 -0
  70. package/runtimes/antigravity/skills/jdi-asker/references/context-template.md +34 -0
  71. package/runtimes/antigravity/skills/jdi-asker/references/decision-format.md +19 -0
  72. package/runtimes/antigravity/skills/jdi-asker/scripts/find_phase_dir.sh +25 -0
  73. package/runtimes/antigravity/skills/jdi-bootstrap/SKILL.md +81 -0
  74. package/runtimes/antigravity/skills/jdi-create/SKILL.md +80 -0
  75. package/runtimes/antigravity/skills/jdi-discuss/SKILL.md +80 -0
  76. package/runtimes/antigravity/skills/jdi-discuss/scripts/run_command.sh +62 -0
  77. package/runtimes/antigravity/skills/jdi-do/SKILL.md +200 -0
  78. package/runtimes/antigravity/skills/jdi-loop/SKILL.md +315 -0
  79. package/runtimes/antigravity/skills/jdi-new/SKILL.md +131 -0
  80. package/runtimes/antigravity/skills/jdi-plan/SKILL.md +73 -0
  81. package/runtimes/antigravity/skills/jdi-planner/SKILL.md +225 -0
  82. package/runtimes/antigravity/skills/jdi-researcher/SKILL.md +274 -0
  83. package/runtimes/antigravity/skills/jdi-ship/SKILL.md +146 -0
  84. package/runtimes/antigravity/skills/jdi-verify/SKILL.md +159 -0
  85. package/runtimes/antigravity/skills/kiss/SKILL.md +169 -0
  86. package/runtimes/antigravity/skills/solid/SKILL.md +272 -0
  87. package/runtimes/antigravity/skills/yagni/SKILL.md +198 -0
  88. package/runtimes/claude/CLAUDE.md +91 -0
  89. package/runtimes/claude/agents/jdi-adopter.md +430 -0
  90. package/runtimes/claude/agents/jdi-architect.md +864 -0
  91. package/runtimes/claude/agents/jdi-asker.md +119 -0
  92. package/runtimes/claude/agents/jdi-bootstrap.md +213 -0
  93. package/runtimes/claude/agents/jdi-planner.md +221 -0
  94. package/runtimes/claude/agents/jdi-researcher.md +269 -0
  95. package/runtimes/claude/commands/jdi-adopt.md +155 -0
  96. package/runtimes/claude/commands/jdi-bootstrap.md +81 -0
  97. package/runtimes/claude/commands/jdi-create.md +80 -0
  98. package/runtimes/claude/commands/jdi-discuss.md +80 -0
  99. package/runtimes/claude/commands/jdi-do.md +200 -0
  100. package/runtimes/claude/commands/jdi-loop.md +315 -0
  101. package/runtimes/claude/commands/jdi-new.md +131 -0
  102. package/runtimes/claude/commands/jdi-plan.md +73 -0
  103. package/runtimes/claude/commands/jdi-ship.md +146 -0
  104. package/runtimes/claude/commands/jdi-verify.md +159 -0
  105. package/runtimes/claude/settings.example.json +132 -0
  106. package/runtimes/claude/skills/clean-code/SKILL.md +247 -0
  107. package/runtimes/claude/skills/dry/SKILL.md +136 -0
  108. package/runtimes/claude/skills/frontend-rules/SKILL.md +369 -0
  109. package/runtimes/claude/skills/frontend-validator/SKILL.md +553 -0
  110. package/runtimes/claude/skills/kiss/SKILL.md +164 -0
  111. package/runtimes/claude/skills/solid/SKILL.md +267 -0
  112. package/runtimes/claude/skills/yagni/SKILL.md +193 -0
  113. package/runtimes/copilot/agents/jdi-adopter.agent.md +430 -0
  114. package/runtimes/copilot/agents/jdi-architect.agent.md +864 -0
  115. package/runtimes/copilot/agents/jdi-asker.agent.md +119 -0
  116. package/runtimes/copilot/agents/jdi-bootstrap.agent.md +213 -0
  117. package/runtimes/copilot/agents/jdi-planner.agent.md +221 -0
  118. package/runtimes/copilot/agents/jdi-researcher.agent.md +269 -0
  119. package/runtimes/copilot/copilot-instructions.md +80 -0
  120. package/runtimes/copilot/prompts/jdi-adopt.prompt.md +155 -0
  121. package/runtimes/copilot/prompts/jdi-bootstrap.prompt.md +81 -0
  122. package/runtimes/copilot/prompts/jdi-create.prompt.md +80 -0
  123. package/runtimes/copilot/prompts/jdi-discuss.prompt.md +80 -0
  124. package/runtimes/copilot/prompts/jdi-do.prompt.md +200 -0
  125. package/runtimes/copilot/prompts/jdi-loop.prompt.md +315 -0
  126. package/runtimes/copilot/prompts/jdi-new.prompt.md +131 -0
  127. package/runtimes/copilot/prompts/jdi-plan.prompt.md +73 -0
  128. package/runtimes/copilot/prompts/jdi-ship.prompt.md +146 -0
  129. package/runtimes/copilot/prompts/jdi-verify.prompt.md +159 -0
  130. package/runtimes/opencode/AGENTS.md +87 -0
  131. package/runtimes/opencode/agents/jdi-adopter.md +434 -0
  132. package/runtimes/opencode/agents/jdi-architect.md +861 -0
  133. package/runtimes/opencode/agents/jdi-asker.md +123 -0
  134. package/runtimes/opencode/agents/jdi-bootstrap.md +217 -0
  135. package/runtimes/opencode/agents/jdi-planner.md +225 -0
  136. package/runtimes/opencode/agents/jdi-researcher.md +273 -0
  137. package/runtimes/opencode/commands/jdi-adopt.md +155 -0
  138. package/runtimes/opencode/commands/jdi-bootstrap.md +81 -0
  139. package/runtimes/opencode/commands/jdi-create.md +80 -0
  140. package/runtimes/opencode/commands/jdi-discuss.md +80 -0
  141. package/runtimes/opencode/commands/jdi-do.md +200 -0
  142. package/runtimes/opencode/commands/jdi-loop.md +315 -0
  143. package/runtimes/opencode/commands/jdi-new.md +131 -0
  144. package/runtimes/opencode/commands/jdi-plan.md +73 -0
  145. package/runtimes/opencode/commands/jdi-ship.md +146 -0
  146. package/runtimes/opencode/commands/jdi-verify.md +159 -0
  147. package/runtimes/opencode/opencode.example.jsonc +169 -0
  148. package/runtimes/opencode/skills/clean-code/SKILL.md +247 -0
  149. package/runtimes/opencode/skills/dry/SKILL.md +136 -0
  150. package/runtimes/opencode/skills/frontend-rules/SKILL.md +369 -0
  151. package/runtimes/opencode/skills/frontend-validator/SKILL.md +553 -0
  152. package/runtimes/opencode/skills/kiss/SKILL.md +164 -0
  153. package/runtimes/opencode/skills/solid/SKILL.md +267 -0
  154. package/runtimes/opencode/skills/yagni/SKILL.md +193 -0
  155. package/templates-jdi-folder/config.json +18 -0
  156. package/templates-jdi-folder/registry.md +31 -0
  157. package/templates-jdi-folder/reviewers.md +33 -0
  158. package/templates-jdi-folder/skills-registry.md +32 -0
  159. package/templates-jdi-folder/specialists.md +39 -0
@@ -0,0 +1,567 @@
1
+ ---
2
+ name: jdi-frontend-validator
3
+ description: Validates live UI via Playwright + axe-core. Detects Playwright; installs if missing (with user consent). Spawns dev server, navigates critical routes on mobile+desktop, captures console errors, network failures, a11y violations, screenshots, layout shifts. Structured JSON output for the reviewer to parse.
4
+ type: skill
5
+ applies_to: |
6
+ Loaded by jdi-reviewer-{slug} at gate 7 when `frontend.has_frontend: true` in .jdi/PROJECT.md.
7
+ Can be invoked manually for ad-hoc UI validation.
8
+ loaded_by:
9
+ - jdi-reviewer-{slug} (conditional)
10
+ runtime_overrides:
11
+ antigravity:
12
+ triggers:
13
+ - "validate UI"
14
+ - "run playwright"
15
+ - "check accessibility"
16
+ - "smoke test frontend"
17
+ - "validate live interface"
18
+ ---
19
+
20
+ # Skill: jdi-frontend-validator
21
+
22
+ Runs UI validation in a real browser. Without Playwright installed, installs with consent. Output always JSON in `.jdi/cache/ui-findings.json` for the parent reviewer to consume.
23
+
24
+ ## When to apply
25
+
26
+ Reviewer calls at gate 7. Preconditions in PROJECT.md:
27
+
28
+ ```yaml
29
+ frontend:
30
+ has_frontend: true
31
+ frontend_url: http://localhost:5173
32
+ dev_command: pnpm dev
33
+ critical_paths:
34
+ - /
35
+ - /login
36
+ - /dashboard
37
+ ```
38
+
39
+ Missing any key -> abort with descriptive error.
40
+
41
+ ## Procedure
42
+
43
+ ### Step 1: Pre-flight
44
+
45
+ ```bash
46
+ # bash
47
+ test -d .jdi/ || { echo "No .jdi/. Run /jdi-new"; exit 1; }
48
+ test -f .jdi/PROJECT.md || { echo "PROJECT.md missing"; exit 1; }
49
+
50
+ # Read frontend.has_frontend, frontend_url, dev_command, critical_paths
51
+ # (simple YAML parse - assume well-defined format)
52
+
53
+ # Create cache
54
+ mkdir -p .jdi/cache/screenshots
55
+
56
+ # .gitignore guarantee
57
+ grep -q '^\.jdi/cache/' .gitignore 2>/dev/null || echo '.jdi/cache/' >> .gitignore
58
+ ```
59
+
60
+ ```powershell
61
+ # PowerShell
62
+ if (-not (Test-Path .jdi)) { Write-Error "No .jdi/. Run /jdi-new"; exit 1 }
63
+ if (-not (Test-Path .jdi/PROJECT.md)) { Write-Error "PROJECT.md missing"; exit 1 }
64
+
65
+ New-Item -ItemType Directory -Force -Path .jdi/cache/screenshots | Out-Null
66
+
67
+ if (-not (Test-Path .gitignore) -or -not (Select-String -Path .gitignore -Pattern '^\.jdi/cache/' -Quiet)) {
68
+ Add-Content .gitignore '.jdi/cache/'
69
+ }
70
+ ```
71
+
72
+ ### Step 2: Detect package manager
73
+
74
+ Lockfile detection (more reliable than `which`):
75
+
76
+ ```bash
77
+ # bash
78
+ if [ -f pnpm-lock.yaml ]; then PKG_MGR=pnpm
79
+ elif [ -f yarn.lock ]; then PKG_MGR=yarn
80
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then PKG_MGR=bun
81
+ elif [ -f package-lock.json ]; then PKG_MGR=npm
82
+ else PKG_MGR=npm # fallback
83
+ fi
84
+ ```
85
+
86
+ ```powershell
87
+ # PowerShell
88
+ if (Test-Path pnpm-lock.yaml) { $PKG_MGR = "pnpm" }
89
+ elseif (Test-Path yarn.lock) { $PKG_MGR = "yarn" }
90
+ elseif ((Test-Path bun.lockb) -or (Test-Path bun.lock)) { $PKG_MGR = "bun" }
91
+ elseif (Test-Path package-lock.json) { $PKG_MGR = "npm" }
92
+ else { $PKG_MGR = "npm" }
93
+ ```
94
+
95
+ Corresponding install command:
96
+
97
+ | Pkg mgr | Install dev dep | Run binary |
98
+ |---|---|---|
99
+ | npm | `npm install --save-dev <pkg>` | `npx <bin>` |
100
+ | pnpm | `pnpm add -D <pkg>` | `pnpm exec <bin>` or `pnpm dlx <bin>` |
101
+ | yarn | `yarn add -D <pkg>` | `yarn <bin>` |
102
+ | bun | `bun add -d <pkg>` | `bunx <bin>` |
103
+
104
+ ### Step 3: Detect Playwright
105
+
106
+ ```bash
107
+ # bash
108
+ PW_BIN="npx --no-install playwright"
109
+ [ "$PKG_MGR" = "pnpm" ] && PW_BIN="pnpm exec playwright"
110
+ [ "$PKG_MGR" = "yarn" ] && PW_BIN="yarn playwright"
111
+ [ "$PKG_MGR" = "bun" ] && PW_BIN="bunx playwright"
112
+
113
+ if ! $PW_BIN --version >/dev/null 2>&1; then
114
+ PLAYWRIGHT_MISSING=1
115
+ fi
116
+
117
+ # Check axe-core/playwright separately
118
+ if [ -f package.json ] && ! grep -q '@axe-core/playwright' package.json; then
119
+ AXE_MISSING=1
120
+ fi
121
+ ```
122
+
123
+ ```powershell
124
+ # PowerShell - simplified
125
+ $pwExists = $false
126
+ try {
127
+ $null = & npx --no-install playwright --version 2>$null
128
+ if ($LASTEXITCODE -eq 0) { $pwExists = $true }
129
+ } catch {}
130
+ if (-not $pwExists) { $env:PLAYWRIGHT_MISSING = "1" }
131
+
132
+ $axeExists = $false
133
+ if (Test-Path package.json) {
134
+ if (Select-String -Path package.json -Pattern '@axe-core/playwright' -Quiet) { $axeExists = $true }
135
+ }
136
+ if (-not $axeExists) { $env:AXE_MISSING = "1" }
137
+ ```
138
+
139
+ ### Step 4: If missing, ask consent + install
140
+
141
+ Use AskUserQuestion (or prompt fallback if runtime doesn't support it):
142
+
143
+ ```
144
+ Playwright not installed in this project.
145
+
146
+ Install now?
147
+ - [Yes, install with Chromium] (~150MB, 2-5min, recommended)
148
+ - [Yes, install with all browsers] (~500MB, 5-10min)
149
+ - [No, skip gate 7 this time] (gate returns SKIPPED)
150
+ - [Cancel entire review]
151
+ ```
152
+
153
+ Choice mapping:
154
+
155
+ **Yes, Chromium:**
156
+ ```bash
157
+ $INSTALL_CMD @playwright/test @axe-core/playwright
158
+ $PLAYWRIGHT_INSTALL chromium
159
+ ```
160
+
161
+ Where:
162
+ - `$INSTALL_CMD` = `pnpm add -D` / `npm install --save-dev` / etc based on PKG_MGR
163
+ - `$PLAYWRIGHT_INSTALL` = `$PW_BIN install`
164
+
165
+ **Yes, all browsers:**
166
+ ```bash
167
+ $INSTALL_CMD @playwright/test @axe-core/playwright
168
+ $PLAYWRIGHT_INSTALL
169
+ ```
170
+
171
+ **No, skip:**
172
+ Returns `{ "status": "SKIPPED", "reason": "user declined Playwright install" }` - reviewer marks gate 7 as SKIPPED (not BLOCK).
173
+
174
+ **Cancel review:**
175
+ Returns error code, reviewer aborts.
176
+
177
+ ### Step 5: Generate temporary Playwright spec
178
+
179
+ Creates `.jdi/cache/playwright-check.spec.js` (gitignored). Content:
180
+
181
+ ```javascript
182
+ // Auto-generated by jdi-frontend-validator. DO NOT edit manually.
183
+ // @ts-check
184
+ const { test, expect } = require('@playwright/test');
185
+ const AxeBuilder = require('@axe-core/playwright').default;
186
+ const fs = require('fs');
187
+ const path = require('path');
188
+
189
+ const URL = process.env.JDI_FRONTEND_URL;
190
+ const ROUTES = (process.env.JDI_ROUTES || '/').split(',').map(r => r.trim()).filter(Boolean);
191
+ const OUT = process.env.JDI_OUT || '.jdi/cache/ui-findings.json';
192
+ const SCREENSHOT_DIR = process.env.JDI_SCREENSHOT_DIR || '.jdi/cache/screenshots';
193
+
194
+ const VIEWPORTS = [
195
+ { name: 'mobile', width: 375, height: 667 },
196
+ { name: 'desktop', width: 1280, height: 720 }
197
+ ];
198
+
199
+ const findings = {
200
+ metadata: { url: URL, routes: ROUTES, timestamp: new Date().toISOString() },
201
+ console: [],
202
+ network: [],
203
+ a11y: [],
204
+ layout: [],
205
+ screenshots: [],
206
+ navigationFailures: []
207
+ };
208
+
209
+ for (const route of ROUTES) {
210
+ for (const viewport of VIEWPORTS) {
211
+ test(`${route} @ ${viewport.name}`, async ({ page }) => {
212
+ await page.setViewportSize({ width: viewport.width, height: viewport.height });
213
+
214
+ page.on('console', msg => {
215
+ if (msg.type() === 'error') {
216
+ findings.console.push({
217
+ route,
218
+ viewport: viewport.name,
219
+ text: msg.text(),
220
+ location: msg.location()
221
+ });
222
+ }
223
+ });
224
+
225
+ page.on('requestfailed', req => {
226
+ findings.network.push({
227
+ route,
228
+ viewport: viewport.name,
229
+ url: req.url(),
230
+ method: req.method(),
231
+ failure: req.failure()?.errorText,
232
+ severity: 'requestfailed'
233
+ });
234
+ });
235
+
236
+ page.on('response', res => {
237
+ if (res.status() >= 500) {
238
+ findings.network.push({
239
+ route,
240
+ viewport: viewport.name,
241
+ url: res.url(),
242
+ status: res.status(),
243
+ severity: '5xx'
244
+ });
245
+ } else if (res.status() >= 400 && res.status() !== 404) {
246
+ findings.network.push({
247
+ route,
248
+ viewport: viewport.name,
249
+ url: res.url(),
250
+ status: res.status(),
251
+ severity: '4xx'
252
+ });
253
+ }
254
+ });
255
+
256
+ const targetUrl = `${URL}${route}`;
257
+ let response;
258
+ try {
259
+ response = await page.goto(targetUrl, { waitUntil: 'networkidle', timeout: 30000 });
260
+ } catch (err) {
261
+ findings.navigationFailures.push({
262
+ route,
263
+ viewport: viewport.name,
264
+ error: err.message
265
+ });
266
+ return;
267
+ }
268
+
269
+ if (!response || !response.ok()) {
270
+ findings.navigationFailures.push({
271
+ route,
272
+ viewport: viewport.name,
273
+ status: response?.status() ?? 'no-response',
274
+ url: targetUrl
275
+ });
276
+ return;
277
+ }
278
+
279
+ // Detect horizontal scroll
280
+ const hasHScroll = await page.evaluate(() => {
281
+ return document.documentElement.scrollWidth > document.documentElement.clientWidth + 1;
282
+ });
283
+ if (hasHScroll) {
284
+ findings.layout.push({
285
+ route,
286
+ viewport: viewport.name,
287
+ issue: 'horizontal_scroll'
288
+ });
289
+ }
290
+
291
+ // axe-core a11y scan
292
+ try {
293
+ const axeResults = await new AxeBuilder({ page })
294
+ .withTags(['wcag2a', 'wcag2aa', 'wcag22aa', 'best-practice'])
295
+ .analyze();
296
+
297
+ for (const v of axeResults.violations) {
298
+ findings.a11y.push({
299
+ route,
300
+ viewport: viewport.name,
301
+ id: v.id,
302
+ impact: v.impact,
303
+ help: v.help,
304
+ helpUrl: v.helpUrl,
305
+ nodes: v.nodes.length,
306
+ sample: v.nodes.slice(0, 3).map(n => ({
307
+ target: n.target,
308
+ html: n.html.slice(0, 200)
309
+ }))
310
+ });
311
+ }
312
+ } catch (err) {
313
+ // axe-core failure doesn't block run
314
+ findings.a11y.push({
315
+ route,
316
+ viewport: viewport.name,
317
+ error: `axe-core failed: ${err.message}`
318
+ });
319
+ }
320
+
321
+ // Screenshot
322
+ const safeName = (route === '/' ? 'root' : route.replace(/^\//, '').replace(/\//g, '_'));
323
+ const screenshotPath = path.join(SCREENSHOT_DIR, `${safeName}_${viewport.name}.png`);
324
+ await page.screenshot({ path: screenshotPath, fullPage: true });
325
+ findings.screenshots.push({ route, viewport: viewport.name, path: screenshotPath });
326
+ });
327
+ }
328
+ }
329
+
330
+ test.afterAll(() => {
331
+ fs.writeFileSync(OUT, JSON.stringify(findings, null, 2));
332
+ });
333
+ ```
334
+
335
+ And inline config `.jdi/cache/playwright.config.js`:
336
+
337
+ ```javascript
338
+ module.exports = {
339
+ testDir: '.jdi/cache',
340
+ testMatch: 'playwright-check.spec.js',
341
+ timeout: 60000,
342
+ retries: 0,
343
+ workers: 1,
344
+ reporter: [['line']],
345
+ use: {
346
+ headless: true,
347
+ ignoreHTTPSErrors: true
348
+ }
349
+ };
350
+ ```
351
+
352
+ ### Step 6: Spawn dev server
353
+
354
+ ```bash
355
+ # bash
356
+ DEV_LOG=.jdi/cache/dev-server.log
357
+ DEV_PID_FILE=.jdi/cache/dev-server.pid
358
+
359
+ # Spawn in background, redirecting log
360
+ nohup $DEV_COMMAND > $DEV_LOG 2>&1 &
361
+ echo $! > $DEV_PID_FILE
362
+
363
+ # Wait ready (poll URL, timeout 60s)
364
+ READY=0
365
+ for i in $(seq 1 60); do
366
+ if curl -sSf -o /dev/null --max-time 2 "$FRONTEND_URL"; then
367
+ READY=1
368
+ break
369
+ fi
370
+ sleep 1
371
+ done
372
+
373
+ if [ $READY -eq 0 ]; then
374
+ # Cleanup
375
+ kill $(cat $DEV_PID_FILE) 2>/dev/null
376
+ echo '{"status":"INCONCLUSIVE","reason":"dev server failed to start in 60s","logs":"'$DEV_LOG'"}' > .jdi/cache/ui-findings.json
377
+ exit 0 # doesn't fail the reviewer - INCONCLUSIVE is WARN
378
+ fi
379
+ ```
380
+
381
+ ```powershell
382
+ # PowerShell
383
+ $DEV_LOG = ".jdi/cache/dev-server.log"
384
+ $DEV_PID_FILE = ".jdi/cache/dev-server.pid"
385
+
386
+ $proc = Start-Process -FilePath pwsh -ArgumentList "-NoProfile", "-Command", $DEV_COMMAND `
387
+ -RedirectStandardOutput $DEV_LOG -RedirectStandardError $DEV_LOG `
388
+ -PassThru -WindowStyle Hidden
389
+ $proc.Id | Out-File -FilePath $DEV_PID_FILE
390
+
391
+ $ready = $false
392
+ for ($i = 0; $i -lt 60; $i++) {
393
+ try {
394
+ $r = Invoke-WebRequest -Uri $FRONTEND_URL -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
395
+ if ($r.StatusCode -eq 200) { $ready = $true; break }
396
+ } catch {}
397
+ Start-Sleep -Seconds 1
398
+ }
399
+
400
+ if (-not $ready) {
401
+ Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
402
+ $err = @{ status="INCONCLUSIVE"; reason="dev server failed to start in 60s"; logs=$DEV_LOG } | ConvertTo-Json
403
+ $err | Out-File .jdi/cache/ui-findings.json
404
+ exit 0
405
+ }
406
+ ```
407
+
408
+ ### Step 7: Run Playwright
409
+
410
+ ```bash
411
+ # bash
412
+ JDI_FRONTEND_URL="$FRONTEND_URL" \
413
+ JDI_ROUTES="$(echo $CRITICAL_PATHS | tr '\n' ',')" \
414
+ JDI_OUT=".jdi/cache/ui-findings.json" \
415
+ JDI_SCREENSHOT_DIR=".jdi/cache/screenshots" \
416
+ $PW_BIN test --config=.jdi/cache/playwright.config.js 2>&1 | tee .jdi/cache/playwright.log
417
+
418
+ # Playwright exit code doesn't matter - findings already written by afterAll
419
+ ```
420
+
421
+ ```powershell
422
+ # PowerShell
423
+ $env:JDI_FRONTEND_URL = $FRONTEND_URL
424
+ $env:JDI_ROUTES = ($CRITICAL_PATHS -join ',')
425
+ $env:JDI_OUT = ".jdi/cache/ui-findings.json"
426
+ $env:JDI_SCREENSHOT_DIR = ".jdi/cache/screenshots"
427
+
428
+ & npx playwright test --config=.jdi/cache/playwright.config.js 2>&1 | Tee-Object -FilePath .jdi/cache/playwright.log
429
+ ```
430
+
431
+ ### Step 8: Kill dev server (always, even on failure)
432
+
433
+ ```bash
434
+ # bash
435
+ if [ -f $DEV_PID_FILE ]; then
436
+ PID=$(cat $DEV_PID_FILE)
437
+ # Kill process + children (dev server usually has children: node, esbuild, vite, etc)
438
+ pkill -P $PID 2>/dev/null
439
+ kill $PID 2>/dev/null
440
+ # Safeguard on common ports (in case wrong PID)
441
+ # Vite: 5173, Next: 3000, etc - skip aggressive cleanup
442
+ rm $DEV_PID_FILE
443
+ fi
444
+ ```
445
+
446
+ ```powershell
447
+ # PowerShell
448
+ if (Test-Path $DEV_PID_FILE) {
449
+ $pid = Get-Content $DEV_PID_FILE
450
+ # Kill children first
451
+ Get-CimInstance Win32_Process -Filter "ParentProcessId=$pid" -ErrorAction SilentlyContinue |
452
+ ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
453
+ # Kill the parent
454
+ Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue
455
+ Remove-Item $DEV_PID_FILE
456
+ }
457
+ ```
458
+
459
+ ### Step 9: Return to parent reviewer
460
+
461
+ Skill doesn't write REVIEW.md. Only writes `.jdi/cache/ui-findings.json` + screenshots.
462
+
463
+ Reviewer reads the JSON, classifies severities, and writes "UI Validation" section in REVIEW.md.
464
+
465
+ ## Finding classification (reference for the reviewer)
466
+
467
+ | Finding | Severity |
468
+ |---|---|
469
+ | `console.error` on any route | BLOCK |
470
+ | `network.5xx` on critical_path | BLOCK |
471
+ | `network.4xx` on critical_path | WARN |
472
+ | `network.requestfailed` (CORS/abort/etc) | WARN |
473
+ | `navigationFailures` (404/timeout/etc on critical_path) | BLOCK |
474
+ | `a11y.impact=critical` | BLOCK |
475
+ | `a11y.impact=serious` | BLOCK |
476
+ | `a11y.impact=moderate` | WARN |
477
+ | `a11y.impact=minor` | INFO |
478
+ | `layout.horizontal_scroll` on mobile | BLOCK |
479
+ | `layout.horizontal_scroll` on desktop | INFO |
480
+ | `axe-core failed` (technical error) | WARN |
481
+ | `INCONCLUSIVE` (dev server timeout) | WARN |
482
+ | `SKIPPED` (user declined install) | WARN |
483
+
484
+ ## Expected inputs
485
+
486
+ From PROJECT.md (passed as environment variables by the reviewer):
487
+ - `frontend.frontend_url` -> `FRONTEND_URL`
488
+ - `frontend.dev_command` -> `DEV_COMMAND`
489
+ - `frontend.critical_paths` -> `CRITICAL_PATHS` (list)
490
+
491
+ ## Outputs
492
+
493
+ Files created in `.jdi/cache/` (gitignored):
494
+ - `ui-findings.json` - structured findings
495
+ - `screenshots/*.png` - 1 per route x viewport
496
+ - `dev-server.log` - dev server log
497
+ - `playwright.log` - Playwright run log
498
+ - `playwright-check.spec.js` - generated spec
499
+ - `playwright.config.js` - generated config
500
+
501
+ NEVER commit `.jdi/cache/`.
502
+
503
+ ## Anti-patterns
504
+
505
+ - Running against prod URL - dev local only. Prod is out of scope for this gate
506
+ - Testing flows that require login - MVP doesn't support auth setup. Critical paths must be public OR pre-authenticated manually (cookie/session passed via PROJECT.md in follow-up)
507
+ - Blocking review if Playwright install fails - degrade to SKIPPED
508
+ - Leaving dev server alive after gate - always kill, even on error
509
+ - Committing screenshots - .gitignore guaranteed in pre-flight
510
+ - Running parallel (workers > 1) - local dev server doesn't scale, and race conditions confuse findings
511
+ - Using `--headed` in CI - always headless
512
+ - Trusting Playwright exit code - findings come from afterAll, even with test failure
513
+
514
+ ## References
515
+
516
+ - `references/playwright-setup.md` - Detailed install per package manager + troubleshoot
517
+ - `references/dev-server-detection.md` - Heuristics for detecting ready (curl, wait-on, polling)
518
+ - `references/axe-rules.md` - Mapping of axe rule IDs -> WCAG -> severity
519
+ - `references/auth-flows.md` - Roadmap for authenticated flows (future)
520
+
521
+ ## Examples
522
+
523
+ ### Example 1: Vite + React, Playwright missing, user accepts install
524
+
525
+ ```
526
+ 1. Reviewer triggers gate 7
527
+ 2. Skill detects `npx playwright --version` -> exit 1
528
+ 3. Lockfile = pnpm-lock.yaml -> PKG_MGR=pnpm
529
+ 4. AskUserQuestion -> user picks "Yes, Chromium"
530
+ 5. pnpm add -D @playwright/test @axe-core/playwright
531
+ 6. pnpm exec playwright install chromium
532
+ 7. Spawns `pnpm dev` in bg, PID 12345
533
+ 8. Waits http://localhost:5173 -> ready in 4s
534
+ 9. Runs Playwright on /, /login, /dashboard x mobile + desktop = 6 navigations
535
+ 10. Findings:
536
+ - 1 console error (uncaught promise) on /dashboard mobile + desktop
537
+ - 0 network errors
538
+ - 2 a11y serious on /login (missing label + contrast)
539
+ - 1 horizontal scroll on /dashboard mobile
540
+ 11. Kill PID 12345 + children
541
+ 12. Writes .jdi/cache/ui-findings.json
542
+ 13. Reviewer reads JSON, marks gate 7 = BLOCK (3 issues), writes REVIEW.md
543
+ ```
544
+
545
+ ### Example 2: API-only, has_frontend=false
546
+
547
+ Skill is not even loaded. Reviewer skips gate 7 with SKIPPED.
548
+
549
+ ### Example 3: Dev server fails to start
550
+
551
+ ```
552
+ 1. Spawns `pnpm dev` -> process dies after 2s (port 5173 occupied)
553
+ 2. Poll of 60s expires without 200 OK
554
+ 3. Cleanup of PID
555
+ 4. Writes {"status":"INCONCLUSIVE","reason":"dev server failed to start in 60s"}
556
+ 5. Reviewer marks gate 7 = WARN with link to dev-server.log
557
+ 6. Review not blocked, but user alerted
558
+ ```
559
+
560
+ ### Example 4: User declines to install Playwright
561
+
562
+ ```
563
+ 1. AskUserQuestion -> "No, skip gate 7"
564
+ 2. Writes {"status":"SKIPPED","reason":"user declined Playwright install"}
565
+ 3. Reviewer marks gate 7 = SKIPPED (warn not block)
566
+ 4. REVIEW.md notes "UI Validation: SKIPPED - run /jdi-verify again when you accept installing"
567
+ ```