similarbuild 0.1.0 → 0.2.1

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 (45) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/README.md +2 -2
  3. package/package.json +1 -1
  4. package/templates/commands/build-page.md +73 -14
  5. package/templates/commands/build-site.md +122 -13
  6. package/templates/commands/clip-section.md +67 -8
  7. package/templates/memory/anti-patterns.md +12 -12
  8. package/templates/memory/fixes.md +17 -17
  9. package/templates/presets/shopify-section.yaml +4 -4
  10. package/templates/presets/wp-elementor.yaml +4 -4
  11. package/templates/skills/sb-build-shopify/references/shopify-build-rules.md +2 -2
  12. package/templates/skills/sb-build-wp/SKILL.md +9 -0
  13. package/templates/skills/sb-build-wp/references/wp-build-rules.md +44 -2
  14. package/templates/skills/sb-build-wp/scripts/build-wp.mjs +132 -7
  15. package/templates/skills/sb-build-wp/scripts/tests/test-build-wp.mjs +174 -0
  16. package/templates/skills/sb-compare-visual/SKILL.md +8 -8
  17. package/templates/skills/sb-compare-visual/scripts/compare-visual.mjs +7 -7
  18. package/templates/skills/sb-compare-visual/scripts/lib/compare-tokens.mjs +1 -1
  19. package/templates/skills/sb-crawl-and-list/SKILL.md +1 -1
  20. package/templates/skills/sb-extract-assets/scripts/extract-assets.mjs +50 -2
  21. package/templates/skills/sb-extract-assets/scripts/tests/test-extract-assets.mjs +107 -1
  22. package/templates/skills/sb-inspect-live/SKILL.md +30 -2
  23. package/templates/skills/sb-inspect-live/scripts/inspect-live.mjs +736 -4
  24. package/templates/skills/sb-inspect-live/scripts/tests/test-inspect-live.mjs +263 -1
  25. package/templates/skills/sb-review-checks/references/review-rules.md +1 -1
  26. package/templates/skills/sb-review-checks/scripts/lib/anti-patterns.mjs +8 -2
  27. package/templates/skills/sb-review-checks/scripts/tests/test-anti-patterns.mjs +23 -0
  28. package/templates/skills/sb-test-interactivity/SKILL.md +133 -0
  29. package/templates/skills/sb-test-interactivity/scripts/test-interactivity.mjs +970 -0
  30. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/aria-controls-broken.html +32 -0
  31. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/aria-controls-good.html +47 -0
  32. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/deferred-listeners.html +67 -0
  33. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/details-good.html +25 -0
  34. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/dialog-good.html +38 -0
  35. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/no-interactive.html +15 -0
  36. package/templates/skills/sb-test-interactivity/scripts/tests/test-test-interactivity.mjs +223 -0
  37. package/templates/skills/sb-tweak/SKILL.md +6 -6
  38. package/templates/skills/sb-tweak/references/tweak-patterns.md +4 -4
  39. package/templates/skills/sb-tweak/scripts/tests/test-element-locator.mjs +1 -1
  40. package/templates/skills/sb-tweak/scripts/tests/test-intent-parser.mjs +1 -1
  41. package/templates/skills/sb-tweak/scripts/tests/test-tweak.mjs +2 -2
  42. package/templates/skills/sb-tweak/scripts/tweak.mjs +10 -10
  43. package/templates/skills/{sb-validate-render → sb-validate-static-render}/SKILL.md +6 -6
  44. package/templates/skills/{sb-validate-render/scripts/tests/test-validate-render.mjs → sb-validate-static-render/scripts/tests/test-validate-static-render.mjs} +5 -5
  45. package/templates/skills/{sb-validate-render/scripts/validate-render.mjs → sb-validate-static-render/scripts/validate-static-render.mjs} +10 -10
package/CHANGELOG.md CHANGED
@@ -31,7 +31,7 @@ Claude Code.
31
31
  - `sb-build-shopify` — compositor Shopify Section `.liquid` com `{% schema %}`
32
32
  editável, settings agrupadas, presets, sem `image_picker default`,
33
33
  `{{ block.shopify_attributes }}` em iterações.
34
- - `sb-validate-render` — render offline via preset YAML, captura screenshot +
34
+ - `sb-validate-static-render` — render offline via preset YAML, captura screenshot +
35
35
  probe de tokens; suporta `--assets-map-path` pra Shopify mock context (resolve
36
36
  `image_picker` settings via id-keyword × context-substring).
37
37
  - `sb-compare-visual` — pixelmatch + token cross-check, scope-aware com
package/README.md CHANGED
@@ -26,7 +26,7 @@ determinísticas** atrás de **3 slash commands** que entregam o arquivo pronto:
26
26
  Pipeline padrão (cada step é uma sub-skill):
27
27
 
28
28
  ```
29
- inspect-live → extract-assets → build-{wp|shopify} → validate-render
29
+ inspect-live → extract-assets → build-{wp|shopify} → validate-static-render
30
30
  → compare-visual → review-checks → (auto-correct loop) → entrega
31
31
  ```
32
32
 
@@ -175,7 +175,7 @@ Cada arquivo é machine-parseable (campos
175
175
  | `sb-extract-assets` | Baixa imagens, strip metadata (EXIF/XMP/IPTC), dedupe content-hash. |
176
176
  | `sb-build-wp` | Compõe HTML/Elementor com defensive specificity + a11y/perf. |
177
177
  | `sb-build-shopify` | Compõe `.liquid` com `{% schema %}` editável + shims locais. |
178
- | `sb-validate-render` | Renderiza fragment offline (preset YAML), captura screenshot+tokens.|
178
+ | `sb-validate-static-render` | Renderiza fragment offline (preset YAML), captura screenshot+tokens.|
179
179
  | `sb-compare-visual` | pixelmatch + token cross-check, scope-aware crops simétricos. |
180
180
  | `sb-review-checks` | Cheerio audit de 14 anti-patterns + 12 design checks (a11y/perf/web).|
181
181
  | `sb-tweak` | Edita arquivo entregue via pedido natural PT/EN, atomic-revert. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "similarbuild",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Visual migration framework for Claude Code — clone a live page, get a paste-ready WordPress/Elementor or Shopify section file, validated and auto-corrected.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,9 +13,9 @@ Receive ONE URL and a target (WP or Shopify) and deliver ONE file the user can p
13
13
 
14
14
  ## The four non-negotiables (inherited from the bootstrap)
15
15
 
16
- 1. **Mobile-first sempre.** Default viewport `390×844` on every `sb-inspect-live` and `sb-validate-render` call. Override only if the user explicitly demanded desktop.
16
+ 1. **Mobile-first sempre.** Default viewport `390×844` on every `sb-inspect-live` and `sb-validate-static-render` call. Override only if the user explicitly demanded desktop.
17
17
  2. **Defensive specificity is mandatory.** This is enforced inside `sb-build-wp` / `sb-build-shopify` — your job is to never strip `fixHints` that target it.
18
- 3. **Validate before showing.** NEVER print the build path, contents, or "ready to ship" message to the user before BOTH `sb-validate-render` AND `sb-compare-visual` confirm `diffPercent < threshold` (or, after auto-correction exhausted, you escalate explicitly).
18
+ 3. **Validate before showing.** NEVER print the build path, contents, or "ready to ship" message to the user before BOTH `sb-validate-static-render` AND `sb-compare-visual` confirm `diffPercent < threshold` (or, after auto-correction exhausted, you escalate explicitly).
19
19
  4. **No fabrication.** If `sb-extract-assets` returns a `failed[]` entry, surface it; never invent a substitute URL. If `sb-inspect-live` returns `widgetBlocked: true`, stop and escalate to user — don't compose from a blocked page.
20
20
 
21
21
  ---
@@ -29,11 +29,12 @@ Receive ONE URL and a target (WP or Shopify) and deliver ONE file the user can p
29
29
  | `sb-inspect-live` | **Bash** → `node .claude/skills/sb-inspect-live/scripts/inspect-live.mjs ...` | 95%+ deterministic. Stable CLI. LLM adds zero value to the invocation — it just passes args. |
30
30
  | `sb-extract-assets` | **Bash** → `node .claude/skills/sb-extract-assets/scripts/extract-assets.mjs ...` | Same. Pure download + sanitize + dedupe. |
31
31
  | `sb-build-wp` / `sb-build-shopify` | **Skill** tool → `Skill(skill="sb-build-wp", args="...")` | **The composition is creative**: pick the pattern, name the scope, place defensive specificity. The skill's SKILL.md + `references/wp-build-rules.md` are the LLM's manual. Bash can't compose. |
32
- | `sb-validate-render` | **Bash** → `node .claude/skills/sb-validate-render/scripts/validate-render.mjs ...` | Headless render + token probe. Fully scripted. |
32
+ | `sb-validate-static-render` | **Bash** → `node .claude/skills/sb-validate-static-render/scripts/validate-static-render.mjs ...` | Headless render + token probe. Fully scripted. |
33
+ | `sb-test-interactivity` | **Bash** → `node .claude/skills/sb-test-interactivity/scripts/test-interactivity.mjs ...` | Headless behavioral probe (aria-controls / details / dialog). Diagnostic gate — `passed:false` becomes a warning, not a block. |
33
34
  | `sb-compare-visual` | **Bash** → `node .claude/skills/sb-compare-visual/scripts/compare-visual.mjs ...` | pixelmatch + token diff. Pure determinism. |
34
35
  | `sb-review-checks` | **Bash** → `node .claude/skills/sb-review-checks/scripts/review-checks.mjs ...` | cheerio + regex. Pure determinism. Its `candidateFix` strings are the contract — you forward them verbatim, never reformat. |
35
36
 
36
- **Why not all-Skill:** the deterministic five would burden the orchestrator with their SKILL.md instructions on every loop iteration (the auto-correct loop calls `validate-render` + `compare-visual` + `review-checks` up to N+1 times). Bash keeps each call to one CLI invocation and one JSON parse.
37
+ **Why not all-Skill:** the deterministic five would burden the orchestrator with their SKILL.md instructions on every loop iteration (the auto-correct loop calls `validate-static-render` + `compare-visual` + `review-checks` up to N+1 times). Bash keeps each call to one CLI invocation and one JSON parse.
37
38
 
38
39
  **Why not all-Bash:** `sb-build-wp` has no compose CLI — its `build-wp.mjs` only handles `validate` and `write` subcommands. The actual composition is the LLM reading the skill's references and producing HTML. That work has to happen via Skill.
39
40
 
@@ -100,11 +101,11 @@ If the user passes any other flag, ignore it and continue (don't error on unknow
100
101
  3. **Project slug.**
101
102
  - If `--project-name <slug>` is given: use it verbatim (validate it's URL-safe; warn if not).
102
103
  - Else: derive from URL hostname.
103
- - Parse hostname (e.g. `https://www.alphainfusemen.com/path?q=x` → `www.alphainfusemen.com`).
104
+ - Parse hostname (e.g. `https://www.example-store.com/path?q=x` → `www.example-store.com`).
104
105
  - Strip `www.`, strip leading subdomain only when it's `www`/`m`/`store`/`shop`.
105
106
  - Strip TLD (last `.xxx` or `.xxx.yy` for known compound TLDs like `.com.br`, `.co.uk`).
106
107
  - Lowercase, replace any non-`[a-z0-9]` with `-`, collapse repeats, trim leading/trailing `-`.
107
- - Examples: `https://alphainfusemen.com` → `alphainfusemen`; `https://www.lojaexemplo.com.br/products/x` → `lojaexemplo`.
108
+ - Examples: `https://example-store.com` → `example-store`; `https://www.lojaexemplo.com.br/products/x` → `lojaexemplo`.
108
109
  - Hold as `{project-slug}` for the rest of the run.
109
110
 
110
111
  4. **Project structure.** Compute `{project-root}/{output_folder}/{project-slug}/` and ensure these subdirs exist (mkdir -p, idempotent):
@@ -202,7 +203,7 @@ Skill(
202
203
 
203
204
  If you have a `{designKnowledgeSubset}` or `{patternsSubset}` to pass, include them as additional `args` keys (`design-knowledge-inline=<base64-or-path>`); otherwise omit and let the skill use its bundled defaults.
204
205
 
205
- **The skill returns:** absolute path to the written file, validator's report (errors/warnings), and a one-line note on the chosen pattern. Do not request the file's contents back — you'll let `sb-validate-render` open it.
206
+ **The skill returns:** absolute path to the written file, validator's report (errors/warnings), and a one-line note on the chosen pattern. Do not request the file's contents back — you'll let `sb-validate-static-render` open it.
206
207
 
207
208
  If the skill's validator returns errors and the skill couldn't fix them, surface to the user; this is a builder-side defect, not an auto-correct case.
208
209
 
@@ -211,7 +212,7 @@ If the skill's validator returns errors and the skill couldn't fix them, surface
211
212
  ## Step 4 — Validate render (Bash)
212
213
 
213
214
  ```bash
214
- node .claude/skills/sb-validate-render/scripts/validate-render.mjs \
215
+ node .claude/skills/sb-validate-static-render/scripts/validate-static-render.mjs \
215
216
  --file "{output-path}" \
216
217
  --preset "{preset}" \
217
218
  --output-dir "{output_folder}/{project-slug}/reports/validations/{slug}-{iteration}" \
@@ -227,7 +228,7 @@ Where `{preset}` is `wp-elementor` or `shopify-section`, `{slug}` is the section
227
228
  `image_picker` setting that the builder emitted *without a `default`* (per
228
229
  anti-pattern #12) renders as the `{% else %}` placeholder — a neutral grey
229
230
  fallback — which inflates `compare-visual` diff% by ~30pp against the live
230
- photo with no actionable fix. With it, `validate-render` mocks the
231
+ photo with no actionable fix. With it, `validate-static-render` mocks the
231
232
  image_picker context from matching assetsMap entries (id-keyword → context-
232
233
  substring heuristic) so the rendered fragment shows real imagery.
233
234
 
@@ -246,6 +247,31 @@ Capture stdout JSON → `render`. Hold `{render-screenshot}` (`render.screenshot
246
247
 
247
248
  ---
248
249
 
250
+ ## Step 4-bis — Test interactivity (Bash, diagnostic gate)
251
+
252
+ ```bash
253
+ node .claude/skills/sb-test-interactivity/scripts/test-interactivity.mjs \
254
+ --file "{output-path}" \
255
+ --preset "{preset}" \
256
+ --output-dir "{output_folder}/{project-slug}/reports/validations/{slug}-{iteration}"
257
+ ```
258
+
259
+ Capture `interactivity-report.json` (same dir as `validate-static-render`). Hold the parsed report.
260
+
261
+ **Diagnostic mode (NOT a blocker):**
262
+
263
+ - Exit `!= 0` → log stderr verbatim and continue to Step 5 with no warning. Common hint: `playwright`/chromium missing — suggest `npx playwright install chromium`. Don't escalate.
264
+ - Exit `0` AND `passed === true` → silently proceed. No warning, no status change.
265
+ - Exit `0` AND `passed === false`:
266
+ - **Append** to `interactivityWarnings` array (NOT single-shot overwrite): `interactivityWarnings.push({ iteration: <n>, tests: report.tests.filter(t => !t.passed) })`. Iterations with 0 warnings do NOT append. Cross-iter history preserved — useful for the success/escalation path (Steps 8/10) which renders a section in `report.html` summarizing which iter saw which break.
267
+ - Status badge on the build's report ganha sufixo `+!interactive` (e.g. `✅+!interactive`). Calculated dynamically: applies if `interactivityWarnings.length > 0` em qualquer iter.
268
+ - **Final status remains whatever the decision matrix decides** (Step 7). No promotion to ❌. The skill is diagnostic, not a gatekeeper — policy locked in G2 spec change log. A user who wants hard blocking can re-run with the warning visible and choose to discard the build.
269
+ - Continue to Step 5.
270
+
271
+ **Why diagnostic and not blocker:** `sb-test-interactivity` is new (G2). False-positives during early adoption are expected (web components outside the canonical whitelist, drawers with atypical transitions). Promoting to hard gate before maturity would block good builds. Explicit warning + `interactivityWarnings` in the report gives visibility without blocking. Promotion to blocker is a separate spec decision (G8+).
272
+
273
+ ---
274
+
249
275
  ## Step 5 — Compare visual (Bash)
250
276
 
251
277
  ```bash
@@ -283,7 +309,7 @@ drift. Crop both sides to their measured bboxes whenever possible.
283
309
  of points with no actual visual drift.
284
310
 
285
311
  Both DPR flags default to `3` (matches `sb-inspect-live`'s iPhone 14 profile and
286
- `sb-validate-render`'s aligned `deviceScaleFactor=3`, Pattern #22). Don't override
312
+ `sb-validate-static-render`'s aligned `deviceScaleFactor=3`, Pattern #22). Don't override
287
313
  unless one of those skills was invoked at a different DPR.
288
314
 
289
315
  Capture stdout JSON → `compare`. Note the path to the persisted `report.json` → `{compare-report-path}`.
@@ -322,6 +348,39 @@ Capture stdout JSON → `review`. Note `review.passed` and the `violations[]` ar
322
348
 
323
349
  ## Step 7 — Decision matrix (Pattern #28)
324
350
 
351
+ **Pre-check 0 (coverage gate, runs FIRST):**
352
+
353
+ Calcule contra schema **real** do `sb-inspect-live`:
354
+
355
+ ```
356
+ buildHeight = render.geometry.totalHeight # primary: from sb-validate-static-render
357
+ liveMainHeight = inspection.dom[0].bbox.h # primary: root walked node bbox (body/main)
358
+ || (walk inspection.dom recursively, collect classified-sections bboxes,
359
+ return max(s.bbox.y + s.bbox.h) - min(s.bbox.y))
360
+ # fallback: span das classified sections (warn)
361
+ coverageRatio = buildHeight / liveMainHeight
362
+ ```
363
+
364
+ `inspection.dom` is **array of root nodes** (schema in `.claude/skills/sb-inspect-live/scripts/inspect-live.mjs:165,1221`). Each node has `bbox: {x, y, w, h}` and optional `sectionType` for classified sections.
365
+
366
+ If `inspection.dom[]` is empty AND no classified sections collectable → log warn `[build-page] WARN: coverage gate skipped — empty inspection.dom`. Skip coverage gate; proceed to existing matrix. Persist `coverage = null`.
367
+
368
+ If `buildHeight` is missing (defeito do `sb-validate-static-render`) → HALT with clear error `[build-page] FATAL: render.geometry.totalHeight missing`. Don't try to recover.
369
+
370
+ If `liveMainHeight < 100` (degenerate) → log warn `[build-page] WARN: liveMainHeight < 100, coverage gate skipped`; skip; persist `coverage.skippedReason = 'degenerate-height'`.
371
+
372
+ **Tier triage:**
373
+
374
+ | Range | Status | Action |
375
+ | --- | --- | --- |
376
+ | `< 0.40` | ❌ **incomplete** | Status: `"incomplete — built {ratio*100:.0f}% of source"`. Goes to existing Step 9 (auto-correct loop) if budget remains; existing `review.violations[]` driven fixHints feed back to composer. **NO new EXTENSION marker** — Step 9 already handles auto-correct via review-derived fixHints. Coverage `< 0.40` overrides matrix to ❌ (without it, matrix could route to ⚠️ and miss the issue). |
377
+ | `0.40 — 0.85` | ⚠️ **partial** | Status: `"partial — built {ratio*100:.0f}%"`. Proceeds to matrix below (does NOT bypass). Coverage warning rides on top of whatever the matrix decides. |
378
+ | `>= 0.85` | (proceed) | Coverage OK. Matrix below runs silently. |
379
+
380
+ Persist `coverage = { buildHeight, liveMainHeight, ratio, source: 'rootBbox'|'sectionsSpan', missingSections?: [...], skippedReason?: <string> }` in the success/escalation report. `missingSections` (when `ratio < 0.85`): walk `inspection.dom` recursively, collect classified-section nodes whose `bbox.y >= buildHeight`.
381
+
382
+ **Existing matrix (runs only when coverage gate didn't route to ❌):**
383
+
325
384
  You hold both `compare.passed` and `review.passed` now. Apply this matrix:
326
385
 
327
386
  | `review.passed` | `compare.passed` | Action |
@@ -366,7 +425,7 @@ You only reach here when `review.passed === true` AND `compare.passed === true`.
366
425
 
367
426
  ## Step 9 — Auto-correct loop body
368
427
 
369
- You only reach here from Step 7's matrix when **both** `review.passed === false` AND `compare.passed === false`. The pre-checks (no-auto-correct, budget exhausted) and the validate-render hard-failure (`tiny-screenshot`) all reroute to Step 10.
428
+ You only reach here from Step 7's matrix when **both** `review.passed === false` AND `compare.passed === false`. The pre-checks (no-auto-correct, budget exhausted) and the validate-static-render hard-failure (`tiny-screenshot`) all reroute to Step 10.
370
429
 
371
430
  `review.violations[]` is in hand from Step 6 — fixHints to feed back into the builder.
372
431
 
@@ -446,10 +505,10 @@ You only reach here when the budget is spent. Be honest:
446
505
 
447
506
  ## Output structure (recap, for sanity)
448
507
 
449
- After a successful run on `https://alphainfusemen.com`:
508
+ After a successful run on `https://example-store.com`:
450
509
 
451
510
  ```
452
- {output_folder}/alphainfusemen/
511
+ {output_folder}/example-store/
453
512
  ├── clean/
454
513
  │ └── home/hero.html ← the deliverable
455
514
  ├── assets/
@@ -484,7 +543,7 @@ Never write outside `{project-slug}/`. Cross-project assets sharing happens only
484
543
  When this command is first wired up, the canonical end-to-end smoke test is:
485
544
 
486
545
  ```
487
- /build-page https://alphainfusemen.com
546
+ /build-page https://example-store.com
488
547
  ```
489
548
 
490
549
  Expected: a clean `home/hero.html` (or the home's first detected section), under-threshold diff, report.html opening successfully. That's the MVP milestone.
@@ -13,9 +13,9 @@ Receber UMA URL raiz e um target (WP ou Shopify), descobrir todas as páginas re
13
13
 
14
14
  ## The five non-negotiables
15
15
 
16
- 1. **Mobile-first sempre.** Default viewport `390×844` em toda chamada `sb-inspect-live` e `sb-validate-render`. Override só se o usuário pedir desktop explicitamente.
16
+ 1. **Mobile-first sempre.** Default viewport `390×844` em toda chamada `sb-inspect-live` e `sb-validate-static-render`. Override só se o usuário pedir desktop explicitamente.
17
17
  2. **Defensive specificity é mandatório.** Aplicado dentro de `sb-build-wp`/`sb-build-shopify` — seu trabalho é nunca comer `fixHints` que cubram isso.
18
- 3. **Validate before showing.** NUNCA imprima paths de build, conteúdo do HTML, ou mensagem "ready to ship" antes de `sb-validate-render` E `sb-compare-visual` confirmarem `diffPercent < threshold` por página (ou, após o auto-correct esgotar, marque a página como ⚠️ explicitamente). Isso vale por página — uma falha em `pdp/foo.html` não bloqueia a entrega de `home/hero.html`, mas cada página ainda passa pela porta antes de virar ✅.
18
+ 3. **Validate before showing.** NUNCA imprima paths de build, conteúdo do HTML, ou mensagem "ready to ship" antes de `sb-validate-static-render` E `sb-compare-visual` confirmarem `diffPercent < threshold` por página (ou, após o auto-correct esgotar, marque a página como ⚠️ explicitamente). Isso vale por página — uma falha em `pdp/foo.html` não bloqueia a entrega de `home/hero.html`, mas cada página ainda passa pela porta antes de virar ✅.
19
19
  4. **No fabrication.** `assetsMap.failed[]` é forwardado pro builder — nunca invente URL substituta. `inspection.widgetBlocked: true` em uma página → marque essa página como ⚠️ e siga o batch; em UMA página inicial bloqueada (a raiz), discuta com o usuário antes de prosseguir.
20
20
  5. **Único checkpoint humano = confirmar a lista de páginas.** Esta é a regra inegociável que distingue `/build-site` de `/build-page`. NÃO faça outras perguntas no meio do batch. Auto-correção é loop fechado, por página, sem interromper o usuário. A única outra interação opcional é o auto-learn batched no final.
21
21
 
@@ -31,7 +31,8 @@ Receber UMA URL raiz e um target (WP ou Shopify), descobrir todas as páginas re
31
31
  | `sb-inspect-live` | **Bash** | 95%+ deterministic. Stable CLI. Roda 1× por página. |
32
32
  | `sb-extract-assets` | **Bash** | Pure download + sanitize + dedupe. **`--existing-assets-dir` apontado para `{output_folder}/{project-slug}/assets/` em todas as páginas do batch** — dedupe automático cross-page por content-hash. |
33
33
  | `sb-build-wp` / `sb-build-shopify` | **Skill** tool | A composição é criativa. Roda 1× por página (mais até N retries por página). |
34
- | `sb-validate-render` | **Bash** | Headless render + token probe. Pure scripted. |
34
+ | `sb-validate-static-render` | **Bash** | Headless render + token probe. Pure scripted. |
35
+ | `sb-test-interactivity` | **Bash** | Headless behavioral probe (aria-controls / details / dialog). Diagnostic gate — `passed:false` vira warning em `pageResults[]`, não bloqueia. |
35
36
  | `sb-compare-visual` | **Bash** | pixelmatch + token diff. Pure determinismo. |
36
37
  | `sb-review-checks` | **Bash** | cheerio + regex. Pure determinismo. `candidateFix` strings são contrato — forward verbatim. |
37
38
 
@@ -66,6 +67,7 @@ O texto após `ARGUMENTS:` contém a entrada do usuário. Extraia:
66
67
  | `--no-auto-correct` | no | false | Em cada página, escala no primeiro decision-matrix que pediria loop. |
67
68
  | `--max-pages <n>` | no | (deixa o default da skill, `200`) | Repassa pra `sb-crawl-and-list` como hard cap. |
68
69
  | `--sitemap-path <path>` | no | (none) | Repassa pra `sb-crawl-and-list`. Útil pra SPAs ou sites que bloqueiam crawler. |
70
+ | `--no-globals` | no | false | Opt-out explícito do Step 4d (globals auto-extract). Sem essa flag, header/footer compartilhados são extraídos pra `clean/global/` quando `crawl.pageCount >= 3` (default ON). Use quando o site não tem chrome compartilhado entre páginas, ou quando estiver fazendo um run de debug que precisa ver o markup completo por página. |
69
71
  | `--dry-run` | no | false | Roda crawl + checkpoint + plan-summary, **não builda**. Imprime o plano (URLs, types, assets estimados) e sai. |
70
72
 
71
73
  Flag desconhecida → ignore com warning de uma linha. Não erre.
@@ -289,11 +291,50 @@ node .claude/skills/sb-extract-assets/scripts/extract-assets.mjs \
289
291
 
290
292
  `assetsMap.failed[]` non-empty → forwarde pro builder, **não** pare a página.
291
293
 
292
- ### Step 4d — Detectar global sections (header/footer compartilhados)
294
+ ### Step 4d — Extract global sections (header/footer compartilhados)
293
295
 
294
- Heurística: depois de inspecionar e antes de buildar, registre `inspection.sections[]` (ou equivalente) por hash estrutural numa tabela em memória `globalSectionTracker = { headerHash: count, footerHash: count, ... }`. Quando uma seção (header ou footer) tiver `count >= 3` páginas com o MESMO hash, a próxima vez que for emitida será gravada em `clean/global/{header,footer}.{html|liquid}` (uma vez), e os builds posteriores referenciam ao invés de inline.
296
+ **Default ON** quando `crawl.pageCount >= 3` AND a flag `--no-globals` NÃO foi passada. Caso contrário, skip este Step inteiro com uma linha em `decisions.md` (`Step 4d skipped: pageCount<3` OU `Step 4d skipped: --no-globals opt-out`).
295
297
 
296
- > **MVP escape hatch:** se `inspection` da skill atual não emite hashes de seção comparáveis, **skip silenciosamente** este passo — o asset cache já cobre a otimização principal (imagens). Documente em `decisions.md` que global-section dedupe está pendente. Não tente computar hash por conta própria com regex de HTML — fica frágil.
298
+ Estado mantido pelo orchestrator durante o batch:
299
+
300
+ ```
301
+ globalsExtracted = {
302
+ header: null | <output-path>, // ex: "clean/global/header.html"
303
+ footer: null | <output-path>,
304
+ }
305
+ ```
306
+
307
+ **Detector estrutural** (sem hash, sem regex sobre HTML cru):
308
+
309
+ - **Header** = primeiro elemento da `inspection.dom` (descida em DFS) cujo `tag === 'header'` E que tenha algum descendant cujo `tag === 'nav'`. Se ausente: **skip globals (não extrai nem strippa) + log warning** em `decisions.md`: `[build-site] WARN: Step 4d header detector missed — no <header> semântico com <nav>; globals not extracted, pages keep chrome inline`. Continue pipeline normalmente. **NÃO pergunte ao humano** — non-negotiable #5 (`single human checkpoint = page list confirmation`) proíbe queries mid-batch. Decisão de fix manual fica pra próximo run com `--no-globals` ou refresh do crawl.
310
+
311
+ - **Footer** = último elemento que é **descendant direto-de-body-ou-main** (i.e., `parent.tag === 'body'` OU `parent.tag === 'main'`) cujo `tag === 'footer'` OU cujo atributo `role === 'contentinfo'`. **NÃO** o último em DFS pós-ordem — isso pegava blog-post `<footer>` ou article `<footer>` em vez do site footer (bug example-shop-class em sites de conteúdo). Se ausente: skip + warn (mesmo padrão do header acima).
312
+
313
+ **Trigger window:** Step 4d roda apenas quando `globalsExtracted.header === null` (e separadamente para footer). Ou seja, só na primeira inspection que casar o detector. Inspeções subsequentes não re-processam — o asset já está em disco e é referenciado.
314
+
315
+ **Extração:**
316
+ 1. Localize header e/ou footer no `inspection.dom` (em memória).
317
+ 2. Serialize cada um pro fragment HTML/Liquid passando pelo composer (`sb-build-wp` ou `sb-build-shopify`) com hint `--target-section=header|footer` (composer trata como input de seção, não página inteira). Output: `clean/global/{header,footer}.{html|liquid}`.
318
+ 3. Set `globalsExtracted.header` (e/ou `.footer`) = output path.
319
+
320
+ **Stripper (mandatório quando globals extracted):** ANTES de invocar o composer pra páginas individuais (Step 4e):
321
+
322
+ 1. Clone a `inspection` em memória.
323
+ 2. Em `inspection.dom`, **remova** as subtrees do `<header>` semântico (se `globalsExtracted.header !== null`) e do `<footer>`/`[role=contentinfo]` (se `globalsExtracted.footer !== null`).
324
+ 3. Passe a inspection editada ao composer. Composer não vê header/footer e não pode fabricá-los.
325
+ 4. Após o composer retornar, **prepende** ao output uma linha de comentário:
326
+ - HTML: `<!-- sb-build-site: header in clean/global/header.html -->\n` (se header extracted)
327
+ - HTML: `<!-- sb-build-site: footer in clean/global/footer.html -->` (se footer extracted, no fim)
328
+ - Liquid: idem com `{% comment %} ... {% endcomment %}`
329
+ 5. Grava em `clean/{home,pdp,...}/{slug}.html` o output já strippado-e-comentado.
330
+
331
+ **Stripper validation:** após gravar o file, faça um grep rápido:
332
+ ```
333
+ grep -nE "<header[ >]|<footer[ >]" clean/{home,pdp,...}/{slug}.{html,liquid}
334
+ ```
335
+ Padrão sem `^` anchor pra pegar pretty-printed (indented ` <header>`). Glob `{html,liquid}` cobre ambos os targets (WP + Shopify). Se retornar match, é defeito — composer ignorou a inspection editada e re-fabricou, ou stripper passou tag misformada. Log `[build-site] WARN: stripper miss in {slug}.{ext}` em `decisions.md`. Não blocking — orchestrator continua.
336
+
337
+ **Por que detector estrutural e não hash:** hash exige inspection emitir hashes comparáveis. Detector estrutural usa só tags semânticas que `sb-inspect-live` já captura na DOM tree. Funciona com qualquer inspection que tenha um DOM serializado, sem mudança upstream. Cobre o caso comum (sites WordPress/Shopify usam `<header>`/`<footer>` semântico). Para sites com chrome em `<div class="site-header">` sem semântica, o Ask First gate força decisão consciente em vez de heurística silenciosa.
297
338
 
298
339
  ### Step 4e — Build (Skill)
299
340
 
@@ -313,7 +354,7 @@ Se o validator interno do builder retornar erros que ele não conseguiu corrigir
313
354
  ### Step 4f — Validate render (Bash)
314
355
 
315
356
  ```bash
316
- node .claude/skills/sb-validate-render/scripts/validate-render.mjs \
357
+ node .claude/skills/sb-validate-static-render/scripts/validate-static-render.mjs \
317
358
  --file "{output-path}" \
318
359
  --preset "{preset}" \
319
360
  --output-dir "{output_folder}/{project-slug}/reports/validations/{slug}-{iteration}" \
@@ -322,10 +363,33 @@ node .claude/skills/sb-validate-render/scripts/validate-render.mjs \
322
363
  [--assets-map-path "{assets-map-path}"]
323
364
  ```
324
365
 
325
- **`--assets-map-path` (Pattern #32, Shopify-only).** Quando `target === shopify` e Step 4c produziu um `assetsMap`, passe `--assets-map-path "{assets-map-path}"`. Sem isso, `image_picker` settings sem `default` (anti-pattern #12) renderizam o placeholder `{% else %}` no validate, inflando o diff visual em ~30pp contra a foto real da live por motivo não-acionável. Com a flag, `validate-render` popula o mock context pelo match id-keyword → context-substring no assetsMap. Pra `target === wp`, não passe — não há `image_picker` no preset WP.
366
+ **`--assets-map-path` (Pattern #32, Shopify-only).** Quando `target === shopify` e Step 4c produziu um `assetsMap`, passe `--assets-map-path "{assets-map-path}"`. Sem isso, `image_picker` settings sem `default` (anti-pattern #12) renderizam o placeholder `{% else %}` no validate, inflando o diff visual em ~30pp contra a foto real da live por motivo não-acionável. Com a flag, `validate-static-render` popula o mock context pelo match id-keyword → context-substring no assetsMap. Pra `target === wp`, não passe — não há `image_picker` no preset WP.
326
367
 
327
368
  Branches idênticas ao `/build-page` (Step 4): `tiny-screenshot` → hard fail desta iteração; `viewportOverflow: true` → flag pra comparator + suprime `--crop-build-bbox` no Step 4g.
328
369
 
370
+ ### Step 4f-bis — Test interactivity (Bash, diagnostic gate)
371
+
372
+ ```bash
373
+ node .claude/skills/sb-test-interactivity/scripts/test-interactivity.mjs \
374
+ --file "{output-path}" \
375
+ --preset "{preset}" \
376
+ --output-dir "{output_folder}/{project-slug}/reports/validations/{slug}-{iteration}"
377
+ ```
378
+
379
+ Capture `interactivity-report.json` (mesmo dir do `validate-static-render`).
380
+
381
+ **Modo diagnóstico (não-blocker):**
382
+
383
+ - Exit `!= 0` → log stderr verbatim em `pageResults[].errors`. Skipa o gate pra essa iteração. Continua pipeline (Step 4g) normalmente. Hint comum: `playwright`/chromium não instalado — sugira `npx playwright install chromium`.
384
+ - Exit `0` AND `passed === true` → silently proceed. Sem warning, sem mudança de status.
385
+ - Exit `0` AND `passed === false`:
386
+ - `pageResults[].interactivityWarnings` é **array de iteration entries** (NÃO single-shot overwrite). A cada iteration que detecta warnings, **append**: `{ iteration: <n>, tests: report.tests.filter(t => !t.passed) }`. Iterations com 0 warnings NÃO appende nada (não polui o array). Esta estrutura preserva histórico cross-iter — útil pro Step 6 auto-learn cross-page repetition (Cap D), que precisa enxergar TODOS os warnings observados em qualquer iter pra detectar pattern repetido.
387
+ - Status badge no console output ganha sufixo `+!interactive` (ex: `✅+!interactive`, `⚠️+!interactive`). Sufix calculado dinamicamente: applies se `pageResults[i].interactivityWarnings.length > 0` em qualquer iter (mesmo que iter atual seja clean).
388
+ - **Status final permanece o que a decision matrix decidir** (Step 4i). Sem promoção pra ❌. Diagnostic gate, não gatekeeper — política definida em G2 spec change log.
389
+ - Continua pipeline (Step 4g).
390
+
391
+ **Por que diagnostic e não blocker:** `sb-test-interactivity` é skill nova ainda em early-adoption. False-positives nesta janela são esperados (web components fora da whitelist canônica, drawers com transições atípicas). Promover a hard gate antes da skill amadurecer geraria fricção em builds bons. Warning explícito + `interactivityWarnings` em `pageResults[]` dá visibilidade ao usuário sem bloquear. Mudança pra blocker é decisão de spec própria (G8+).
392
+
329
393
  ### Step 4g — Compare visual (Bash)
330
394
 
331
395
  ```bash
@@ -356,6 +420,41 @@ Capture `review`.
356
420
 
357
421
  ### Step 4i — Decision matrix (Pattern #28)
358
422
 
423
+ **Pre-check 0 (coverage gate, runs FIRST, before the matrix):**
424
+
425
+ Calcule contra schema **real** do `sb-inspect-live` (não invenção):
426
+
427
+ ```
428
+ buildHeight = render.geometry.totalHeight # source primário do sb-validate-static-render
429
+ liveMainHeight = inspection.dom[0].bbox.h # primary: root walked node bbox (body/main)
430
+ || (walk inspection.dom recursively, collect classified-sections bboxes,
431
+ return max(s.bbox.y + s.bbox.h) - min(s.bbox.y))
432
+ # fallback: span das classified sections (warn)
433
+ coverageRatio = buildHeight / liveMainHeight
434
+ ```
435
+
436
+ Onde `inspection.dom` é **array de root nodes** (não um object com `.mainBbox`/`.sections` — schema confirmado em `.claude/skills/sb-inspect-live/scripts/inspect-live.mjs:165,1221`). Cada node tem `bbox: {x, y, w, h}` e opcional `sectionType` (presente em sections classificadas durante walk).
437
+
438
+ Se `inspection.dom[]` estiver vazio (page bloqueada, cross-origin only, etc.) E sem classified sections coletáveis → log warn `[build-site] WARN: coverage gate skipped — empty inspection.dom`. Skip coverage gate; prossegue pra matrix com `pageResults[].coverage = null`.
439
+
440
+ Se `buildHeight` está ausente (defeito do `sb-validate-static-render`) → HALT com erro claro `[build-site] FATAL: render.geometry.totalHeight missing — validate-static-render defect`. Não tente recuperar.
441
+
442
+ Se `liveMainHeight < 100` (degenerate: inspection com altura sub-viewport, geralmente erro upstream) → log warn `[build-site] WARN: liveMainHeight < 100, coverage gate skipped`; skip; prossegue pra matrix com `pageResults[].coverage.skippedReason = 'degenerate-height'`.
443
+
444
+ **Tier triage:**
445
+
446
+ | Range | Status | Ação |
447
+ | --- | --- | --- |
448
+ | `< 0.40` | ❌ **incomplete** | `pageResults[].status = ❌`; mensagem: `"incomplete — built {ratio*100:.0f}% of source"`. Vai direto pra Step 4j (auto-correct loop) se budget permite, OR Step 4k (escalation) se exhausted. **NÃO** dispara EXTENSION mode novo: o auto-correct existente já consome `review.violations[]` como `fixHints`. Coverage `< 0.40` apenas força o status ❌ no matrix override (em vez de deixar a matrix decidir) — o composer continua recebendo o feedback canonical via review-checks. |
449
+ | `0.40 — 0.85` | ⚠️ **partial** | `pageResults[].status = ⚠️` com nota: `"partial — built {ratio*100:.0f}%"`. Prossegue pra matrix abaixo (não bypassa). Coverage warning sobrevive como overlay no status final. |
450
+ | `>= 0.85` | (proceed) | Coverage OK. Prossegue pra matrix abaixo silenciosamente. |
451
+
452
+ Anote em `pageResults[].coverage = { buildHeight, liveMainHeight, ratio, source: 'rootBbox'|'sectionsSpan', missingSections?: [...], skippedReason?: <string> }`.
453
+
454
+ `missingSections` é derivado quando `ratio < 0.85`: walk `inspection.dom` recursivamente, coletar nodes com `sectionType` cujo `bbox.y >= buildHeight` (sections que existem na live mas ficaram além do build). Útil pro auto-correct hint.
455
+
456
+ **Decision matrix existente (roda só se coverage gate não escalou pra ❌):**
457
+
359
458
  | `review.passed` | `compare.passed` | Ação na página |
360
459
  | --- | --- | --- |
361
460
  | true | true | ✅ **Página pronta.** Atualiza `pageResults[]` com sucesso, próxima página. |
@@ -363,7 +462,7 @@ Capture `review`.
363
462
  | true | false | ⚠️ **Visual drift sem fixHints.** `pageResults[].status = ⚠️` com top-5 `structuredDiffs`. Próxima página. |
364
463
  | false | false | 🔄 **Auto-correct loop.** Vai pra Step 4j. |
365
464
 
366
- Pré-checks (idênticos ao `/build-page`):
465
+ Pré-checks adicionais (idênticos ao `/build-page`):
367
466
  1. `--no-auto-correct` foi passado → escala primeiro diff de cada página: rotas que iriam pra Step 4j viram ⚠️ direto.
368
467
  2. `iteration >= auto_correct_max_iterations` (default 2) → idem, vira ⚠️.
369
468
  3. **Mesmo `violations[]` 2 iterações seguidas** → fixHint não pegou. Não rode 3ª. ⚠️ imediato.
@@ -383,7 +482,7 @@ Skill(
383
482
 
384
483
  ### Step 4k — Append to pageResults
385
484
 
386
- Após cada página, append `{url, type, slug, status, diffPercent, iterations, outputPath, screenshotsLive, screenshotsBuild, diffMap, violations}` ao `pageResults[]`. Imprima a linha de status no console (formato no início do Step 4).
485
+ Após cada página, append `{url, type, slug, status, diffPercent, iterations, outputPath, screenshotsLive, screenshotsBuild, diffMap, violations, coverage, interactivityWarnings}` ao `pageResults[]`. Imprima a linha de status no console (formato no início do Step 4). Se `interactivityWarnings.length > 0`, sufixa o status badge com `+!interactive`. Se `coverage.ratio < 0.85`, anota `(coverage {ratio*100:.0f}%)` na linha.
387
486
 
388
487
  ---
389
488
 
@@ -393,9 +492,11 @@ Depois que TODAS as páginas confirmadas foram processadas (`pageResults[].lengt
393
492
 
394
493
  1. **Escreva `{output_folder}/{project-slug}/reports/index.html`** com:
395
494
  - Header com root URL, target, project-slug, timestamp do run, totals (`X✅ / Y⚠️ / Z❌`).
396
- - Tabela com uma linha por página: status badge, type, URL → output path link, diff %, iterations, screenshot side-by-side (live + build, thumbs com link pro full), link pro diff map, violations resumo.
495
+ - Tabela com uma linha por página: status badge (com sufixos `+!interactive` quando aplicável), type, URL → output path link, diff %, **coverage %**, iterations, screenshot side-by-side (live + build, thumbs com link pro full), link pro diff map, violations resumo, link pro `interactivity-report.json` quando `interactivityWarnings.length > 0`.
397
496
  - Section "Auto-correct details" listando páginas com iteration > 0 e o que mudou.
398
497
  - Section "Escalations" com páginas ⚠️/❌ — top diffs visuais e candidate fixes inline.
498
+ - Section "Interactivity warnings" listando páginas com `interactivityWarnings.length > 0`, agrupadas por type de teste reprovado (aria-controls / details-summary / dialog) e mostrando o trigger/target + check name por failure.
499
+ - Section "Coverage warnings" listando páginas com `coverage.ratio < 0.85`, ordenadas por ratio asc — primeiro caso é o mais crítico.
399
500
  - Footer com: link pra `pages-confirmed.json`, link pro `crawl/pages-list.json` (raw discovery), config snapshot.
400
501
 
401
502
  2. **Persistência cumulativa.** Se o `report.html` já existir (rerun), preserve runs anteriores em uma section "Previous runs" (hierárquica por timestamp). O run mais recente fica no topo. NÃO sobrescreva tudo.
@@ -406,9 +507,17 @@ Depois que TODAS as páginas confirmadas foram processadas (`pageResults[].lengt
406
507
 
407
508
  ## Step 6 — Batched auto-learn prompt
408
509
 
409
- Único momento opcional de interação após o checkpoint. dispare se houve **pelo menos um fixHint do `sb-review-checks` que destravou um build** durante o batch (i.e. existe ao menos uma página com `iterations > 0` E `status === ✅`).
510
+ Único momento opcional de interação após o checkpoint. Dispare se EITHER condição (a) OU (b) for atendida:
511
+
512
+ **(a) Iter+success (regra original):** existe ao menos uma página com `iterations > 0` E `status === ✅` — i.e. um fixHint do `sb-review-checks` destravou um build durante o batch. Sinal forte: pattern proven.
513
+
514
+ **(b) Cross-page repetition (nova clause):** a MESMA `violation` (matchada por `id`/`pattern-name` em `review.violations[].id`) aparece em pelo menos `ceil(0.3 * pageCount)` páginas do batch — ou seja, em ≥30% das páginas do batch (com floor de 1 pra batches pequenos: batch de 3 → threshold 1; batch de 10 → threshold 3). Sinal médio: pattern que repete batch-wide é provavelmente generalizável mesmo que ninguém destrave.
515
+
516
+ A clause (b) cobre o blind spot do example-shop: heurística #5b false-positive em 10/10 páginas, todas marcadas ⚠️ ou ❌ (nenhuma com iter>0+✅), pattern não persistido. Com a nova clause, batch-wide repetition triggera o digest mesmo sem destrave.
517
+
518
+ A lista para o digest é a UNIÃO dos patterns detectados pelas duas clauses (deduplicado por `id`).
410
519
 
411
- 1. Escaneie esses fixHints. Para cada um, cheque se ele já está coberto pela memory cascade carregada na Step 0. Mantenha só os GENERALIZÁVEIS NOVOS (não específicos do site).
520
+ 1. Escaneie esses fixHints/violations. Para cada um, cheque se ele já está coberto pela memory cascade carregada na Step 0. Mantenha só os GENERALIZÁVEIS NOVOS (não específicos do site).
412
521
 
413
522
  2. Se a lista pós-filtragem não está vazia, pergunte UMA vez ao usuário, em português, em formato de digest:
414
523
 
@@ -15,9 +15,9 @@ Receber UMA URL + UM seletor CSS + um target (WP ou Shopify) e entregar UM arqui
15
15
 
16
16
  ## The four non-negotiables (herdados do bootstrap)
17
17
 
18
- 1. **Mobile-first sempre.** Default viewport `390×844` em toda chamada `sb-inspect-live` e `sb-validate-render`. Override só se o usuário pedir desktop explicitamente.
18
+ 1. **Mobile-first sempre.** Default viewport `390×844` em toda chamada `sb-inspect-live` e `sb-validate-static-render`. Override só se o usuário pedir desktop explicitamente.
19
19
  2. **Defensive specificity é mandatório.** Aplicado dentro de `sb-build-wp` / `sb-build-shopify` — seu trabalho é nunca comer `fixHints` que cubram isso.
20
- 3. **Validate before showing.** NUNCA imprima path de build, conteúdo, ou mensagem "ready to ship" antes de `sb-validate-render` E `sb-compare-visual` confirmarem `diffPercent < threshold` (ou, após o auto-correct esgotar, escale explicitamente).
20
+ 3. **Validate before showing.** NUNCA imprima path de build, conteúdo, ou mensagem "ready to ship" antes de `sb-validate-static-render` E `sb-compare-visual` confirmarem `diffPercent < threshold` (ou, após o auto-correct esgotar, escale explicitamente).
21
21
  4. **No fabrication.** `assetsMap.failed[]` não some — surface ao builder; nunca invente URL substituta. `inspection.widgetBlocked: true` → pare e escale; não componha de página bloqueada. **Selector que não casa nada** → pare e escale; não fall-back pra full-page silenciosamente (isso é fabricação de escopo).
22
22
 
23
23
  ---
@@ -31,7 +31,8 @@ Receber UMA URL + UM seletor CSS + um target (WP ou Shopify) e entregar UM arqui
31
31
  | `sb-inspect-live` | **Bash** → `node .claude/skills/sb-inspect-live/scripts/inspect-live.mjs ...` | 95%+ deterministic. Aceita `--selector <css>` nativamente — escopo limitado ao elemento + descendentes. |
32
32
  | `sb-extract-assets` | **Bash** → `node .claude/skills/sb-extract-assets/scripts/extract-assets.mjs ...` | Pure download + sanitize + dedupe. **`inspection.imgUrls` já vem filtrado pelo selector** — extract roda nessa lista enxuta. |
33
33
  | `sb-build-wp` / `sb-build-shopify` | **Skill** tool → `Skill(skill="sb-build-wp", args="...")` | A composição é criativa. O skill SKILL.md + `references/wp-build-rules.md` é o manual do LLM. Bash não compõe. |
34
- | `sb-validate-render` | **Bash** → `node .claude/skills/sb-validate-render/scripts/validate-render.mjs ...` | Headless render + token probe. Fully scripted. |
34
+ | `sb-validate-static-render` | **Bash** → `node .claude/skills/sb-validate-static-render/scripts/validate-static-render.mjs ...` | Headless render + token probe. Fully scripted. |
35
+ | `sb-test-interactivity` | **Bash** → `node .claude/skills/sb-test-interactivity/scripts/test-interactivity.mjs ...` | Headless behavioral probe (aria-controls / details / dialog). Diagnostic gate — `passed:false` vira warning, não bloqueia. |
35
36
  | `sb-compare-visual` | **Bash** → `node .claude/skills/sb-compare-visual/scripts/compare-visual.mjs ...` | pixelmatch + token diff. Pure determinismo. |
36
37
  | `sb-review-checks` | **Bash** → `node .claude/skills/sb-review-checks/scripts/review-checks.mjs ...` | cheerio + regex. Pure determinismo. `candidateFix` strings são contrato — forward verbatim. |
37
38
 
@@ -241,7 +242,7 @@ Skill(
241
242
 
242
243
  Se tiver `{designKnowledgeSubset}`/`{patternsSubset}` curados, inclua como args extras (`design-knowledge-inline=<base64-or-path>`); senão omita e deixe o skill usar bundled defaults.
243
244
 
244
- **O skill retorna:** path absoluto do arquivo escrito, validator report (errors/warnings), e nota de uma linha sobre o pattern escolhido. Não peça o conteúdo do arquivo de volta — o `sb-validate-render` abre.
245
+ **O skill retorna:** path absoluto do arquivo escrito, validator report (errors/warnings), e nota de uma linha sobre o pattern escolhido. Não peça o conteúdo do arquivo de volta — o `sb-validate-static-render` abre.
245
246
 
246
247
  Se o validator interno do skill retornar erros que ele não conseguiu corrigir → surface ao usuário; isso é defeito do builder, não caso de auto-correct.
247
248
 
@@ -250,7 +251,7 @@ Se o validator interno do skill retornar erros que ele não conseguiu corrigir
250
251
  ## Step 4 — Validate render (Bash)
251
252
 
252
253
  ```bash
253
- node .claude/skills/sb-validate-render/scripts/validate-render.mjs \
254
+ node .claude/skills/sb-validate-static-render/scripts/validate-static-render.mjs \
254
255
  --file "{output-path}" \
255
256
  --preset "{preset}" \
256
257
  --output-dir "{output_folder}/{project-slug}/reports/validations/{section-slug}-{iteration}" \
@@ -272,6 +273,31 @@ Capture stdout JSON → `render`. Hold `{render-screenshot}` (`render.screenshot
272
273
 
273
274
  ---
274
275
 
276
+ ## Step 4-bis — Test interactivity (Bash, diagnostic gate)
277
+
278
+ ```bash
279
+ node .claude/skills/sb-test-interactivity/scripts/test-interactivity.mjs \
280
+ --file "{output-path}" \
281
+ --preset "{preset}" \
282
+ --output-dir "{output_folder}/{project-slug}/reports/validations/{section-slug}-{iteration}"
283
+ ```
284
+
285
+ Capture `interactivity-report.json` (mesmo dir do `validate-static-render`).
286
+
287
+ **Modo diagnóstico (não-blocker):**
288
+
289
+ - Exit `!= 0` → log stderr verbatim e continue pra Step 5 sem warning. Hint comum: `playwright`/chromium faltando — sugira `npx playwright install chromium`. Não escala.
290
+ - Exit `0` AND `passed === true` → silently proceed. Sem warning, sem mudança de status.
291
+ - Exit `0` AND `passed === false`:
292
+ - **Append** ao `interactivityWarnings` array (NÃO single-shot overwrite): `interactivityWarnings.push({ iteration: <n>, tests: report.tests.filter(t => !t.passed) })`. Iterations com 0 warnings NÃO appende. Histórico cross-iter preservado — útil pro success/escalation path (Steps 8/10) que renderiza section em `report.html` resumindo qual iter viu qual break.
293
+ - Status badge da section build ganha sufixo `+!interactive` (e.g. `✅+!interactive`). Calculado dinamicamente: applies se `interactivityWarnings.length > 0` em qualquer iter.
294
+ - **Status final permanece o que a decision matrix decidir** (Step 7). Sem promoção pra ❌. Skill é diagnóstica, não gatekeeper — política locked em G2 spec change log.
295
+ - Continue pra Step 5.
296
+
297
+ **Por que diagnostic e não blocker:** `sb-test-interactivity` é skill nova (G2). False-positives em early adoption são esperados (web components fora da whitelist canônica, drawers com transições atípicas). Promover a hard gate antes da skill amadurecer geraria fricção em builds bons. Warning explícito + `interactivityWarnings` no report dá visibilidade sem bloquear. Mudança pra blocker é decisão de spec própria (G8+).
298
+
299
+ ---
300
+
275
301
  ## Step 5 — Compare visual (Bash)
276
302
 
277
303
  ```bash
@@ -295,7 +321,7 @@ node .claude/skills/sb-compare-visual/scripts/compare-visual.mjs \
295
321
 
296
322
  Pattern #27 ainda aplica do lado do build — se a section em mobile fica em 390×507 dentro de viewport 390×844, sem o crop sobra 337px de espaço branco que infla diff% gratuitamente.
297
323
 
298
- DPR flags default `3` (matches `sb-inspect-live` iPhone 14 + `sb-validate-render` `deviceScaleFactor=3`, Pattern #22). Não override.
324
+ DPR flags default `3` (matches `sb-inspect-live` iPhone 14 + `sb-validate-static-render` `deviceScaleFactor=3`, Pattern #22). Não override.
299
325
 
300
326
  Capture stdout JSON → `compare`. Note path do `report.json` persistido → `{compare-report-path}`.
301
327
 
@@ -333,6 +359,39 @@ Capture stdout JSON → `review`. Note `review.passed` e o array `violations[]`
333
359
 
334
360
  ## Step 7 — Decision matrix (Pattern #28)
335
361
 
362
+ **Pre-check 0 (coverage gate, runs FIRST):**
363
+
364
+ Para `/clip-section`, "live" é o elemento alvo do selector. Calcule contra schema **real** do `sb-inspect-live`:
365
+
366
+ ```
367
+ buildHeight = render.geometry.totalHeight # primary: from sb-validate-static-render
368
+ liveMainHeight = inspection.sectionBoundingBox.h # primary: bbox do selector target (set when --selector é passed)
369
+ || inspection.dom[0].bbox.h # fallback 1: root walked node bbox (warn — clip pode ser menor que a página)
370
+ || (walk inspection.dom recursively, collect classified-sections bboxes,
371
+ return max(s.bbox.y + s.bbox.h) - min(s.bbox.y))
372
+ # fallback 2: classified sections span (warn)
373
+ coverageRatio = buildHeight / liveMainHeight
374
+ ```
375
+
376
+ `inspection.sectionBoundingBox` é o bbox do selector alvo (schema em `inspect-live.mjs:163,1219`). Quando `--selector` é passed, este field é populated. Sem selector, fica `null` e cai pros fallbacks.
377
+
378
+ If `buildHeight` is missing → HALT com erro `[clip-section] FATAL: render.geometry.totalHeight missing`.
379
+ If `liveMainHeight` fell back → log warning ao user em `report.html` notes.
380
+ If `liveMainHeight < 100` → log warn `[clip-section] WARN: liveMainHeight < 100, coverage gate skipped`; skip; persist `coverage.skippedReason = 'degenerate-height'`.
381
+ If `inspection.dom[]` empty AND no classified sections → log warn `[clip-section] WARN: coverage gate skipped — empty inspection.dom`; skip; `coverage = null`.
382
+
383
+ **Tier triage:**
384
+
385
+ | Range | Status | Ação |
386
+ | --- | --- | --- |
387
+ | `< 0.40` | ❌ **incomplete** | Status: `"incomplete — built {ratio*100:.0f}% of source clip"`. Vai pra Step 9 (auto-correct loop) se budget permite; existing review-derived fixHints feed back to composer. **NO new EXTENSION marker** — Step 9 já consome `review.violations[]` canonical. Coverage `< 0.40` apenas força ❌ no matrix override. |
388
+ | `0.40 — 0.85` | ⚠️ **partial** | Status: `"partial — built {ratio*100:.0f}%"`. Prossegue pra matriz abaixo (não bypassa). Coverage warning sobrevive como overlay. |
389
+ | `>= 0.85` | (proceed) | Coverage OK. Matriz roda silenciosamente. |
390
+
391
+ Persist `coverage = { buildHeight, liveMainHeight, ratio, source: 'sectionBoundingBox'|'rootBbox'|'sectionsSpan', missingSections?: [...], skippedReason?: <string> }` em report. `missingSections` (quando `ratio < 0.85`): walk `inspection.dom` recursivamente, collect classified-section children do selector cujo `bbox.y >= buildHeight`.
392
+
393
+ **Existing matrix (roda só se coverage gate não routou pra ❌):**
394
+
336
395
  Você tem `compare.passed` E `review.passed` agora. Aplique:
337
396
 
338
397
  | `review.passed` | `compare.passed` | Ação |
@@ -513,7 +572,7 @@ Nunca escreva fora de `{project-slug}/`. Cross-project asset sharing acontece s
513
572
  Quando este command for primeiro wired-up, smoke test canônico:
514
573
 
515
574
  ```
516
- /clip-section https://alphainfusemen.com .ai-hero --target shopify
575
+ /clip-section https://example-store.com .ai-hero --target shopify
517
576
  ```
518
577
 
519
- Esperado: arquivo `{output_folder}/alphainfusemen/clean/sections/ai-hero.liquid` em até ~30s, diff sob threshold, decision matrix funcionando, report.html abrindo. Sem crawl, sem checkpoint humano. MVP milestone do `/clip-section`.
578
+ Esperado: arquivo `{output_folder}/example-store/clean/sections/ai-hero.liquid` em até ~30s, diff sob threshold, decision matrix funcionando, report.html abrindo. Sem crawl, sem checkpoint humano. MVP milestone do `/clip-section`.