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,553 @@
1
+ ---
2
+ name: 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
+ ---
5
+
6
+ # Skill: jdi-frontend-validator
7
+
8
+ 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.
9
+
10
+ ## When to apply
11
+
12
+ Reviewer calls at gate 7. Preconditions in PROJECT.md:
13
+
14
+ ```yaml
15
+ frontend:
16
+ has_frontend: true
17
+ frontend_url: http://localhost:5173
18
+ dev_command: pnpm dev
19
+ critical_paths:
20
+ - /
21
+ - /login
22
+ - /dashboard
23
+ ```
24
+
25
+ Missing any key -> abort with descriptive error.
26
+
27
+ ## Procedure
28
+
29
+ ### Step 1: Pre-flight
30
+
31
+ ```bash
32
+ # bash
33
+ test -d .jdi/ || { echo "No .jdi/. Run /jdi-new"; exit 1; }
34
+ test -f .jdi/PROJECT.md || { echo "PROJECT.md missing"; exit 1; }
35
+
36
+ # Read frontend.has_frontend, frontend_url, dev_command, critical_paths
37
+ # (simple YAML parse - assume well-defined format)
38
+
39
+ # Create cache
40
+ mkdir -p .jdi/cache/screenshots
41
+
42
+ # .gitignore guarantee
43
+ grep -q '^\.jdi/cache/' .gitignore 2>/dev/null || echo '.jdi/cache/' >> .gitignore
44
+ ```
45
+
46
+ ```powershell
47
+ # PowerShell
48
+ if (-not (Test-Path .jdi)) { Write-Error "No .jdi/. Run /jdi-new"; exit 1 }
49
+ if (-not (Test-Path .jdi/PROJECT.md)) { Write-Error "PROJECT.md missing"; exit 1 }
50
+
51
+ New-Item -ItemType Directory -Force -Path .jdi/cache/screenshots | Out-Null
52
+
53
+ if (-not (Test-Path .gitignore) -or -not (Select-String -Path .gitignore -Pattern '^\.jdi/cache/' -Quiet)) {
54
+ Add-Content .gitignore '.jdi/cache/'
55
+ }
56
+ ```
57
+
58
+ ### Step 2: Detect package manager
59
+
60
+ Lockfile detection (more reliable than `which`):
61
+
62
+ ```bash
63
+ # bash
64
+ if [ -f pnpm-lock.yaml ]; then PKG_MGR=pnpm
65
+ elif [ -f yarn.lock ]; then PKG_MGR=yarn
66
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then PKG_MGR=bun
67
+ elif [ -f package-lock.json ]; then PKG_MGR=npm
68
+ else PKG_MGR=npm # fallback
69
+ fi
70
+ ```
71
+
72
+ ```powershell
73
+ # PowerShell
74
+ if (Test-Path pnpm-lock.yaml) { $PKG_MGR = "pnpm" }
75
+ elseif (Test-Path yarn.lock) { $PKG_MGR = "yarn" }
76
+ elseif ((Test-Path bun.lockb) -or (Test-Path bun.lock)) { $PKG_MGR = "bun" }
77
+ elseif (Test-Path package-lock.json) { $PKG_MGR = "npm" }
78
+ else { $PKG_MGR = "npm" }
79
+ ```
80
+
81
+ Corresponding install command:
82
+
83
+ | Pkg mgr | Install dev dep | Run binary |
84
+ |---|---|---|
85
+ | npm | `npm install --save-dev <pkg>` | `npx <bin>` |
86
+ | pnpm | `pnpm add -D <pkg>` | `pnpm exec <bin>` or `pnpm dlx <bin>` |
87
+ | yarn | `yarn add -D <pkg>` | `yarn <bin>` |
88
+ | bun | `bun add -d <pkg>` | `bunx <bin>` |
89
+
90
+ ### Step 3: Detect Playwright
91
+
92
+ ```bash
93
+ # bash
94
+ PW_BIN="npx --no-install playwright"
95
+ [ "$PKG_MGR" = "pnpm" ] && PW_BIN="pnpm exec playwright"
96
+ [ "$PKG_MGR" = "yarn" ] && PW_BIN="yarn playwright"
97
+ [ "$PKG_MGR" = "bun" ] && PW_BIN="bunx playwright"
98
+
99
+ if ! $PW_BIN --version >/dev/null 2>&1; then
100
+ PLAYWRIGHT_MISSING=1
101
+ fi
102
+
103
+ # Check axe-core/playwright separately
104
+ if [ -f package.json ] && ! grep -q '@axe-core/playwright' package.json; then
105
+ AXE_MISSING=1
106
+ fi
107
+ ```
108
+
109
+ ```powershell
110
+ # PowerShell - simplified
111
+ $pwExists = $false
112
+ try {
113
+ $null = & npx --no-install playwright --version 2>$null
114
+ if ($LASTEXITCODE -eq 0) { $pwExists = $true }
115
+ } catch {}
116
+ if (-not $pwExists) { $env:PLAYWRIGHT_MISSING = "1" }
117
+
118
+ $axeExists = $false
119
+ if (Test-Path package.json) {
120
+ if (Select-String -Path package.json -Pattern '@axe-core/playwright' -Quiet) { $axeExists = $true }
121
+ }
122
+ if (-not $axeExists) { $env:AXE_MISSING = "1" }
123
+ ```
124
+
125
+ ### Step 4: If missing, ask consent + install
126
+
127
+ Use AskUserQuestion (or prompt fallback if runtime doesn't support it):
128
+
129
+ ```
130
+ Playwright not installed in this project.
131
+
132
+ Install now?
133
+ - [Yes, install with Chromium] (~150MB, 2-5min, recommended)
134
+ - [Yes, install with all browsers] (~500MB, 5-10min)
135
+ - [No, skip gate 7 this time] (gate returns SKIPPED)
136
+ - [Cancel entire review]
137
+ ```
138
+
139
+ Choice mapping:
140
+
141
+ **Yes, Chromium:**
142
+ ```bash
143
+ $INSTALL_CMD @playwright/test @axe-core/playwright
144
+ $PLAYWRIGHT_INSTALL chromium
145
+ ```
146
+
147
+ Where:
148
+ - `$INSTALL_CMD` = `pnpm add -D` / `npm install --save-dev` / etc based on PKG_MGR
149
+ - `$PLAYWRIGHT_INSTALL` = `$PW_BIN install`
150
+
151
+ **Yes, all browsers:**
152
+ ```bash
153
+ $INSTALL_CMD @playwright/test @axe-core/playwright
154
+ $PLAYWRIGHT_INSTALL
155
+ ```
156
+
157
+ **No, skip:**
158
+ Returns `{ "status": "SKIPPED", "reason": "user declined Playwright install" }` - reviewer marks gate 7 as SKIPPED (not BLOCK).
159
+
160
+ **Cancel review:**
161
+ Returns error code, reviewer aborts.
162
+
163
+ ### Step 5: Generate temporary Playwright spec
164
+
165
+ Creates `.jdi/cache/playwright-check.spec.js` (gitignored). Content:
166
+
167
+ ```javascript
168
+ // Auto-generated by jdi-frontend-validator. DO NOT edit manually.
169
+ // @ts-check
170
+ const { test, expect } = require('@playwright/test');
171
+ const AxeBuilder = require('@axe-core/playwright').default;
172
+ const fs = require('fs');
173
+ const path = require('path');
174
+
175
+ const URL = process.env.JDI_FRONTEND_URL;
176
+ const ROUTES = (process.env.JDI_ROUTES || '/').split(',').map(r => r.trim()).filter(Boolean);
177
+ const OUT = process.env.JDI_OUT || '.jdi/cache/ui-findings.json';
178
+ const SCREENSHOT_DIR = process.env.JDI_SCREENSHOT_DIR || '.jdi/cache/screenshots';
179
+
180
+ const VIEWPORTS = [
181
+ { name: 'mobile', width: 375, height: 667 },
182
+ { name: 'desktop', width: 1280, height: 720 }
183
+ ];
184
+
185
+ const findings = {
186
+ metadata: { url: URL, routes: ROUTES, timestamp: new Date().toISOString() },
187
+ console: [],
188
+ network: [],
189
+ a11y: [],
190
+ layout: [],
191
+ screenshots: [],
192
+ navigationFailures: []
193
+ };
194
+
195
+ for (const route of ROUTES) {
196
+ for (const viewport of VIEWPORTS) {
197
+ test(`${route} @ ${viewport.name}`, async ({ page }) => {
198
+ await page.setViewportSize({ width: viewport.width, height: viewport.height });
199
+
200
+ page.on('console', msg => {
201
+ if (msg.type() === 'error') {
202
+ findings.console.push({
203
+ route,
204
+ viewport: viewport.name,
205
+ text: msg.text(),
206
+ location: msg.location()
207
+ });
208
+ }
209
+ });
210
+
211
+ page.on('requestfailed', req => {
212
+ findings.network.push({
213
+ route,
214
+ viewport: viewport.name,
215
+ url: req.url(),
216
+ method: req.method(),
217
+ failure: req.failure()?.errorText,
218
+ severity: 'requestfailed'
219
+ });
220
+ });
221
+
222
+ page.on('response', res => {
223
+ if (res.status() >= 500) {
224
+ findings.network.push({
225
+ route,
226
+ viewport: viewport.name,
227
+ url: res.url(),
228
+ status: res.status(),
229
+ severity: '5xx'
230
+ });
231
+ } else if (res.status() >= 400 && res.status() !== 404) {
232
+ findings.network.push({
233
+ route,
234
+ viewport: viewport.name,
235
+ url: res.url(),
236
+ status: res.status(),
237
+ severity: '4xx'
238
+ });
239
+ }
240
+ });
241
+
242
+ const targetUrl = `${URL}${route}`;
243
+ let response;
244
+ try {
245
+ response = await page.goto(targetUrl, { waitUntil: 'networkidle', timeout: 30000 });
246
+ } catch (err) {
247
+ findings.navigationFailures.push({
248
+ route,
249
+ viewport: viewport.name,
250
+ error: err.message
251
+ });
252
+ return;
253
+ }
254
+
255
+ if (!response || !response.ok()) {
256
+ findings.navigationFailures.push({
257
+ route,
258
+ viewport: viewport.name,
259
+ status: response?.status() ?? 'no-response',
260
+ url: targetUrl
261
+ });
262
+ return;
263
+ }
264
+
265
+ // Detect horizontal scroll
266
+ const hasHScroll = await page.evaluate(() => {
267
+ return document.documentElement.scrollWidth > document.documentElement.clientWidth + 1;
268
+ });
269
+ if (hasHScroll) {
270
+ findings.layout.push({
271
+ route,
272
+ viewport: viewport.name,
273
+ issue: 'horizontal_scroll'
274
+ });
275
+ }
276
+
277
+ // axe-core a11y scan
278
+ try {
279
+ const axeResults = await new AxeBuilder({ page })
280
+ .withTags(['wcag2a', 'wcag2aa', 'wcag22aa', 'best-practice'])
281
+ .analyze();
282
+
283
+ for (const v of axeResults.violations) {
284
+ findings.a11y.push({
285
+ route,
286
+ viewport: viewport.name,
287
+ id: v.id,
288
+ impact: v.impact,
289
+ help: v.help,
290
+ helpUrl: v.helpUrl,
291
+ nodes: v.nodes.length,
292
+ sample: v.nodes.slice(0, 3).map(n => ({
293
+ target: n.target,
294
+ html: n.html.slice(0, 200)
295
+ }))
296
+ });
297
+ }
298
+ } catch (err) {
299
+ // axe-core failure doesn't block run
300
+ findings.a11y.push({
301
+ route,
302
+ viewport: viewport.name,
303
+ error: `axe-core failed: ${err.message}`
304
+ });
305
+ }
306
+
307
+ // Screenshot
308
+ const safeName = (route === '/' ? 'root' : route.replace(/^\//, '').replace(/\//g, '_'));
309
+ const screenshotPath = path.join(SCREENSHOT_DIR, `${safeName}_${viewport.name}.png`);
310
+ await page.screenshot({ path: screenshotPath, fullPage: true });
311
+ findings.screenshots.push({ route, viewport: viewport.name, path: screenshotPath });
312
+ });
313
+ }
314
+ }
315
+
316
+ test.afterAll(() => {
317
+ fs.writeFileSync(OUT, JSON.stringify(findings, null, 2));
318
+ });
319
+ ```
320
+
321
+ And inline config `.jdi/cache/playwright.config.js`:
322
+
323
+ ```javascript
324
+ module.exports = {
325
+ testDir: '.jdi/cache',
326
+ testMatch: 'playwright-check.spec.js',
327
+ timeout: 60000,
328
+ retries: 0,
329
+ workers: 1,
330
+ reporter: [['line']],
331
+ use: {
332
+ headless: true,
333
+ ignoreHTTPSErrors: true
334
+ }
335
+ };
336
+ ```
337
+
338
+ ### Step 6: Spawn dev server
339
+
340
+ ```bash
341
+ # bash
342
+ DEV_LOG=.jdi/cache/dev-server.log
343
+ DEV_PID_FILE=.jdi/cache/dev-server.pid
344
+
345
+ # Spawn in background, redirecting log
346
+ nohup $DEV_COMMAND > $DEV_LOG 2>&1 &
347
+ echo $! > $DEV_PID_FILE
348
+
349
+ # Wait ready (poll URL, timeout 60s)
350
+ READY=0
351
+ for i in $(seq 1 60); do
352
+ if curl -sSf -o /dev/null --max-time 2 "$FRONTEND_URL"; then
353
+ READY=1
354
+ break
355
+ fi
356
+ sleep 1
357
+ done
358
+
359
+ if [ $READY -eq 0 ]; then
360
+ # Cleanup
361
+ kill $(cat $DEV_PID_FILE) 2>/dev/null
362
+ echo '{"status":"INCONCLUSIVE","reason":"dev server failed to start in 60s","logs":"'$DEV_LOG'"}' > .jdi/cache/ui-findings.json
363
+ exit 0 # doesn't fail the reviewer - INCONCLUSIVE is WARN
364
+ fi
365
+ ```
366
+
367
+ ```powershell
368
+ # PowerShell
369
+ $DEV_LOG = ".jdi/cache/dev-server.log"
370
+ $DEV_PID_FILE = ".jdi/cache/dev-server.pid"
371
+
372
+ $proc = Start-Process -FilePath pwsh -ArgumentList "-NoProfile", "-Command", $DEV_COMMAND `
373
+ -RedirectStandardOutput $DEV_LOG -RedirectStandardError $DEV_LOG `
374
+ -PassThru -WindowStyle Hidden
375
+ $proc.Id | Out-File -FilePath $DEV_PID_FILE
376
+
377
+ $ready = $false
378
+ for ($i = 0; $i -lt 60; $i++) {
379
+ try {
380
+ $r = Invoke-WebRequest -Uri $FRONTEND_URL -UseBasicParsing -TimeoutSec 2 -ErrorAction Stop
381
+ if ($r.StatusCode -eq 200) { $ready = $true; break }
382
+ } catch {}
383
+ Start-Sleep -Seconds 1
384
+ }
385
+
386
+ if (-not $ready) {
387
+ Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
388
+ $err = @{ status="INCONCLUSIVE"; reason="dev server failed to start in 60s"; logs=$DEV_LOG } | ConvertTo-Json
389
+ $err | Out-File .jdi/cache/ui-findings.json
390
+ exit 0
391
+ }
392
+ ```
393
+
394
+ ### Step 7: Run Playwright
395
+
396
+ ```bash
397
+ # bash
398
+ JDI_FRONTEND_URL="$FRONTEND_URL" \
399
+ JDI_ROUTES="$(echo $CRITICAL_PATHS | tr '\n' ',')" \
400
+ JDI_OUT=".jdi/cache/ui-findings.json" \
401
+ JDI_SCREENSHOT_DIR=".jdi/cache/screenshots" \
402
+ $PW_BIN test --config=.jdi/cache/playwright.config.js 2>&1 | tee .jdi/cache/playwright.log
403
+
404
+ # Playwright exit code doesn't matter - findings already written by afterAll
405
+ ```
406
+
407
+ ```powershell
408
+ # PowerShell
409
+ $env:JDI_FRONTEND_URL = $FRONTEND_URL
410
+ $env:JDI_ROUTES = ($CRITICAL_PATHS -join ',')
411
+ $env:JDI_OUT = ".jdi/cache/ui-findings.json"
412
+ $env:JDI_SCREENSHOT_DIR = ".jdi/cache/screenshots"
413
+
414
+ & npx playwright test --config=.jdi/cache/playwright.config.js 2>&1 | Tee-Object -FilePath .jdi/cache/playwright.log
415
+ ```
416
+
417
+ ### Step 8: Kill dev server (always, even on failure)
418
+
419
+ ```bash
420
+ # bash
421
+ if [ -f $DEV_PID_FILE ]; then
422
+ PID=$(cat $DEV_PID_FILE)
423
+ # Kill process + children (dev server usually has children: node, esbuild, vite, etc)
424
+ pkill -P $PID 2>/dev/null
425
+ kill $PID 2>/dev/null
426
+ # Safeguard on common ports (in case wrong PID)
427
+ # Vite: 5173, Next: 3000, etc - skip aggressive cleanup
428
+ rm $DEV_PID_FILE
429
+ fi
430
+ ```
431
+
432
+ ```powershell
433
+ # PowerShell
434
+ if (Test-Path $DEV_PID_FILE) {
435
+ $pid = Get-Content $DEV_PID_FILE
436
+ # Kill children first
437
+ Get-CimInstance Win32_Process -Filter "ParentProcessId=$pid" -ErrorAction SilentlyContinue |
438
+ ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
439
+ # Kill the parent
440
+ Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue
441
+ Remove-Item $DEV_PID_FILE
442
+ }
443
+ ```
444
+
445
+ ### Step 9: Return to parent reviewer
446
+
447
+ Skill doesn't write REVIEW.md. Only writes `.jdi/cache/ui-findings.json` + screenshots.
448
+
449
+ Reviewer reads the JSON, classifies severities, and writes "UI Validation" section in REVIEW.md.
450
+
451
+ ## Finding classification (reference for the reviewer)
452
+
453
+ | Finding | Severity |
454
+ |---|---|
455
+ | `console.error` on any route | BLOCK |
456
+ | `network.5xx` on critical_path | BLOCK |
457
+ | `network.4xx` on critical_path | WARN |
458
+ | `network.requestfailed` (CORS/abort/etc) | WARN |
459
+ | `navigationFailures` (404/timeout/etc on critical_path) | BLOCK |
460
+ | `a11y.impact=critical` | BLOCK |
461
+ | `a11y.impact=serious` | BLOCK |
462
+ | `a11y.impact=moderate` | WARN |
463
+ | `a11y.impact=minor` | INFO |
464
+ | `layout.horizontal_scroll` on mobile | BLOCK |
465
+ | `layout.horizontal_scroll` on desktop | INFO |
466
+ | `axe-core failed` (technical error) | WARN |
467
+ | `INCONCLUSIVE` (dev server timeout) | WARN |
468
+ | `SKIPPED` (user declined install) | WARN |
469
+
470
+ ## Expected inputs
471
+
472
+ From PROJECT.md (passed as environment variables by the reviewer):
473
+ - `frontend.frontend_url` -> `FRONTEND_URL`
474
+ - `frontend.dev_command` -> `DEV_COMMAND`
475
+ - `frontend.critical_paths` -> `CRITICAL_PATHS` (list)
476
+
477
+ ## Outputs
478
+
479
+ Files created in `.jdi/cache/` (gitignored):
480
+ - `ui-findings.json` - structured findings
481
+ - `screenshots/*.png` - 1 per route x viewport
482
+ - `dev-server.log` - dev server log
483
+ - `playwright.log` - Playwright run log
484
+ - `playwright-check.spec.js` - generated spec
485
+ - `playwright.config.js` - generated config
486
+
487
+ NEVER commit `.jdi/cache/`.
488
+
489
+ ## Anti-patterns
490
+
491
+ - Running against prod URL - dev local only. Prod is out of scope for this gate
492
+ - 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)
493
+ - Blocking review if Playwright install fails - degrade to SKIPPED
494
+ - Leaving dev server alive after gate - always kill, even on error
495
+ - Committing screenshots - .gitignore guaranteed in pre-flight
496
+ - Running parallel (workers > 1) - local dev server doesn't scale, and race conditions confuse findings
497
+ - Using `--headed` in CI - always headless
498
+ - Trusting Playwright exit code - findings come from afterAll, even with test failure
499
+
500
+ ## References
501
+
502
+ - `references/playwright-setup.md` - Detailed install per package manager + troubleshoot
503
+ - `references/dev-server-detection.md` - Heuristics for detecting ready (curl, wait-on, polling)
504
+ - `references/axe-rules.md` - Mapping of axe rule IDs -> WCAG -> severity
505
+ - `references/auth-flows.md` - Roadmap for authenticated flows (future)
506
+
507
+ ## Examples
508
+
509
+ ### Example 1: Vite + React, Playwright missing, user accepts install
510
+
511
+ ```
512
+ 1. Reviewer triggers gate 7
513
+ 2. Skill detects `npx playwright --version` -> exit 1
514
+ 3. Lockfile = pnpm-lock.yaml -> PKG_MGR=pnpm
515
+ 4. AskUserQuestion -> user picks "Yes, Chromium"
516
+ 5. pnpm add -D @playwright/test @axe-core/playwright
517
+ 6. pnpm exec playwright install chromium
518
+ 7. Spawns `pnpm dev` in bg, PID 12345
519
+ 8. Waits http://localhost:5173 -> ready in 4s
520
+ 9. Runs Playwright on /, /login, /dashboard x mobile + desktop = 6 navigations
521
+ 10. Findings:
522
+ - 1 console error (uncaught promise) on /dashboard mobile + desktop
523
+ - 0 network errors
524
+ - 2 a11y serious on /login (missing label + contrast)
525
+ - 1 horizontal scroll on /dashboard mobile
526
+ 11. Kill PID 12345 + children
527
+ 12. Writes .jdi/cache/ui-findings.json
528
+ 13. Reviewer reads JSON, marks gate 7 = BLOCK (3 issues), writes REVIEW.md
529
+ ```
530
+
531
+ ### Example 2: API-only, has_frontend=false
532
+
533
+ Skill is not even loaded. Reviewer skips gate 7 with SKIPPED.
534
+
535
+ ### Example 3: Dev server fails to start
536
+
537
+ ```
538
+ 1. Spawns `pnpm dev` -> process dies after 2s (port 5173 occupied)
539
+ 2. Poll of 60s expires without 200 OK
540
+ 3. Cleanup of PID
541
+ 4. Writes {"status":"INCONCLUSIVE","reason":"dev server failed to start in 60s"}
542
+ 5. Reviewer marks gate 7 = WARN with link to dev-server.log
543
+ 6. Review not blocked, but user alerted
544
+ ```
545
+
546
+ ### Example 4: User declines to install Playwright
547
+
548
+ ```
549
+ 1. AskUserQuestion -> "No, skip gate 7"
550
+ 2. Writes {"status":"SKIPPED","reason":"user declined Playwright install"}
551
+ 3. Reviewer marks gate 7 = SKIPPED (warn not block)
552
+ 4. REVIEW.md notes "UI Validation: SKIPPED - run /jdi-verify again when you accept installing"
553
+ ```
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: kiss
3
+ description: KISS (Keep It Simple, Stupid). The simplest solution that solves the problem wins. Complexity only justified by real measured pain. Each layer/abstraction must pay its own cost. Applies in any language.
4
+ ---
5
+
6
+ # Skill: KISS
7
+
8
+ > Simplicity is the best design. Every complexity must pay its own cost.
9
+
10
+ KISS is not "dumb code". It's **rejecting unjustified complexity**. Every interface, every layer, every abstraction has maintenance cost — only worth it if it solves real pain.
11
+
12
+ ## Rules
13
+
14
+ ### 1. Default is the simplest
15
+
16
+ Ask before adding:
17
+ - **Function** vs class vs framework?
18
+ - **Variable** vs config vs feature flag?
19
+ - **If/else** vs strategy pattern vs plugin system?
20
+ - **Sync** vs async vs queue vs event bus?
21
+ - **Inline** vs helper vs lib?
22
+
23
+ Start with the leftmost. Only step up if there is a real requirement.
24
+
25
+ ### 2. Complexity must justify pain
26
+
27
+ **Allowed:**
28
+ - New pattern if it has 3+ real cases using it
29
+ - Abstraction layer if it has 2+ implementations that exist today
30
+ - Cache if measurement shows hot path
31
+ - Async if there's unacceptable latency synchronous
32
+ - Plugin system if there are confirmed external extenders
33
+
34
+ **Forbidden:**
35
+ - "Will scale later" without current requirement
36
+ - "Other people might need it" without other people
37
+ - "To make it generic" without 2nd use case
38
+ - "Will look cleaner" trading 5 clear lines for 50 elegant ones
39
+ - Enterprise pattern in small codebase (Repository + UoW + Mediator + CQRS for 10-controller app)
40
+
41
+ ### 3. Cognitive load is a real metric
42
+
43
+ Code you read 10x and write 1x. Optimize for reading:
44
+ - **Named variables** > composite expression
45
+ - **Early return** > nested if/else
46
+ - **Linear function** > jumps between callbacks
47
+ - **Explicit types** > magical inference in large codebase
48
+ - **Simple procedural code** > fancy OOP for 50 lines
49
+
50
+ Rule: code that needs a comment explaining "why so complex" is too complex.
51
+
52
+ ### 4. Indicators of over-engineering
53
+
54
+ Signs the code went over the line:
55
+
56
+ - Interface with 1 implementation
57
+ - Factory/Builder for something instantiated 1x
58
+ - Generic <T> only used with 1 type
59
+ - Config with a key that never changed
60
+ - Abstraction layer that only encapsulates a call to another layer (pass-through)
61
+ - Inheritance hierarchy > 2 levels
62
+ - File with more setup than logic
63
+ - Test that needs 30 lines of mock to run 5 lines of logic
64
+
65
+ ### 5. Refactor is the opposite direction
66
+
67
+ Natural tendency: code grows in complexity. Refactoring = REMOVE complexity that no longer pays.
68
+
69
+ Ask:
70
+ - Does this layer still exist to solve a problem, or did it become tradition?
71
+ - Does this abstraction have 2+ implementations today?
72
+ - If I delete this, what breaks?
73
+ - Can I solve it with 5 lines instead of 50?
74
+
75
+ ## Anti-patterns
76
+
77
+ | Anti-pattern | Symptom |
78
+ |---|---|
79
+ | Interface + 1 implementation | `IUserService` + `UserService` (only 1) — delete the interface, use the class |
80
+ | Generic `<T>` used with 1 type | `Repository<User>` but never `Repository<Order>` — concretize |
81
+ | Factory for new() | `UserFactory.create()` that only does `return new User()` |
82
+ | Config string that never changed | `MAX_RETRIES: 3` in config + nobody ever changed it — hardcode |
83
+ | Inheritance > 2 levels | `BaseEntity -> AuditableEntity -> SoftDeletableEntity -> User` — flatten via composition |
84
+ | Pass-through layer | `Controller -> Service -> Repository -> DbContext` where Service only calls Repository without logic — delete Service |
85
+ | Enterprise pattern without demand | Mediator/CQRS in small app — replace with direct call |
86
+ | Comment explaining "why so complex" | Code lost the war — refactor |
87
+ | Mock setup > logic test | Test gets fragile; code under test is over-coupled |
88
+ | Unused future-proof params | `(opts?: { future?: boolean })` without caller passing — remove |
89
+
90
+ ## Procedure
91
+
92
+ ### Doer (before writing)
93
+
94
+ 1. Ask: "What is the **simplest** version that meets the current requirement?"
95
+ 2. Write that version.
96
+ 3. Only step up complexity if you hit real pain.
97
+ 4. After writing, ask: "Can I delete any layer/parameter/abstraction without losing functionality?"
98
+
99
+ ### Reviewer (gate 5)
100
+
101
+ Over-engineering heuristics:
102
+
103
+ ```bash
104
+ # Interfaces with 1 implementation
105
+ grep -RnE '^(public |export )?interface I?[A-Z]\w+' src/ | while read iface; do
106
+ name=$(echo "$iface" | grep -oE '[A-Z]\w+\b' | head -1)
107
+ count=$(grep -RnE "class \w+\s*:\s*$name|implements $name" src/ | wc -l)
108
+ [[ $count -eq 1 ]] && echo "WARN: $iface has only 1 implementation"
109
+ done
110
+
111
+ # Deep inheritance (> 2 levels)
112
+ # (depends on stack — specific heuristic)
113
+
114
+ # Very large or nested functions
115
+ grep -cE '^\s{20,}\S' src/**/* # lines with 20+ spaces = deep nesting
116
+ ```
117
+
118
+ Match -> WARN with suggestion to simplify.
119
+
120
+ ## Inputs
121
+
122
+ - Diff/content of the file
123
+ - Context: codebase size (over-engineering is relative)
124
+
125
+ ## Outputs
126
+
127
+ Does NOT produce a file. Modifies judgement.
128
+
129
+ ## Examples
130
+
131
+ ### Example 1: Interface with 1 impl
132
+
133
+ Wrong:
134
+ ```typescript
135
+ interface ILogger { log(msg: string): void }
136
+ class ConsoleLogger implements ILogger { log(msg) { console.log(msg) } }
137
+ const logger: ILogger = new ConsoleLogger()
138
+ ```
139
+
140
+ Right (KISS):
141
+ ```typescript
142
+ function log(msg: string) { console.log(msg) }
143
+ // or
144
+ class Logger { static log(msg: string) { console.log(msg) } }
145
+ ```
146
+
147
+ Add interface when 2nd impl arrives, not before.
148
+
149
+ ### Example 2: Pass-through service
150
+
151
+ Wrong:
152
+ ```csharp
153
+ public class UserService {
154
+ public User GetById(int id) => _repo.GetById(id); // only calls repo
155
+ }
156
+ ```
157
+
158
+ Right: use `_repo` directly in the controller. Add Service when there is real logic (validation, multi-step, transaction, event).
159
+
160
+ ### Example 3: Hardcodable config
161
+
162
+ Wrong: `appsettings.json -> "MaxItemsPerPage": 50` that nobody ever changed in 2 years.
163
+
164
+ Right: `const MAX_ITEMS_PER_PAGE = 50` in the code. Move back to config if some client actually needs to customize.