similarbuild 0.3.4 → 0.4.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 (26) hide show
  1. package/package.json +1 -1
  2. package/templates/commands/build-page.md +62 -493
  3. package/templates/commands/build-site.md +98 -705
  4. package/templates/commands/clip-section.md +102 -501
  5. package/templates/skills/sb-build-wp/SKILL.md +37 -164
  6. package/templates/skills/sb-build-wp/references/wp-build-rules.md +11 -1
  7. package/templates/skills/sb-inspect-live/scripts/inspect-live.mjs +4 -367
  8. package/templates/skills/sb-review-checks/SKILL.md +0 -113
  9. package/templates/skills/sb-review-checks/references/review-rules.md +0 -195
  10. package/templates/skills/sb-review-checks/scripts/lib/anti-patterns.mjs +0 -385
  11. package/templates/skills/sb-review-checks/scripts/lib/cross-reference.mjs +0 -115
  12. package/templates/skills/sb-review-checks/scripts/lib/design-quality.mjs +0 -541
  13. package/templates/skills/sb-review-checks/scripts/review-checks.mjs +0 -250
  14. package/templates/skills/sb-review-checks/scripts/tests/test-anti-patterns.mjs +0 -366
  15. package/templates/skills/sb-review-checks/scripts/tests/test-cross-reference.mjs +0 -170
  16. package/templates/skills/sb-review-checks/scripts/tests/test-design-quality.mjs +0 -493
  17. package/templates/skills/sb-review-checks/scripts/tests/test-review-checks.mjs +0 -267
  18. package/templates/skills/sb-test-interactivity/SKILL.md +0 -133
  19. package/templates/skills/sb-test-interactivity/scripts/test-interactivity.mjs +0 -970
  20. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/aria-controls-broken.html +0 -32
  21. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/aria-controls-good.html +0 -47
  22. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/deferred-listeners.html +0 -67
  23. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/details-good.html +0 -25
  24. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/dialog-good.html +0 -38
  25. package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/no-interactive.html +0 -15
  26. package/templates/skills/sb-test-interactivity/scripts/tests/test-test-interactivity.mjs +0 -223
@@ -1,772 +1,165 @@
1
1
  ---
2
- description: SimilarBuild orchestrator descobre as páginas de um site live, confirma a lista com o usuário (único checkpoint humano da framework) e clona todas em batch pro destino (WP/Shopify), validadas e auto-corrigidas.
3
- argument-hint: <root-URL> [--target wp|shopify] [--project-name <slug>] [--no-auto-correct] [--max-pages <n>] [--sitemap-path <path>] [--dry-run]
2
+ description: Clones a live site by crawling pages, confirming the page list with the user, cloning header + footer first (as global sections, paste-once in WP/Shopify), then cloning each page's body sections via /build-page.
3
+ argument-hint: <root-URL> [--target wp|shopify] [--project-name <slug>] [--max-pages <n>] [--sitemap-path <path>] [--dry-run] [--max-iterations <n>]
4
4
  ---
5
5
 
6
- # /build-site — SimilarBuild batch site-wide orchestrator
6
+ # /build-site — site = globals + list of pages
7
7
 
8
- You are the SimilarBuild orchestrator for a full live site. Persona: senior visual-migration engineer — direct, opinionado, fluente em português técnico, alérgico a improviso, devoto de defensive specificity. Você acredita em **"validate before showing the user"** e **"only one human checkpoint, ever"** — e nunca quebra nenhuma das duas regras.
8
+ You are the SimilarBuild orchestrator for a full site. The mental model:
9
9
 
10
- ## Mission
10
+ 1. The site has **2 shared chrome sections** (header, footer) that go in WP/Shopify's site-wide template, pasted ONCE.
11
+ 2. Each page has a **list of body sections** (no header, no footer — those come from global). Each body section is pasted into the page's Custom HTML widget in order.
11
12
 
12
- Receber UMA URL raiz e um target (WP ou Shopify), descobrir todas as páginas relevantes do site via `sb-crawl-and-list`, **confirmar a lista com o usuário** (este é o único checkpoint humano de toda a framework SimilarBuild), e então buildar cada página confirmada — entregando uma estrutura completa por destino, com `report.html` agregado, validação visual por página e auto-correção em até N=2 tentativas por página.
13
+ This command discovers pages, confirms the list with the user (single checkpoint), clones header+footer once as globals, then clones each page's body sections via `/build-page`.
13
14
 
14
- ## The five non-negotiables
15
-
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
- 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-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
- 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
- 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
-
22
- ---
23
-
24
- ## Architectural decision: how to invoke sub-skills
25
-
26
- **Decision: hybrid — `Bash` para skills determinísticas, `Skill` tool para o composer.** (Mesmo do `/build-page`.)
27
-
28
- | Sub-skill | Invocation | Why |
29
- | --- | --- | --- |
30
- | `sb-crawl-and-list` | **Bash** → `node .claude/skills/sb-crawl-and-list/scripts/crawl-and-list.mjs ...` | Descoberta determinística (sitemap → cheerio fallback). Stdout é JSON puro, stderr é log com prefixo `[sb-crawl-and-list]`. |
31
- | `sb-inspect-live` | **Bash** | 95%+ deterministic. Stable CLI. Roda 1× por página. |
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
- | `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-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. |
36
- | `sb-compare-visual` | **Bash** | pixelmatch + token diff. Pure determinismo. |
37
- | `sb-review-checks` | **Bash** | cheerio + regex. Pure determinismo. `candidateFix` strings são contrato — forward verbatim. |
38
-
39
- **Decision sobre reuso da lógica de `/build-page`:** orchestration loop interno reusando os mesmos Bash/Skill calls. **Não invoque `/build-page` via Task tool e não delegue pra subagente** — o orchestrator fica na conversa principal, com playbook próprio (este arquivo), reusando as MESMAS invocações de sub-skill que `/build-page` usa. Razão: (a) as invocações são triviais (`node ... | jq` ou `Skill(...)`), (b) delegar via Task duplicaria contexto e cobraria 2× tokens por página, (c) o batch precisa enxergar os outputs por página para preencher o `report.html` agregado e detectar seções repetidas (header/footer) — isso requer estado no orchestrator, não em subagentes isolados.
40
-
41
- **Decision sobre paralelismo: sequencial pro MVP.** O config `max_parallel_pages` (default 6) está reservado mas **ignorado nesta versão**. Razão: (a) output do orchestrator legível em ordem (uma página por vez), (b) debug previsível quando uma página falha, (c) Playwright concorrente exige profile isolado por worker e gestão de processos que adiciona complexidade incidental sem ganho de UX no early adoption. Roadmap V2: `/build-site parallelism` — adicionar Bash backgrounding com `& wait`, semáforo limitando a `max_parallel_pages`, captura de stdout/stderr por worker, e merge ordenado no report final.
42
-
43
- ---
44
-
45
- ## Tool dependencies
46
-
47
- - `Bash` — orquestrar, parsear JSON via `jq` ou shell, rodar os `.mjs`, mkdir, ler config
48
- - `Read` — carregar `.claude/sb-config.yaml`, memory files, fragmentos buildados, sitemap raw quando útil
49
- - `Write` — escrever `report.html`, `decisions.md`, `pages-confirmed.json`, anti-patterns auto-aprendidos
50
- - `Edit` — atualizações cirúrgicas em `report.html` entre páginas e em fragmentos durante auto-correct
51
- - `Skill` — invocar `sb-build-wp` / `sb-build-shopify`
52
- - `WebFetch` — opcional, sanity-check do root URL antes de lançar o crawler
53
-
54
- NÃO use o `Agent` tool pra delegar o workflow — o orchestrator fica na main conversation. Não invoque `/build-page` via Task — orchestre direto.
55
-
56
- ---
57
-
58
- ## Inputs (parse from `ARGUMENTS:`)
59
-
60
- O texto após `ARGUMENTS:` contém a entrada do usuário. Extraia:
15
+ ## Inputs
61
16
 
62
17
  | Flag | Required | Default | Behavior |
63
- | --- | --- | --- | --- |
64
- | `<root-URL>` (positional, primeiro) | yes | — | URL absoluta raiz do site. Se ausente, pare e pergunte. |
65
- | `--target wp\|shopify` | no | `default_target` do `.claude/sb-config.yaml`, fallback `wp` | Roteia pro `sb-build-wp` (`wp`) ou `sb-build-shopify` (`shopify`). Se `shopify` foi pedido e a skill não existe (cheque `.claude/skills/sb-build-shopify/SKILL.md`), pare e diga. |
66
- | `--project-name <slug>` | no | auto-derivado do hostname (Step 0.3) | Override do nome do projeto. Útil pra branch experiments. |
67
- | `--no-auto-correct` | no | false | Em cada página, escala no primeiro decision-matrix que pediria loop. |
68
- | `--max-pages <n>` | no | (deixa o default da skill, `200`) | Repassa pra `sb-crawl-and-list` como hard cap. |
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 do Step 3.5 (globals-first phase, V03-0a). Sem essa flag, header/footer compartilhados são extraídos pra `clean/global/` ANTES do loop de páginas, com gates duros de interactivity+visual. Default ON quando `crawl.pageCount >= 3` AND existe ≥1 página `type === 'home'`. Use quando o site não tem chrome compartilhado, ou em debug que precisa ver markup completo por página. |
71
- | `--dry-run` | no | false | Roda crawl + checkpoint + plan-summary, **não builda**. Imprime o plano (URLs, types, assets estimados) e sai. |
72
-
73
- Flag desconhecida → ignore com warning de uma linha. Não erre.
74
-
75
- ---
76
-
77
- ## Step 0 — Config + memory (idêntico ao `/build-page`)
78
-
79
- 1. **Load config.** Read `.claude/sb-config.yaml` se existir. Defaults quando ausente ou key faltando:
80
-
81
- ```yaml
82
- output_folder: ./sb-output
83
- default_target: wp
84
- default_viewport: 390
85
- auto_correct_max_iterations: 2
86
- diff_threshold_percent: 10
87
- strip_metadata: true
88
- max_parallel_pages: 6 # reservado, não usado no MVP — ver "paralelismo V2"
89
- crawl_blocklist: [] # extras pro sb-crawl-and-list, somam aos defaults da skill
90
- ```
91
-
92
- Não auto-crie o arquivo — o installer (item #16 do roadmap) é dono disso.
93
-
94
- 2. **Memory cascade** (pattern #21). Mesma lógica do `/build-page`: plugin → per-user → bundled fallback. Carregue em working memory pra (a) filtrar subset relevante por section-type quando for invocar `sb-build-{wp,shopify}` em cada página, e (b) reconhecer pattern novo no auto-learn final do batch.
95
-
96
- 3. **Project slug.** Mesma derivação do `/build-page` (hostname → strip `www.`/`m.`/`store.`/`shop.` → strip TLD → kebab-case). Se `--project-name <slug>` foi passado, usa verbatim (valida URL-safe; warn se não). Hold como `{project-slug}`.
97
-
98
- 4. **Project structure.** `mkdir -p` idempotente:
99
-
100
- ```
101
- {output_folder}/{project-slug}/
102
- ├── clean/
103
- │ ├── home/ ← hero/intro/cta/etc da home
104
- │ ├── pdp/ ← um por produto
105
- │ ├── collections/ ← um por collection
106
- │ ├── pages/ ← contact/about/policy/blog/other
107
- │ ├── sections/ ← seções genéricas
108
- │ └── global/ ← header/footer compartilhados (Step 3.5, V03-0a)
109
- ├── assets/ ← content-hash images, COMPARTILHADO entre todas as páginas
110
- ├── reports/
111
- │ ├── diffs/
112
- │ ├── validations/
113
- │ └── crawl/ ← pages-list.json, sitemap-raw.xml
114
- └── .sb-memory/
115
- ├── decisions.md
116
- ├── pages-confirmed.json ← lista FINAL pós-checkpoint humano
117
- └── inspect-{ts}/ ← um por página (nested por slug)
118
- ```
119
-
120
- Nunca escreva fora de `{project-slug}/`.
121
-
122
- ---
18
+ |---|---|---|---|
19
+ | `<root-URL>` (positional) | yes | — | Site root. |
20
+ | `--target wp\|shopify` | no | `wp` from config | Routes composer choice. |
21
+ | `--project-name <slug>` | no | hostname-derived | Output folder. |
22
+ | `--max-pages <n>` | no | 200 | Crawl cap. |
23
+ | `--sitemap-path <path>` | no | (none) | Custom sitemap path for crawl-and-list. |
24
+ | `--dry-run` | no | false | Crawl + confirm + plan only, no clone. |
25
+ | `--max-iterations <n>` | no | 2 | Per-section loop cap (passed through to `/clip-section`). |
26
+
27
+ ## Flow
28
+
29
+ ### Step 0 — Config + project slug
30
+
31
+ Load `.claude/sb-config.yaml`. Derive `{project-slug}`. Create the output structure:
32
+
33
+ ```
34
+ {output_folder}/{project-slug}/
35
+ ├── sections/
36
+ ├── global/ — header, footer (paste once in WP template)
37
+ ├── home/ — home body sections
38
+ ├── pdp/{slug}/ — PDP body sections
39
+ ├── pages/{slug}/
40
+ └── ...
41
+ ├── assets/ — content-hashed, shared across all sections
42
+ ├── final/ — concat helpers per page
43
+ └── .sb-memory/
44
+ ├── pages-confirmed.json
45
+ └── decisions.md
46
+ ```
123
47
 
124
- ## Step 1 — Discover pages (Bash → `sb-crawl-and-list`)
48
+ ### Step 1 — Discovery (Bash)
125
49
 
126
50
  ```bash
127
51
  node .claude/skills/sb-crawl-and-list/scripts/crawl-and-list.mjs \
128
52
  --root-url "{root-URL}" \
129
- --output-dir "{output_folder}/{project-slug}/reports/crawl" \
130
- [--max-pages {max-pages-from-args}] \
131
- [--sitemap-path "{sitemap-path-from-args}"] \
132
- [--blocklist "{crawl_blocklist joined by comma}"]
133
- ```
134
-
135
- Use `crawl_blocklist` do config se ele existir (junte com vírgulas). Não passe se vazio. Se o usuário passou `--max-pages` ou `--sitemap-path`, repasse verbatim.
136
-
137
- Capture stdout JSON → `crawl`. Hold:
138
-
139
- - `crawl.pages[]` (a lista ordenada — Pattern #33: home → depth → alfabético)
140
- - `crawl.warnings[]` (Pattern #34: formato `categoria-id:detalhe`)
141
- - `crawl.pageCount`, `crawl.blocked`, `crawl.duplicates`, `crawl.source`
142
-
143
- **Branch:**
144
- - Exit non-zero → pass stderr verbatim e pare. Se mencionar `cheerio`, sugira `npm i cheerio`.
145
- - `crawl.pageCount === 0` → mostre os warnings e pare. Sugira `--sitemap-path` se houver `spa-suspected`.
146
- - Senão continue.
147
-
148
- ---
149
-
150
- ## Step 2 — Parse warnings & escalate when required (Pattern #34)
151
-
152
- Antes de mostrar a tabela, processe cada `crawl.warnings[]`. O formato é `categoria-id:detalhe-opcional`. Faça parse string-match (não NLP):
153
-
154
- | Warning prefix | Severidade | Ação |
155
- | --- | --- | --- |
156
- | `robots-disallow-root` | **bloqueante** | **Não builda.** Imprima: "`robots.txt` desse site bloqueia o crawler na raiz. Re-rode com `--respect-robots-txt false` por sua conta e risco (e com permissão do dono do site), ou abra um sitemap manual com `--sitemap-path`." Pare. |
157
- | `spa-suspected` | alerta pré-confirmação | Antes da tabela, exiba: "⚠️ Site parece ser SPA — a lista descoberta pode estar incompleta. Considere fornecer `--sitemap-path` manual com a estrutura real do site, ou prossiga sabendo que páginas geradas via JS podem ter sido perdidas." |
158
- | `max-pages-cap:N-of-M` | informativo | Anote `(capped at N of M total)` no header da tabela. Ofereça: "Pra incluir mais, re-rode com `--max-pages M` (ou maior)." |
159
- | `sitemap-truncated:N` | informativo | Anote `(sitemap teve >1000 entries — truncado em N)` no header. |
160
- | `sitemap-malformed` | informativo | Anote: "sitemap.xml mal-formado — fallback pro crawl HTML." |
161
- | qualquer outro | informativo | Repasse no header da tabela como linha-livre. |
162
-
163
- `robots-disallow-root` é o único que termina o run. Os outros decoram a tabela; o usuário decide se quer prosseguir.
164
-
165
- ---
166
-
167
- ## Step 3 — Human checkpoint (THE non-negotiable)
168
-
169
- Esta é a única vez no batch inteiro em que você espera input do usuário. Faça com cuidado.
170
-
171
- ### Step 3a — Apresente a tabela
172
-
173
- Imprima em português, formato markdown:
174
-
175
- ```
176
- 📋 {pageCount} páginas descobertas em {root-URL} (source: {source}{notas-do-step-2})
177
-
178
- | # | Type | URL | Depth |
179
- | --- | ----------- | ------------------------------------ | ----- |
180
- | 1 | home | https://example.com/ | 0 |
181
- | 2 | pdp | https://example.com/products/foo | 1 |
182
- | 3 | collection | https://example.com/collections/bar | 1 |
183
- | ... | ... | ... | ... |
184
-
185
- Filtros disponíveis (responda com UMA linha):
186
- build all → builda todas
187
- skip <regex|paths> → remove páginas que casarem (ex: skip /policies/, /blog/)
188
- only <regex|paths> → keep só as que casarem (ex: only /products/)
189
- exclude <type1,type2> → remove por type (ex: exclude policy,blog)
190
- include <type1,type2> → keep só esses types (ex: include home,pdp)
191
- cancel | abort → para sem buildar
192
-
193
- Diagnostics: {blocked} bloqueadas pela blocklist, {duplicates} duplicatas removidas.
194
- ```
195
-
196
- Liste TODAS as páginas (não trunque). Se houver `>50`, agrupe por type colapsando em "(N pdp pages — ver lista completa em reports/crawl/pages-list.json)" para os types com >15 entries — mas mantenha o índice global numérico contínuo.
197
-
198
- ### Step 3b — Capture e parse a resposta
199
-
200
- Aguarde UMA linha. Trim. Lowercase para comparação de comando (mas mantenha case original para regex/paths). Branche:
201
-
202
- - `build all` ou string vazia → vai pra Step 3c com a lista inteira.
203
- - `cancel` ou `abort` → "Cancelado pelo usuário. Crawl persistido em `{output_folder}/{project-slug}/reports/crawl/pages-list.json`. Pare." sem buildar.
204
- - `skip <args>` → divida `<args>` por vírgula. Cada token é um path-prefix OU regex (detecte regex se começa com `/` E termina com `/[gimsuy]*`). Remova cada `page` cujo `page.url` ou `page.path` casar com qualquer token.
205
- - `only <args>` → mesma parser, mas KEEP só os matches.
206
- - `exclude <args>` → divida por vírgula. Cada token é um type literal (`home`, `pdp`, `collection`, `contact`, `about`, `policy`, `blog`, `other`). Remova páginas cujo `page.type` esteja na lista.
207
- - `include <args>` → mesma parser, KEEP só os matches.
208
- - Resposta não-reconhecida → re-prompt: "Não entendi. Use `build all`, `skip ...`, `only ...`, `exclude ...`, `include ...` ou `cancel`." (Re-aguarde input.)
209
-
210
- Se o filtro deixar a lista vazia → diga "Filtro removeu todas as páginas. Volta pro filtro." e re-prompt da Step 3a.
211
-
212
- ### Step 3c — Confirmação dupla
213
-
214
- Mostre a lista FINAL pós-filtro (mesma tabela formatada, mas com a contagem nova). Imprima:
215
-
216
- ```
217
- ✏️ {N} páginas confirmadas pra build. Confirma?
218
- confirm → builda agora
219
- edit → volta pra etapa de filtros (mostra a tabela ORIGINAL)
220
- cancel → para sem buildar
221
- ```
222
-
223
- - `confirm` → persista a lista em `{output_folder}/{project-slug}/.sb-memory/pages-confirmed.json` e siga pra Step 4.
224
- - `edit` → volta pra Step 3a (apresenta a tabela original do crawl, **não** a filtrada).
225
- - `cancel` → "Cancelado. Lista descoberta em `reports/crawl/pages-list.json`." Pare.
226
- - Outra coisa → re-prompt.
227
-
228
- ### Step 3d — Dry-run check
229
-
230
- Se `--dry-run` foi passado, AGORA é o ponto de saída: depois de `confirm`, escreva `reports/plan.{ts}.md` com a lista final + estimativa por type, imprima o sumário, e pare. Não rode Step 3.5 nem Step 4.
231
-
232
- ---
233
-
234
- ## Step 3.5 — Globals-first phase (V03-0, hydrated-snapshot powered)
235
-
236
- **Default ON** quando `crawl.pageCount >= 3` AND a flag `--no-globals` NÃO foi passada AND existe ao menos uma página com `type === 'home'` em `pagesConfirmed[]`. Caso contrário, skip o Step 3.5 inteiro com uma linha em `decisions.md` (`Step 3.5 skipped: <reason>`) e segue pro Step 4 com `globalsExtracted = { header: null, footer: null, status: 'skipped' }`.
237
-
238
- Header/footer são **shared chrome**: usados em TODAS as páginas. Bug em qualquer um afeta o site inteiro. Esta fase é dedicada e roda ANTES do loop de páginas pra evitar gastar tempo nas páginas individuais quando os globais estão quebrados.
239
-
240
- **Diferença vs tentativa anterior (revertida em v0.2.2)**: agora o `sb-inspect-live` emite `inspection.hydratedHeader` / `inspection.hydratedFooter` — payloads estruturados (links, headings, inputs, forms, images) capturados WHILE THE CHROME IS IN VIEW e ainda hidratado. Isso elimina o problema de lazy-light-DOM em Shopify (onde menus `<store-footer-menu>` populam `<li>` via JS pós-intersection-out). Composer compõe markup REAL — não image-slice estática.
241
-
242
- Estado do orchestrator:
243
-
244
- ```
245
- globalsExtracted = {
246
- header: null | <output-path>, // ex: "clean/global/header.html"
247
- footer: null | <output-path>,
248
- status: 'extracted' | 'skipped' | 'failed',
249
- failureReason: null | <string>,
250
- headerBbox: null | {x,y,w,h},
251
- footerBbox: null | {x,y,w,h},
252
- homeInspectionPath: null | <path>,
253
- }
254
- ```
255
-
256
- ### Step 3.5a — Inspect home (Bash, dedicado)
257
-
258
- ```bash
259
- node .claude/skills/sb-inspect-live/scripts/inspect-live.mjs \
260
- --url "{home-page.url}" \
261
- --viewport-width {default_viewport} \
262
- --viewport-height 844 \
263
- --output-dir "{output_folder}/{project-slug}/.sb-memory/inspect-globals-{ts}"
264
- ```
265
-
266
- Onde `{home-page}` é a primeira entrada de `pagesConfirmed[]` com `type === 'home'`. Set `globalsExtracted.homeInspectionPath` = path resultante. Se inspect falha (`widgetBlocked`, exit non-zero), `globalsExtracted.status = 'failed'`, `failureReason = 'home-inspect-failed: <stderr>'`, segue pro Step 3.5g.
267
-
268
- ### Step 3.5b — Detect hydrated chrome
269
-
270
- Carregue `inspection.json` da Step 3.5a:
271
-
272
- - **Header**: se `inspection.hydratedHeader !== null` AND `inspection.hydratedHeader.links.length >= 2`, set `globalsExtracted.headerBbox = inspection.hydratedHeader.bbox`. Caso contrário, set `globalsExtracted.header = null` (skip header compose).
273
- - **Footer**: se `inspection.hydratedFooter !== null` AND (`inspection.hydratedFooter.links.length >= 3` OR `inspection.hydratedFooter.forms.length >= 1`), set `globalsExtracted.footerBbox = inspection.hydratedFooter.bbox`. Caso contrário, set `globalsExtracted.footer = null`.
274
-
275
- Se AMBOS skipped, `globalsExtracted.status = 'skipped'`, `failureReason = 'no-hydrated-chrome-detected'`. Log `[build-site] Step 3.5: no hydrated header/footer in home inspection — pages keep chrome inline`. Pula direto pro Step 4 (cenário válido — não é falha).
276
-
277
- ### Step 3.5c — Compose globals (Skill por target)
278
-
279
- Para cada um dos detectados (`header` e/ou `footer`), invoque o composer:
280
-
281
- ```
282
- Skill(
283
- skill="sb-build-wp",
284
- args="inspection={globals-inspection-path} assets-map={assets-map-path} output-path=clean/global/{header|footer}.html preset=wp-elementor target-section={header|footer}"
285
- )
286
- ```
287
-
288
- O composer consome `inspection.hydratedHeader` / `inspection.hydratedFooter` (per SKILL.md §V03-0a) — markup real com links, forms, imgs.
289
-
290
- (`sb-build-shopify` quando `target === shopify`, mesmo padrão.)
291
-
292
- Set `globalsExtracted.header` e/ou `.footer` = output paths. Se composer falha, `globalsExtracted.status = 'failed'`, `failureReason = 'compose-failed: <which> <stderr>'`, segue pro Step 3.5g.
293
-
294
- ### Step 3.5d — Test interactivity (Bash, HARD blocker)
295
-
296
- Diferente do Step 4f-bis (diagnóstico em páginas individuais), aqui o gate é DURO. Para cada um:
297
-
298
- ```bash
299
- node .claude/skills/sb-test-interactivity/scripts/test-interactivity.mjs \
300
- --file "clean/global/{header|footer}.html" \
301
- --preset "{preset}" \
302
- --output-dir "{output_folder}/{project-slug}/reports/validations/global-{ts}"
303
- ```
304
-
305
- Se `passed === false`, `globalsExtracted.status = 'failed'`, `failureReason = 'interactivity-failed: <which>'`, segue pro Step 3.5g.
306
-
307
- ### Step 3.5e — Compare visual focado em crops
308
-
309
- Pré-condição: cada fragment já foi composto. Renderize cada um e compare contra o crop correspondente da live screenshot.
310
-
311
- **3.5e.i — Render do fragment**:
312
-
313
- ```bash
314
- node .claude/skills/sb-validate-static-render/scripts/validate-static-render.mjs \
315
- --file "clean/global/{header|footer}.{html|liquid}" \
316
- --preset "{preset}" \
317
- --output-dir "{output_folder}/{project-slug}/reports/validations/global-{ts}/{header|footer}/"
318
- ```
319
-
320
- **3.5e.ii — Bbox no live**: use `globalsExtracted.headerBbox` (ou `footerBbox`). Se `w === 0 || h === 0`, log warning e omita `--crop-live-bbox` (compare-visual cai pra comparação full-screen).
321
-
322
- **3.5e.iii — Compare**:
323
-
324
- ```bash
325
- node .claude/skills/sb-compare-visual/scripts/compare-visual.mjs \
326
- --live-screenshot "{globals-inspection-screenshot}" \
327
- --build-screenshot "{globals-render-screenshot}" \
328
- --output-dir "{output_folder}/{project-slug}/reports/diffs/global-{ts}/{header|footer}/" \
329
- --tokens-live "{globals-inspection-path}" \
330
- --tokens-build "{globals-render-json-path}" \
331
- --threshold {diff_threshold_percent} \
332
- [--crop-live-bbox "{x},{y},{w},{h}"] \
333
- [--crop-build-bbox "0,0,{width},{totalHeight}"]
53
+ --output-dir "{output_folder}/{project-slug}/.sb-memory" \
54
+ [--max-pages {max-pages}] \
55
+ [--sitemap-path "{sitemap-path}"]
334
56
  ```
335
57
 
336
- Se `passed === false`, `globalsExtracted.status = 'failed'`, `failureReason = 'visual-diff: <which> <diffPercent>%'`, segue pro Step 3.5g.
337
-
338
- ### Step 3.5f — Sucesso
339
-
340
- `globalsExtracted.status = 'extracted'`. Log `[build-site] Step 3.5 ✅ globals extracted: header={path}, footer={path}`. Prossegue pro Step 4.
341
-
342
- ### Step 3.5g — Gate duro (early-exit em falha)
343
-
344
- Se `globalsExtracted.status === 'failed'`:
345
-
346
- 1. Escreva `reports/index.html` mínimo com o motivo + links pros artefatos diagnósticos.
347
- 2. Append em `decisions.md`: `{ts} | /build-site {root-URL} | Step 3.5 BLOCKED: {failureReason} | pages NOT processed`.
348
- 3. Console:
349
-
350
- ```
351
- ❌ /build-site {root-URL}
58
+ Produces `pages-list.json`.
352
59
 
353
- Step 3.5 (globals pass) bloqueou o batch.
354
- Motivo: {failureReason}
355
- Artefatos: {output_folder}/{project-slug}/reports/index.html
60
+ ### Step 2 Confirm page list (single human checkpoint)
356
61
 
357
- Páginas individuais NÃO foram processadas fix os globals e re-rode.
358
- ```
359
- 4. Pare.
62
+ Present a markdown table grouped by type (home, pdp, collection, pages, blog, other). User replies:
360
63
 
361
- ---
362
-
363
- ## Step 4 Per-page build loop (sequencial, MVP)
364
-
365
- Para cada `page` em `pagesConfirmed[]`, na ordem em que veio (Pattern #33), execute o pipeline abaixo. Mantenha um array `pageResults[]` com `{url, type, slug, status: '✅'|'⚠️'|'❌', diffPercent, iterations, outputPath, screenshots, violations}` para o report agregado.
366
-
367
- > **⚠️ Importante (não-paralelo no MVP).** Não use `&` / background. Não use Task tool pra delegar uma página. Uma página por vez, output linear no console, no formato:
368
- >
369
- > ```
370
- > [3/12] pdp https://example.com/products/foo → clean/pdp/foo.html
371
- > inspect ✓ (1.2s) | extract ✓ (3 assets, 1 cached) | build ✓ | validate ✓ | compare 8.4% ✅
372
- > ```
64
+ - `confirm` → proceed with the list as-is.
65
+ - `edit` → user edits the list inline (drops items, adds items).
66
+ - `only home,pdp` keep only those types.
67
+ - `cancel` → exit.
373
68
 
374
- ### Step 4a Determine output path
69
+ Persist final list to `.sb-memory/pages-confirmed.json`.
375
70
 
376
- Mapeie `page.type` + URL → `{output-path}` dentro de `clean/`:
71
+ ### Step 3 Dry-run check
377
72
 
378
- | `page.type` | Subpath | Slug derivation |
379
- | --- | --- | --- |
380
- | `home` | `home/` | sectionType-driven (hero/intro/cta/etc) — **mas no `/build-site`, a home builda como página inteira primeiro**: arquivo único `clean/home/index.html`. Se a inspeção identificar múltiplas seções, o builder é responsável por compô-las. |
381
- | `pdp` | `pdp/` | last path segment do URL (kebab-case, max 60 chars) |
382
- | `collection` | `collections/` | last path segment |
383
- | `contact` | `pages/` | `contact` |
384
- | `about` | `pages/` | `about` |
385
- | `policy` | `pages/` | last path segment (ex: `privacy-policy`, `refund`) |
386
- | `blog` | `pages/` | `blog-{last-segment}` |
387
- | `other` | `pages/` | last path segment, fallback `page-{n}` se vazio |
73
+ If `--dry-run` was passed, emit a plan summary (page count by type, asset estimate) and exit. Don't clone.
388
74
 
389
- Para Shopify, troque `.html` `.liquid` na extensão final.
75
+ ### Step 4 Clone globals (header + footer)
390
76
 
391
- Se duas páginas colidirem no mesmo `{output-path}` (ex: dois PDPs com mesmo último segmento por causa de subpaths), sufixar `-2`, `-3`, ... ao slug. Logue.
77
+ Both header and footer of a representative page (typically the home) are SHARED across the site. Clone them as global sections first so they exist before any page body is cloned.
392
78
 
393
- ### Step 4b — Inspect live (Bash)
79
+ **Step 4a — Inspect home once** (used as reference for both globals):
394
80
 
395
81
  ```bash
396
82
  node .claude/skills/sb-inspect-live/scripts/inspect-live.mjs \
397
- --url "{page.url}" \
83
+ --url "{home-URL}" \
398
84
  --viewport-width {default_viewport} \
399
85
  --viewport-height 844 \
400
- --output-dir "{output_folder}/{project-slug}/.sb-memory/inspect-{slug}-{ts}"
401
- ```
402
-
403
- **Re-uso da inspection da home (V03-0a):** se `page.type === 'home'` AND `globalsExtracted.homeInspectionPath !== null`, SKIP esta inspeção e re-use `globalsExtracted.homeInspectionPath` como o `{inspection-path}` deste step. Step 3.5a já inspecionou a home — re-rodar custaria ~5-8s e produziria conteúdo idêntico. Logue `[build-site] Step 4b: re-using home inspection from Step 3.5 ({path})`.
404
-
405
- **Section crops organizados (V03-3):** após cada inspeção, copie/link os crops de `{inspection-path}/sections/*.png` para `{output_folder}/{project-slug}/screenshots/{page.slug}/` (com prefixo de página tipo `home/`, `pdp/originals/`, `pages/privacy-policy/` etc, derivado de `page.type + page.slug`). Isso dá ao usuário uma estrutura inspecionável `screenshots/<page>/<idx>-<sectionType>.png` que ele pode abrir num browser/folder pra ver O QUE o composer recebeu como input. Use `ln -sf` (symlink) ou `cp` — sem mover, porque a inspeção também precisa da pasta original pra cross-validation.
406
-
407
- Capture `inspection`. Branches específicas do batch:
408
-
409
- - `inspection.widgetBlocked === true` → marque a página como `❌` em `pageResults[]`, anote o motivo, e **continue para a próxima página** (não pare o batch). Exceção: se essa for a PRIMEIRA página E for a `home`, pare e escale — provavelmente o site inteiro está atrás de bot-wall.
410
- - `inspection.dom` empty mas `widgetBlocked: false` → mesmo tratamento (`❌`, próxima).
411
- - Outros erros → `❌`, próxima.
412
-
413
- ### Step 4c — Extract assets (Bash, com cache compartilhado)
414
-
415
- ```bash
416
- node .claude/skills/sb-extract-assets/scripts/extract-assets.mjs \
417
- --inspection-path "{inspection-path}" \
418
- --output-dir "{output_folder}/{project-slug}/assets" \
419
- --target "{target}" \
420
- --existing-assets-dir "{output_folder}/{project-slug}/assets"
86
+ --output-dir "{output_folder}/{project-slug}/.sb-memory/inspect-home-for-globals"
421
87
  ```
422
88
 
423
- **Crítico no batch:** `--existing-assets-dir` aponta pro MESMO `assets/` do projeto, **em todas as páginas**. O dedupe por content-hash naturalmente faz com que o logo da home (por exemplo) seja baixado 1× e reusado em N páginas — você verá entradas `cached: true` em `assetsMap.assets[]` da segunda página em diante. Não tente "otimizar" nada além disso — o content-hash já é a primitiva.
424
-
425
- `assetsMap.failed[]` non-empty → forwarde pro builder, **não** pare a página.
426
-
427
- ### Step 4d — Strip globals from per-page inspection (V03-0a, post-Step 3.5)
89
+ **Step 4b Identify header + footer selectors** from the home inspection:
428
90
 
429
- A detecção e composição de header/footer **agora roda no Step 3.5** (Globals-first phase, antes do loop). Aqui o trabalho é apenas garantir que páginas individuais NÃO incluam header/footer no markup gerado — o destino renderiza os globais UMA vez via `clean/global/{header,footer}.{html|liquid}`.
91
+ - Header: first `<header>` element OR first `<aside class*="announcement-bar">` + adjacent `<header>` (when both present, capture as ONE section so the announcement bar is part of the global header).
92
+ - Footer: last `<footer>` direct child of `<body>` or `<main>`.
430
93
 
431
- **Skip este Step** se `globalsExtracted.status !== 'extracted'` (Step 3.5 foi `skipped` ou retornou sem chrome detectado). Páginas mantêm chrome inline — comportamento pré-v0.3.0 para sites sem chrome global.
94
+ Generate stable CSS selectors for both.
432
95
 
433
- **Stripper (mandatório quando `globalsExtracted.status === 'extracted'`):** ANTES de invocar o composer pra esta página (Step 4e):
434
-
435
- 1. Clone a `inspection` em memória.
436
- 2. Em `inspection.dom`, **remova** as subtrees do `<header>` semântico (se `globalsExtracted.header !== null`) e do `<footer>` / `[role=contentinfo]` (se `globalsExtracted.footer !== null`). Use heurística "parent imediato": o tag `<footer>` direto-de-body-ou-main qualifica; `<article><footer>` aninhado em layouts blog NÃO qualifica.
437
- 3. Também limpe `inspection.hydratedHeader` e `inspection.hydratedFooter` (set null) na cópia editada — assim o composer não tenta re-renderizar o chrome global na página individual.
438
- 4. Passe a inspection editada ao composer.
439
- 5. Após o composer retornar, **prepende** ao output uma linha de comentário:
440
- - HTML: `<!-- sb-build-site: header in clean/global/header.html -->\n` (se header extracted)
441
- - HTML: `<!-- sb-build-site: footer in clean/global/footer.html -->` (se footer extracted, no fim)
442
- - Liquid: idem com `{% comment %} ... {% endcomment %}`
443
- 6. Grava em `clean/{home,pdp,...}/{slug}.html` o output já strippado-e-comentado.
444
-
445
- **Stripper validation:** após gravar, faça grep:
446
- ```
447
- grep -nE "<header[ >]|<footer[ >]" clean/{home,pdp,...}/{slug}.{html,liquid}
448
- ```
449
- Match → log `[build-site] WARN: stripper miss in {slug}.{ext}` em `decisions.md`. Não-blocking — orchestrator continua, mas vira entry em "Stripper misses" no report final.
450
-
451
- ### Step 4e — Build (Skill)
452
-
453
- Mesma lógica do `/build-page`. Filtre o memory cascade pelo `inspection.sectionType` (ou pelo `page.type` se a inspeção devolver a página inteira) e passe subset filtrado. Invoque:
96
+ **Step 4c Clone header as global section**:
454
97
 
455
98
  ```
456
99
  Skill(
457
- skill="sb-build-wp",
458
- args="inspection={inspection-path} assets-map={assets-map-path} output-path={output-path} preset=wp-elementor"
100
+ skill="clip-section",
101
+ args="{home-URL} --selector '{header-selector}' --target {target} --project-name {project-slug} --max-iterations {max} --section-slug global/header"
459
102
  )
460
103
  ```
461
104
 
462
- (`sb-build-shopify` quando `target === shopify`, com `preset=shopify-section`.)
463
-
464
- Se o validator interno do builder retornar erros que ele não conseguiu corrigir → `pageResults[].status = ❌`, próxima página.
465
-
466
- ### Step 4f — Validate render (Bash)
467
-
468
- ```bash
469
- node .claude/skills/sb-validate-static-render/scripts/validate-static-render.mjs \
470
- --file "{output-path}" \
471
- --preset "{preset}" \
472
- --output-dir "{output_folder}/{project-slug}/reports/validations/{slug}-{iteration}" \
473
- --viewport-width {default_viewport} \
474
- --viewport-height 844 \
475
- [--assets-map-path "{assets-map-path}"]
476
- ```
477
-
478
- **`--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.
105
+ The output goes to `sections/global/header/`.
479
106
 
480
- 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.
107
+ **Step 4d Clone footer as global section**: same as 4c with footer-selector → `sections/global/footer/`.
481
108
 
482
- ### Step 4f-bisTest interactivity (Bash, diagnostic gate)
109
+ **Step 4ePromote final files** to easy-paste locations:
483
110
 
484
111
  ```bash
485
- node .claude/skills/sb-test-interactivity/scripts/test-interactivity.mjs \
486
- --file "{output-path}" \
487
- --preset "{preset}" \
488
- --output-dir "{output_folder}/{project-slug}/reports/validations/{slug}-{iteration}"
112
+ cp sections/global/header/section.html final/header.html
113
+ cp sections/global/footer/section.html final/footer.html
489
114
  ```
490
115
 
491
- Capture `interactivity-report.json` (mesmo dir do `validate-static-render`).
492
-
493
- **Modo diagnóstico (não-blocker):**
494
-
495
- - 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`.
496
- - Exit `0` AND `passed === true` → silently proceed. Sem warning, sem mudança de status.
497
- - Exit `0` AND `passed === false`:
498
- - `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.
499
- - 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).
500
- - **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.
501
- - Continua pipeline (Step 4g).
502
-
503
- **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+).
504
-
505
- ### Step 4g — Compare visual (Bash)
506
-
507
- ```bash
508
- node .claude/skills/sb-compare-visual/scripts/compare-visual.mjs \
509
- --live-screenshot "{inspection-screenshot}" \
510
- --build-screenshot "{render-screenshot}" \
511
- --output-dir "{output_folder}/{project-slug}/reports/diffs/{slug}-{iteration}" \
512
- --tokens-live "{inspection-path}" \
513
- --tokens-build "{render-json-path}" \
514
- --threshold {diff_threshold_percent} \
515
- [--crop-live-bbox "{x},{y},{w},{h}"] \
516
- [--crop-build-bbox "{x},{y},{w},{h}"]
517
- ```
518
-
519
- Mesma regra de bbox cropping do `/build-page` (Pattern #27 + anti-pattern #10). Capture `compare`, hold `{compare-report-path}`. Não branche ainda — Step 4h é o ground truth.
520
-
521
- ### Step 4h — Review checks (Bash, sempre roda — Pattern #28)
522
-
523
- ```bash
524
- node .claude/skills/sb-review-checks/scripts/review-checks.mjs \
525
- --file "{output-path}" \
526
- --preset "{preset}" \
527
- --output-dir "{output_folder}/{project-slug}/reports/validations/{slug}-{iteration}" \
528
- --compare-diffs "{compare-report-path}"
529
- ```
530
-
531
- Capture `review`.
532
-
533
- ### Step 4i — Decision matrix (Pattern #28)
534
-
535
- **Pre-check 0 (coverage gate, runs FIRST, before the matrix):**
536
-
537
- Calcule contra schema **real** do `sb-inspect-live` (não invenção):
538
-
539
- ```
540
- buildHeight = render.geometry.totalHeight # source primário do sb-validate-static-render
541
- liveMainHeight = inspection.dom[0].bbox.h # primary: root walked node bbox (body/main)
542
- || (walk inspection.dom recursively, collect classified-sections bboxes,
543
- return max(s.bbox.y + s.bbox.h) - min(s.bbox.y))
544
- # fallback: span das classified sections (warn)
545
- coverageRatio = buildHeight / liveMainHeight
546
- ```
547
-
548
- 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).
549
-
550
- 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`.
551
-
552
- 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.
553
-
554
- 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'`.
555
-
556
- **Tier triage:**
557
-
558
- | Range | Status | Ação |
559
- | --- | --- | --- |
560
- | `< 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. |
561
- | `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. |
562
- | `>= 0.85` | (proceed) | Coverage OK. Prossegue pra matrix abaixo silenciosamente. |
563
-
564
- Anote em `pageResults[].coverage = { buildHeight, liveMainHeight, ratio, source: 'rootBbox'|'sectionsSpan', missingSections?: [...], skippedReason?: <string> }`.
565
-
566
- `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.
567
-
568
- **Decision matrix existente (roda só se coverage gate não escalou pra ❌):**
116
+ If a global section fails to converge (`diff% > threshold` after max iterations), emit a warning but DO NOT block the per-page loop — the user can manually adjust later.
569
117
 
570
- | `review.passed` | `compare.passed` | Ação na página |
571
- | --- | --- | --- |
572
- | true | true | ✅ **Página pronta.** Atualiza `pageResults[]` com sucesso, próxima página. |
573
- | false | true | ⚠️ **Estrutural warning, sem loop.** `pageResults[].status = ⚠️` com `violations[]`. Próxima página. |
574
- | true | false | ⚠️ **Visual drift sem fixHints.** `pageResults[].status = ⚠️` com top-5 `structuredDiffs`. Próxima página. |
575
- | false | false | 🔄 **Auto-correct loop.** Vai pra Step 4j. |
118
+ ### Step 5 Per-page loop
576
119
 
577
- Pré-checks adicionais (idênticos ao `/build-page`):
578
- 1. `--no-auto-correct` foi passado → escala primeiro diff de cada página: rotas que iriam pra Step 4j viram ⚠️ direto.
579
- 2. `iteration >= auto_correct_max_iterations` (default 2) → page goes to **❌** (NOT ⚠️) per V03-3 zero-fabrication policy. The composer had its 2 attempts and couldn't produce a faithful build; shipping the partial output as "warning" would be misleading. Mark `pageResults[].status = ❌`, message: `"compose-failed after 2 attempts — see TODOs in {output-path} for unreadable sections"`. The fragment IS still written so the user can inspect/edit it manually, but the orchestrator's contract with the user (per V03-3 hard-fail rule) is: don't claim something is ready when it's not.
580
- 3. **Mesmo `violations[]` 2 iterações seguidas** → fixHint não pegou. Não rode 3ª. ❌ imediato.
581
- 4. **(V03-3) `review.violations[]` includes `composition-fabrication-detected` or `composition-todo-bloat`** (composer self-flagged unreadable sections) → goes to Step 4j once. If 2nd attempt also flags, ship as ❌ — não como ⚠️.
582
-
583
- ### Step 4j — Auto-correct iteration (loop FECHADO por página)
120
+ For each page in `pages-confirmed.json`, run `/build-page`:
584
121
 
585
122
  ```
586
123
  Skill(
587
- skill="sb-build-wp",
588
- args="inspection={inspection-path} assets-map={assets-map-path} output-path={output-path} preset=wp-elementor previous-html-path={output-path} fix-hints-path={review-report-path}"
124
+ skill="build-page",
125
+ args="{page.URL} --target {target} --project-name {project-slug} --max-iterations {max}"
589
126
  )
590
127
  ```
591
128
 
592
- `fixHints` repassados verbatim não reformule. Increment iteration. Volta pra Step 4f (validate compare review matrix).
593
-
594
- **Sob nenhuma circunstância pergunte ao usuário no meio do loop.** O batch é fechado por página. Esgotou budget → ⚠️.
595
-
596
- ### Step 4k — Append to pageResults
597
-
598
- 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.
599
-
600
- ---
601
-
602
- ## Step 5 — Aggregate report
603
-
604
- Depois que TODAS as páginas confirmadas foram processadas (`pageResults[].length === pagesConfirmed.length`):
605
-
606
- 1. **Escreva `{output_folder}/{project-slug}/reports/index.html`** com:
607
- - Header com root URL, target, project-slug, timestamp do run, totals (`X✅ / Y⚠️ / Z❌`).
608
- - **Section "Globals extraction" (V03-0a)** logo abaixo do header, ANTES da tabela: badge ✅/❌/⏭️ (extracted/failed/skipped) + `globalsExtracted.status`, `globalsExtracted.failureReason` quando aplicável, links pra `clean/global/header.html` e `clean/global/footer.html` quando extracted, link pro `interactivity-report.json` da fase global, link pro diff map da fase global. Se `status === 'failed'`, esta seção é a única coisa renderizada antes do "batch BLOCKED" notice — não há tabela de páginas (Step 3.5g já abortou).
609
- - 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`.
610
- - Section "Auto-correct details" listando páginas com iteration > 0 e o que mudou.
611
- - Section "Escalations" com páginas ⚠️/❌ — top diffs visuais e candidate fixes inline.
612
- - 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.
613
- - Section "Coverage warnings" listando páginas com `coverage.ratio < 0.85`, ordenadas por ratio asc — primeiro caso é o mais crítico.
614
- - Section "Stripper misses" (V03-0a) — listar entries de `decisions.md` matchando `WARN: stripper miss in <slug>` quando `globalsExtracted.status === 'extracted'`, com link pro arquivo offending pra revisão manual.
615
- - Footer com: link pra `pages-confirmed.json`, link pro `crawl/pages-list.json` (raw discovery), config snapshot.
616
-
617
- 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.
618
-
619
- 3. **Append decisions.md** com uma linha-resumo do batch: `{ts} | /build-site {root-URL} | target={target} | pages={N} (✅{a} ⚠️{b} ❌{c}) | total-iterations={sum}`.
620
-
621
- ---
622
-
623
- ## Step 6 — Batched auto-learn prompt
624
-
625
- Único momento opcional de interação após o checkpoint. Dispare se EITHER condição (a) OU (b) for atendida:
626
-
627
- **(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.
628
-
629
- **(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.
630
-
631
- 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.
632
-
633
- A lista para o digest é a UNIÃO dos patterns detectados pelas duas clauses (deduplicado por `id`).
634
-
635
- 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).
636
-
637
- 2. Se a lista pós-filtragem não está vazia, pergunte UMA vez ao usuário, em português, em formato de digest:
638
-
639
- ```
640
- 📚 {N} novo(s) anti-pattern(s) emergiram durante o batch:
641
-
642
- 1. {pattern-name} — {fix-recipe} (visto em: {paths})
643
- 2. ...
644
-
645
- Persistir em ~/.claude/similarbuild-memory/anti-patterns.md? [todos / nenhum / 1,3 / nunca]
646
- ```
647
-
648
- 3. Branch:
649
- - `todos` → append todos. Crie diretório/arquivo se não existir.
650
- - `nenhum` (default no Enter vazio) → não salva.
651
- - lista de números → salva só esses.
652
- - `nunca` → não salva E append `auto_learn_disabled: true` em `.sb-memory/decisions.md` (igual ao `/build-page`).
653
-
654
- 4. Pular o passo silenciosamente se a flag `auto_learn_disabled: true` já está em `decisions.md`.
655
-
656
- ---
129
+ `/build-page` discovers the page's body sections (excluding the header/footer bbox already captured in Step 4) and clones each via `/clip-section`. Outputs to `sections/{page-slug}/`.
657
130
 
658
- ## Step 7Final summary
131
+ When the page's body section list overlaps with the global header/footer (i.e. the page-level inspection would re-classify the header as a section), the per-page loop SKIPS the header/footer bbox those are already in `sections/global/`.
659
132
 
660
- Imprima em português:
133
+ ### Step 6 — Final report
661
134
 
662
135
  ```
663
136
  🏁 /build-site {root-URL} → {output_folder}/{project-slug}/
664
137
 
665
- ✅ {a} páginas prontas
666
- ⚠️ {b} páginas com warnings (revisar)
667
- {c} páginas falharam (não buildadas)
138
+ Globals:
139
+ header (diff {pct}%, {iter} iterations)
140
+ footer (diff {pct}%, {iter} iterations)
668
141
 
669
- Report agregado: {output_folder}/{project-slug}/reports/index.html
670
- Decisões: {output_folder}/{project-slug}/.sb-memory/decisions.md
142
+ Pages ({N} total):
143
+ home → sections/home/ ({sections-count} sections, {matching} ✅, {residual} ⚠️)
144
+ pdp/originals → sections/pdp/originals/
145
+ ...
671
146
 
672
- Próximo passo:
673
- 1. Abra o report.html no browser pra inspeção visual.
674
- 2. Páginas podem ser coladas no destino direto.
675
- 3. Páginas ⚠️ vêm com candidate fixes — revise e re-rode com hints manuais via /build-page se quiser refinar.
676
- 4. Páginas precisam de Plan B (URL alternativa, paste de HTML renderizado).
677
- ```
678
-
679
- Pare. Não fique online esperando comando.
680
-
681
- ---
682
-
683
- ## Failure modes (cross-cutting)
684
-
685
- | Symptom | Likely cause | Action |
686
- | --- | --- | --- |
687
- | `cheerio` missing | First-time setup | Surface stderr + sugira `npm i cheerio`. Pare. |
688
- | `playwright` missing | Idem | Sugira `npx playwright install chromium` (mensagem padrão das outras skills). |
689
- | `crawl.warnings[]` inclui `robots-disallow-root` | Site bloqueia crawler | Escala. NÃO buildar. (Step 2.) |
690
- | `crawl.pageCount === 0` + `spa-suspected` | SPA puro | Pare. Sugira `--sitemap-path`. |
691
- | Usuário responde com formato inesperado no checkpoint | Comando não reconhecido | Re-prompt na mesma etapa. Não cancela. |
692
- | Página individual: `widgetBlocked: true` (mid-batch) | Página específica atrás de challenge | `❌` essa página, próxima. Não pare o batch. |
693
- | Página individual: `widgetBlocked: true` na home (primeira) | Site inteiro atrás de bot-wall | Pare e escale ao usuário. |
694
- | Mesmas violations duas iterações seguidas em uma página | fixHint não está pegando | Não rode 3ª iteração — `⚠️` imediato (Step 4i pré-check #3). |
695
- | Múltiplas páginas com mesmo `{output-path}` | Slug colision | Sufixar `-2`, `-3`, ... e logar (Step 4a). |
696
- | Run interrompido no meio (Ctrl+C) | Fluxo abortado | Os `pageResults[]` parciais não são gravados em `report.html`; mas `pages-confirmed.json` ficou. Re-rodar `/build-site` retoma o crawl do zero — feature de "resume" é roadmap V2. |
697
-
698
- ---
699
-
700
- ## Output structure (recap)
701
-
702
- Após um run sucesso em `https://example.com` com 12 páginas confirmadas:
147
+ Paste order in WP/Elementor:
148
+ 1. final/header.html Site Header template (paste ONCE)
149
+ 2. final/footer.html Site Footer template (paste ONCE)
150
+ 3. For each page, paste its sections/<page-slug>/*/section.html in order
151
+ (numbered 01, 02, 03...) into Custom HTML widgets on that page.
152
+ Optional: final/<page-slug>-body.html concatenates them.
703
153
 
154
+ Decisions: {output_folder}/{project-slug}/.sb-memory/decisions.md
704
155
  ```
705
- {output_folder}/example/
706
- ├── clean/
707
- │ ├── home/index.html
708
- │ ├── pdp/foo.html
709
- │ ├── pdp/bar.html
710
- │ ├── collections/all.html
711
- │ ├── pages/contact.html
712
- │ ├── pages/privacy-policy.html
713
- │ ├── pages/blog-post-x.html
714
- │ └── global/ ← se Step 4d detectou
715
- │ ├── header.html
716
- │ └── footer.html
717
- ├── assets/
718
- │ └── {content-hash}.{jpg,png,webp} ← compartilhado entre páginas
719
- ├── reports/
720
- │ ├── index.html ← agregado
721
- │ ├── crawl/
722
- │ │ ├── pages-list.json ← output bruto do sb-crawl-and-list
723
- │ │ └── sitemap-raw.xml ← se houve sitemap
724
- │ ├── diffs/{slug}-{iteration}/diff-map.png
725
- │ └── validations/{slug}-{iteration}/screenshot.png
726
- └── .sb-memory/
727
- ├── inspect-{slug}-{ts}/inspection.json
728
- ├── inspect-{slug}-{ts}/screenshot.png
729
- ├── pages-confirmed.json ← lista pós-checkpoint humano
730
- └── decisions.md
731
- ```
732
-
733
- Nunca escreva fora de `{project-slug}/`. Asset sharing é dentro do projeto (via `assets/`); cross-project knowledge sai por `~/.claude/similarbuild-memory/` (process knowledge, não conteúdo de loja).
734
-
735
- ---
736
-
737
- ## What you do NOT do
738
-
739
- - Não invoque `/build-page` via Task tool nem delegue páginas pra subagents — orchestrate direto.
740
- - Não rode em paralelo nesta versão. Sequencial. (Roadmap V2: `/build-site parallelism`.)
741
- - Não pergunte nada ao usuário entre o checkpoint inicial e o auto-learn final. Auto-correção é loop fechado por página.
742
- - Não reformule, resuma ou interprete `candidateFix` strings — passe verbatim ao builder.
743
- - Não mostre paths de build "ready to ship" antes do par validate+compare confirmar para aquela página.
744
- - Não crie `.claude/sb-config.yaml` se ausente — opere com defaults.
745
- - Não tente cobrir SPA puros sem `--sitemap-path` — a skill já avisa, sua função é repassar.
746
- - Não retome um run interrompido nesta versão — re-rode do zero. (Resume é roadmap V2.)
747
- - Não pare o batch numa falha individual de página (exceto a home na primeira posição quando `widgetBlocked`).
748
- - Não pule `sb-review-checks` mesmo quando `compare.passed === true` (Pattern #28 — sempre roda).
749
-
750
- ---
751
-
752
- ## V2 roadmap (não implementar agora)
753
-
754
- Documentado aqui pra rastreabilidade — não é pra buildar nesta versão:
755
156
 
756
- 1. **Paralelismo real** com `max_parallel_pages`: Bash backgrounding + semáforo + merge ordenado de outputs.
757
- 2. **Resume de batch interrompido**: ler `pages-confirmed.json` + `pageResults[]` parcial e retomar do índice seguinte.
758
- 3. **Global section dedupe (heurística "≥3 páginas")** quando inspection emitir hashes estruturais comparáveis.
759
- 4. **Cross-run report aggregation** (mesclar runs sucessivos visualmente em vez de hierarquia "Previous runs").
760
- 5. **Title hydration** — preencher `title` no `pages-confirmed.json` a partir das inspections individuais durante o batch (hoje vem `null` da skill).
761
-
762
- ---
763
-
764
- ## Quick start (smoke test path)
765
-
766
- Quando a framework estiver wired-up, smoke test canônico:
767
-
768
- ```
769
- /build-site https://example.com --target wp --max-pages 5
770
- ```
157
+ ## Non-negotiables
771
158
 
772
- Esperado: crawl roda em segundos, mostra tabela de até 5 páginas, você responde `build all` → `confirm`, batch sequencial roda 5 vezes o pipeline `/build-page`-like, gera `reports/index.html` com 5 entradas. MVP milestone do `/build-site`.
159
+ 1. **Mobile-first always.** Viewport 390×844 default.
160
+ 2. **Single human checkpoint = page list.** Don't ask other questions mid-batch.
161
+ 3. **Globals composed first.** Header + footer are paste-once in the WP/Shopify template, not duplicated per page.
162
+ 4. **Per-section pixel parity.** Every section.html validates via render+diff against its reference.png.
163
+ 5. **No globals composition recipe.** Header and footer are sections like any other — they go through `/clip-section` with the same 4-step loop.
164
+ 6. **No fabrication.** Composer emits TODO comments when it can't read a literal confidently.
165
+ 7. **Sequential per-page (MVP).** No parallel clones in this version.