similarbuild 0.3.5 → 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.
- package/package.json +1 -1
- package/templates/commands/build-page.md +62 -493
- package/templates/commands/build-site.md +98 -705
- package/templates/commands/clip-section.md +102 -501
- package/templates/skills/sb-build-wp/SKILL.md +37 -265
- package/templates/skills/sb-inspect-live/scripts/inspect-live.mjs +4 -512
- package/templates/skills/sb-review-checks/SKILL.md +0 -113
- package/templates/skills/sb-review-checks/references/review-rules.md +0 -195
- package/templates/skills/sb-review-checks/scripts/lib/anti-patterns.mjs +0 -385
- package/templates/skills/sb-review-checks/scripts/lib/cross-reference.mjs +0 -115
- package/templates/skills/sb-review-checks/scripts/lib/design-quality.mjs +0 -541
- package/templates/skills/sb-review-checks/scripts/review-checks.mjs +0 -250
- package/templates/skills/sb-review-checks/scripts/tests/test-anti-patterns.mjs +0 -366
- package/templates/skills/sb-review-checks/scripts/tests/test-cross-reference.mjs +0 -170
- package/templates/skills/sb-review-checks/scripts/tests/test-design-quality.mjs +0 -493
- package/templates/skills/sb-review-checks/scripts/tests/test-review-checks.mjs +0 -267
- package/templates/skills/sb-test-interactivity/SKILL.md +0 -133
- package/templates/skills/sb-test-interactivity/scripts/test-interactivity.mjs +0 -970
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/aria-controls-broken.html +0 -32
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/aria-controls-good.html +0 -47
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/deferred-listeners.html +0 -67
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/details-good.html +0 -25
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/dialog-good.html +0 -38
- package/templates/skills/sb-test-interactivity/scripts/tests/fixtures/no-interactive.html +0 -15
- package/templates/skills/sb-test-interactivity/scripts/tests/test-test-interactivity.mjs +0 -223
|
@@ -1,772 +1,165 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
3
|
-
argument-hint: <root-URL> [--target wp|shopify] [--project-name <slug>] [--
|
|
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 —
|
|
6
|
+
# /build-site — site = globals + list of pages
|
|
7
7
|
|
|
8
|
-
You are the SimilarBuild orchestrator for a full
|
|
8
|
+
You are the SimilarBuild orchestrator for a full site. The mental model:
|
|
9
9
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
65
|
-
| `--target wp\|shopify` | no | `
|
|
66
|
-
| `--project-name <slug>` | no |
|
|
67
|
-
| `--
|
|
68
|
-
| `--
|
|
69
|
-
| `--
|
|
70
|
-
| `--
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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}
|
|
130
|
-
[--max-pages {max-pages
|
|
131
|
-
[--sitemap-path "{sitemap-path
|
|
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
|
-
|
|
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
|
-
|
|
354
|
-
Motivo: {failureReason}
|
|
355
|
-
Artefatos: {output_folder}/{project-slug}/reports/index.html
|
|
60
|
+
### Step 2 — Confirm page list (single human checkpoint)
|
|
356
61
|
|
|
357
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69
|
+
Persist final list to `.sb-memory/pages-confirmed.json`.
|
|
375
70
|
|
|
376
|
-
|
|
71
|
+
### Step 3 — Dry-run check
|
|
377
72
|
|
|
378
|
-
|
|
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
|
-
|
|
75
|
+
### Step 4 — Clone globals (header + footer)
|
|
390
76
|
|
|
391
|
-
|
|
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
|
-
|
|
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 "{
|
|
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-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
94
|
+
Generate stable CSS selectors for both.
|
|
432
95
|
|
|
433
|
-
**
|
|
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="
|
|
458
|
-
args="
|
|
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
|
-
|
|
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
|
-
|
|
107
|
+
**Step 4d — Clone footer as global section**: same as 4c with footer-selector → `sections/global/footer/`.
|
|
481
108
|
|
|
482
|
-
|
|
109
|
+
**Step 4e — Promote final files** to easy-paste locations:
|
|
483
110
|
|
|
484
111
|
```bash
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
588
|
-
args="
|
|
124
|
+
skill="build-page",
|
|
125
|
+
args="{page.URL} --target {target} --project-name {project-slug} --max-iterations {max}"
|
|
589
126
|
)
|
|
590
127
|
```
|
|
591
128
|
|
|
592
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
133
|
+
### Step 6 — Final report
|
|
661
134
|
|
|
662
135
|
```
|
|
663
136
|
🏁 /build-site {root-URL} → {output_folder}/{project-slug}/
|
|
664
137
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
138
|
+
Globals:
|
|
139
|
+
✅ header (diff {pct}%, {iter} iterations)
|
|
140
|
+
✅ footer (diff {pct}%, {iter} iterations)
|
|
668
141
|
|
|
669
|
-
|
|
670
|
-
|
|
142
|
+
Pages ({N} total):
|
|
143
|
+
home → sections/home/ ({sections-count} sections, {matching} ✅, {residual} ⚠️)
|
|
144
|
+
pdp/originals → sections/pdp/originals/
|
|
145
|
+
...
|
|
671
146
|
|
|
672
|
-
|
|
673
|
-
1.
|
|
674
|
-
2.
|
|
675
|
-
3.
|
|
676
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|