ganbatte-os 0.2.34 → 0.2.35

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.
@@ -99,6 +99,7 @@ comprehension_gate:
99
99
  step_0_58_knowledge: "On *plan: index <PROJETO>/docs/regras-de-negocio/ and <PROJETO>/docs/postman/ (when present). Register inventory in progress.txt under '## Knowledge mapped — PLAN-NNN'. Ausência não bloqueia (apenas Storybook bloqueia)."
100
100
  step_0_59_backend_gaps: "On *plan: detected backend gaps (endpoint não existe no Postman, RLS incompleto, migration ausente para o shape exigido) → criar task ClickUp via mcp__clickup__clickup_create_task, assignee Douglas Oliveira (112010775) salvo override ASSIGNEE no prompt. Título: '[Backend] PLAN-NNN: <gap>'. Registrar IDs em progress.txt e plan.md (## Backend pendings). Flag --skip-clickup desliga."
101
101
  step_0_6_progress: "If progress.txt exists at the configured path: read it for active plan/task context (memória L1)."
102
+ step_1_2_interactions: "On *plan: detect if a tela tem table-clicável/drawer/modal/popup. Heurística: (a) Figma MCP frames com layer names contendo Drawer|Modal|Dialog|Popup|Sheet; (b) NOTAS menciona drawer/modal/popup/clickable row; (c) tela existente em dirs.app com componente equivalente (page.tsx que importa Drawer/Dialog). Se sim E INTERACOES ausente no input: BLOQUEAR plan-blueprint e disparar AskUserQuestion estruturado pedindo lista de interações com 3 exemplos pré-preenchidos (clickable row, submit assíncrono, filtro). Resposta vira entrada da Fase 1.4 do plan-blueprint (`## Interações & Estados`). Telas simples (form linear, lista read-only, página estática) NÃO acionam o bloqueio."
102
103
  step_1_document: "State what exists (current state, patterns, constraints) in factual terms"
103
104
  step_2_assess: "Determine which agent/skill/workflow is appropriate based on evidence, not assumption"
104
105
  step_3_delegate: "Route with context summary so the target has full picture"
@@ -203,13 +204,30 @@ routing_matrix:
203
204
  plan_creation:
204
205
  triggers: [plan, plano, screen plan, tela, criar plano, blueprint, plano de tela]
205
206
  target: skill:plan-blueprint
206
- pre_action: Validate docs/stack.md exists; if not, dispatch stack-profiler first
207
+ pre_action: Validate docs/stack.md exists; if not, dispatch stack-profiler first. Run step_1_2_interactions — block + AskUserQuestion when tela tem table-clicável/drawer/modal/popup E INTERACOES ausente.
208
+ post_action: |
209
+ OBRIGATÓRIO em sequência ANTES de devolver "plano criado":
210
+ 1. dispatch skill:plan-to-tasks apontando plan.md recém-criado (gera T-NN*.md no tasks/).
211
+ 2. Bash: `node .gos/scripts/integrations/check-plan.js <plan-dir>` (gate determinístico).
212
+ 3. Se exit != 0: regerar plan-to-tasks 1x e re-rodar check-plan. Se persistir, ABORTAR
213
+ e devolver a saída literal do check-plan ao usuário com instrução para migrate-task-status.js.
214
+ `*plan` NUNCA termina com tasks/ vazio. NUNCA termina sem exit 0 do check-plan.
207
215
  notes: |
216
+ *plan é operação ATÔMICA: {plan.md + context.md + tasks/T-NN.md (TODAS) + progress.txt}.
217
+ Plano sem tasks = falha — plan-blueprint deve invocar plan-to-tasks E rodar o gate
218
+ determinístico `node .gos/scripts/integrations/check-plan.js <plan-dir>` como ÚLTIMA
219
+ ação. Exit != 0 bloqueia o "plano criado" — sem barreira no LLM (script roda fora).
220
+ Bug histórico: tasks geradas sem frontmatter ficavam travadas em pendente mesmo após
221
+ *execute-plan rodar código (PLAN-006). Planos preexistentes: rodar migrate-task-status.js.
222
+
208
223
  1 tela = 1 plano. OBJETIVO obrigatório no prompt:
209
224
  - implantacao → criar do zero (fluxo padrão)
210
225
  - correcao → cirúrgico, diff vs Storybook, 1 task por componente
211
226
  - refactor → implica --allow-arch-change + ADR
212
227
  Auto-resolve PROJETO/WORK_BRANCH/BUSINESS_RULES/POSTMAN no comprehension gate.
228
+ INTERACOES obrigatório quando tela tem table-clicável/drawer/modal/popup — gos-master bloqueia
229
+ e abre AskUserQuestion se ausente. Plano captura comportamentos (Fase 1.4 do plan-blueprint)
230
+ e page-level overrides (Figma da página > Storybook canônico em conflito visual).
213
231
  Backend gaps → tasks ClickUp automáticas pro Douglas (--skip-clickup desliga).
214
232
 
215
233
  progress_tracking:
@@ -220,11 +238,28 @@ routing_matrix:
220
238
  execute_plan:
221
239
  triggers: [execute, executar plano, run plan, "*execute-plan", execute plan, executar PLAN]
222
240
  target: skill:execute-plan
223
- pre_action: Validate PLAN-NNN-<slug>/plan.md exists; load stack.md; index dirs.storybook stories
241
+ pre_action: Validate PLAN-NNN-<slug>/plan.md exists; load stack.md; index dirs.storybook stories. Verificar que cada T-NN.md tem frontmatter com `status:` — task malformada ABORTA com instrução para rodar migrate-task-status.js.
224
242
  notes: |
225
243
  Comando primário do ambiente Codex IDE Extension.
226
- Ciclo Opus(plan) → Codex(execute). Roda task-a-task com state machine
227
- e visual gate obrigatório contra Storybook canônico antes de validacao.
244
+ Ciclo Opus(plan) → Codex(execute) → Opus(validate). Roda task-a-task com state machine
245
+ e visual gate obrigatório (5 dimensões: anatomia, tokens, variants, densidade, comportamentos)
246
+ contra Storybook canônico antes de validacao. Pre-flight smoke compara screenshot da página
247
+ vs Figma frame antes da T-01 (gera tasks T-000-XX para gaps grandes).
248
+ Non-blocking em backend gaps: tasks com depends_on_backend não-resolvido viram
249
+ bloqueada-backend (ClickUp aberto pro Douglas), demais seguem.
250
+ Transições de status são VINCULANTES com pós-condição: cada task DEVE sair de pendente
251
+ antes da próxima — se o executor não chamar progress-tracker, abortar (bug PLAN-006).
252
+
253
+ validate_plan:
254
+ triggers: [validate, validar plano, "*validate-plan", validate plan, revisar plano, plano implementado]
255
+ target: skill:validate-plan
256
+ pre_action: Validate PLAN-NNN-<slug>/plan.md exists; load progress.txt
257
+ notes: |
258
+ Turn pós-execute. Para cada task em validacao, re-roda visual gate curto
259
+ (anatomia + tokens, sem refazer Figma MCP), confronta diff staged vs
260
+ Componentes mapeados, confere checklist do plano e da task.
261
+ Auto-marca concluido o que passa. Fecha plano quando todas tasks fecham
262
+ E backend pendings ClickUp estão concluídas. NÃO dá push.
228
263
 
229
264
  product_decisions:
230
265
  triggers: [PRD, requirements, product decision, scope, feature priority]
@@ -313,8 +348,11 @@ commands:
313
348
  args: "[init|show|set <plan>|status <task> <novo-status>|compact|read]"
314
349
  description: Gerencia progress.txt (memória L1) e state machine de status
315
350
  - name: execute-plan
316
- args: "<PLAN-NNN-slug> [--task T-NNN-NN] [--skip-visual-gate]"
317
- description: Executa plano task-a-task com visual gate obrigatório (comando primário do Codex IDE)
351
+ args: "<PLAN-NNN-slug> [--task T-NNN-NN] [--skip-visual-gate] [--skip-clickup]"
352
+ description: Executa plano task-a-task com visual gate obrigatório, non-blocking em backend gaps (Codex IDE)
353
+ - name: validate-plan
354
+ args: "<PLAN-NNN-slug> [NOTAS=...]"
355
+ description: Valida plano pós-execute; auto-marca concluido tasks que passam em checklist + visual gate curto + diff (Opus revisor)
318
356
 
319
357
  # Quality
320
358
  - name: check
@@ -351,7 +389,7 @@ available_squads:
351
389
  - name: git-operations
352
390
  purpose: SSH setup, quality gate, safe commit+push
353
391
 
354
- # ─── AVAILABLE SKILLS (19) ────────────────────────────────────
392
+ # ─── AVAILABLE SKILLS (20) ────────────────────────────────────
355
393
  available_skills:
356
394
  - design-to-code
357
395
  - figma-implement-design
@@ -372,6 +410,7 @@ available_skills:
372
410
  - plan-blueprint
373
411
  - progress-tracker
374
412
  - execute-plan
413
+ - validate-plan
375
414
 
376
415
  # ─── PLAYBOOKS ────────────────────────────────────────────────
377
416
  available_playbooks:
@@ -474,7 +513,8 @@ security:
474
513
 
475
514
  - `*stack [refresh|show|drift]` - Mantém docs/stack.md
476
515
  - `*plan <tela|figma-url|descrição>` - Cria plano por tela (Opus, planejamento)
477
- - `*execute-plan <PLAN-NNN-slug>` - Executa plano com visual gate (Codex IDE, execução)
516
+ - `*execute-plan <PLAN-NNN-slug>` - Executa plano com visual gate, non-blocking em backend gaps (Codex IDE, execução)
517
+ - `*validate-plan <PLAN-NNN-slug>` - Valida plano pós-execute; auto-marca concluido (Opus, revisor)
478
518
  - `*progress [show|set|status|compact]` - Gerencia progress.txt (L1)
479
519
 
480
520
  **Framework:**
@@ -0,0 +1,300 @@
1
+ # State Persistence Patterns (FOUC Zero)
2
+
3
+ Reference library for persisting UI state across reloads in SSR/Edge frameworks (Next.js App Router, Remix, SvelteKit) **without** flash of incorrect content (FOUC) or hydration mismatch.
4
+
5
+ This library is the positive counterpart to `content/ai-writing-patterns.md`: instead of cataloging anti-patterns to avoid, it codifies **patterns to apply** for any UI state that survives reload.
6
+
7
+ ---
8
+
9
+ ## When this applies
10
+
11
+ State is "persisted UI state" if **all** of the following are true:
12
+
13
+ 1. It survives page reloads (cookie, localStorage, URL, server session).
14
+ 2. It affects the **first paint** of the layout (sidebar collapsed/expanded, theme, density, locale, RTL/LTR, font-size preset).
15
+ 3. It has a default value used when the persisted value is missing.
16
+
17
+ If state is client-only and **does not** affect the first paint (form drafts, in-page panels not visible at load, ephemeral preferences) — these patterns are not required; localStorage + `useEffect` is fine.
18
+
19
+ ---
20
+
21
+ ## The core problem: client-only persistence creates FOUC
22
+
23
+ A naive implementation reads the persisted value on the client *after* hydration:
24
+
25
+ ```tsx
26
+ // ANTI-PATTERN — produces visible flash
27
+ function SidebarProvider({ defaultOpen = true, children }) {
28
+ const [open, setOpen] = useState(defaultOpen)
29
+
30
+ useEffect(() => {
31
+ const saved = document.cookie.match(/sidebar_state=(\w+)/)?.[1]
32
+ if (saved === "false") setOpen(false) // flash: SSR rendered open, this closes it
33
+ }, [])
34
+
35
+ return <Context.Provider value={{ open }}>{children}</Context.Provider>
36
+ }
37
+ ```
38
+
39
+ Sequence that produces the flash:
40
+
41
+ 1. **SSR**: HTML is rendered with `defaultOpen=true` (server has no access to client state).
42
+ 2. **First paint**: browser shows sidebar **open**.
43
+ 3. **Hydration**: React mounts with `open=true`.
44
+ 4. **`useEffect` fires**: reads cookie `false` → `setOpen(false)` → re-render → sidebar collapses.
45
+
46
+ The flash is 16–200ms depending on network/CPU. On slow connections it is painful UX.
47
+
48
+ ---
49
+
50
+ ## Pattern A — Server-read cookie + `defaultX` injection (canonical)
51
+
52
+ **When to use**: any state where the persistence medium is a **cookie** and the framework supports reading cookies in Server Components.
53
+
54
+ **Next.js App Router (canonical)**:
55
+
56
+ ```tsx
57
+ // app-shell.tsx — Server Component
58
+ import { cookies } from "next/headers"
59
+ import { SidebarProvider } from "@/components/ui/sidebar"
60
+
61
+ export async function AppShell({ children }: { children: React.ReactNode }) {
62
+ const cookieStore = await cookies() // Next 15+ async; Next 14 sync
63
+ const saved = cookieStore.get("sidebar_state")?.value
64
+ const defaultOpen = saved === undefined ? true : saved === "true"
65
+
66
+ return <SidebarProvider defaultOpen={defaultOpen}>{children}</SidebarProvider>
67
+ }
68
+ ```
69
+
70
+ ```tsx
71
+ // sidebar.tsx — Client Component
72
+ "use client"
73
+
74
+ export function SidebarProvider({ defaultOpen = true, children }) {
75
+ const [open, _setOpen] = useState(defaultOpen) // hydrates with correct value
76
+
77
+ const setOpen = useCallback((next: boolean) => {
78
+ _setOpen(next)
79
+ document.cookie = `sidebar_state=${next}; path=/; max-age=${60 * 60 * 24 * 7}`
80
+ }, [])
81
+
82
+ return <Context.Provider value={{ open, setOpen }}>{children}</Context.Provider>
83
+ }
84
+ ```
85
+
86
+ **Why this works**:
87
+ - Server reads cookie → SSR HTML reflects the persisted state.
88
+ - First paint already correct.
89
+ - Hydration uses `defaultOpen` from server → no client-side correction needed.
90
+ - `useEffect` is **not used for initial state** — only `setOpen` writes the cookie when the user toggles.
91
+
92
+ **Cost**: `cookies()` forces dynamic rendering of the page that uses it. Acceptable for authenticated dashboards (already dynamic). For static pages, use Pattern C.
93
+
94
+ ---
95
+
96
+ ## Pattern B — `headers()` for derived state (locale, theme by system pref proxy)
97
+
98
+ When the state can be derived from a request header (e.g., `Accept-Language`, `Sec-CH-Prefers-Color-Scheme`), read it server-side and inject as default:
99
+
100
+ ```tsx
101
+ import { headers } from "next/headers"
102
+
103
+ export async function ThemeShell({ children }) {
104
+ const h = await headers()
105
+ const prefersDark = h.get("sec-ch-prefers-color-scheme") === "dark"
106
+ return <ThemeProvider defaultTheme={prefersDark ? "dark" : "light"}>{children}</ThemeProvider>
107
+ }
108
+ ```
109
+
110
+ Use this **in addition to** Pattern A: server reads cookie first; if missing, derives from headers; client toggles persist back to cookie.
111
+
112
+ ---
113
+
114
+ ## Pattern C — Inline blocking script for static pages (last resort)
115
+
116
+ When the page **must** stay static (cannot opt into dynamic rendering) but still needs FOUC-free persisted state, use a small synchronous script in `<head>` that sets a class/attribute on `<html>` **before** the first paint:
117
+
118
+ ```tsx
119
+ // app/layout.tsx
120
+ import Script from "next/script"
121
+
122
+ export default function RootLayout({ children }) {
123
+ return (
124
+ <html lang="pt-BR" suppressHydrationWarning>
125
+ <head>
126
+ <Script id="set-sidebar-state" strategy="beforeInteractive">
127
+ {`(function(){try{var m=document.cookie.match(/sidebar_state=(\\w+)/);
128
+ if(m&&m[1]==="false"){document.documentElement.dataset.sidebar="collapsed"}}catch(e){}})()`}
129
+ </Script>
130
+ </head>
131
+ <body>{children}</body>
132
+ </html>
133
+ )
134
+ }
135
+ ```
136
+
137
+ Then CSS reads the attribute:
138
+
139
+ ```css
140
+ [data-sidebar="collapsed"] aside { width: 56px; }
141
+ ```
142
+
143
+ **Caveats**:
144
+ - Adds blocking JS to critical path (~1KB).
145
+ - CSP must allow the inline script (or extract to external file with hash).
146
+ - Only use when Pattern A is impossible (truly static pages).
147
+
148
+ This is what `next-themes` does for theme persistence on static pages.
149
+
150
+ ---
151
+
152
+ ## Pattern D — URL state (no FOUC by definition)
153
+
154
+ For state that should be **shareable** (filters, tabs, pagination), persist in the URL search params. The server reads `searchParams` on render, so first paint is always correct:
155
+
156
+ ```tsx
157
+ // page.tsx — Server Component
158
+ export default async function Page({ searchParams }) {
159
+ const params = await searchParams
160
+ const view = params.view ?? "grid"
161
+ return <Layout defaultView={view}>...</Layout>
162
+ }
163
+ ```
164
+
165
+ This trades persistence-across-sessions (cookies persist; URL is per-tab) for shareability and bookmarkability. Use both: URL for view state, cookie for chrome (sidebar/theme).
166
+
167
+ ---
168
+
169
+ ## Decision tree
170
+
171
+ ```
172
+ Does the state affect first paint?
173
+ ├── No → localStorage + useEffect is fine.
174
+ └── Yes → Is it shareable / bookmarkable?
175
+ ├── Yes → Pattern D (URL search params).
176
+ └── No → Is the route already dynamic?
177
+ ├── Yes → Pattern A (cookies() in Server Component).
178
+ └── No → Can the route opt into dynamic rendering?
179
+ ├── Yes → Pattern A; route becomes dynamic.
180
+ └── No → Pattern C (blocking script).
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Anti-patterns (catalog)
186
+
187
+ ### A1. `useEffect` to read persisted state and `setState`
188
+
189
+ ```tsx
190
+ // BAD
191
+ useEffect(() => {
192
+ const saved = localStorage.getItem("theme")
193
+ if (saved) setTheme(saved)
194
+ }, [])
195
+ ```
196
+
197
+ **Why bad**: SSR renders default; client corrects after hydration → flash.
198
+
199
+ **Fix**: Pattern A or C.
200
+
201
+ ### A2. Conditional render that hides until hydrated
202
+
203
+ ```tsx
204
+ // BAD
205
+ const [hydrated, setHydrated] = useState(false)
206
+ useEffect(() => setHydrated(true), [])
207
+ if (!hydrated) return null // layout shift on hydration
208
+ return <Sidebar open={open} />
209
+ ```
210
+
211
+ **Why bad**: layout shift (CLS hit), accessibility regression (sidebar invisible to screen readers initially).
212
+
213
+ **Fix**: Pattern A or C.
214
+
215
+ ### A3. `suppressHydrationWarning` to mask the mismatch
216
+
217
+ ```tsx
218
+ // BAD
219
+ <aside suppressHydrationWarning data-state={open ? "open" : "closed"}>
220
+ ```
221
+
222
+ **Why bad**: hides the symptom, not the cause. The actual visual flash still happens.
223
+
224
+ **Fix**: solve the root cause with Pattern A. Use `suppressHydrationWarning` only on `<html>` for theme (where the script in Pattern C legitimately mutates the attribute before React hydrates).
225
+
226
+ ### A4. Cookie set in client without server read
227
+
228
+ ```tsx
229
+ // BAD — server cannot read what only the client wrote
230
+ function setOpen(next) {
231
+ document.cookie = `sidebar_state=${next}`
232
+ setOpenState(next)
233
+ }
234
+ // And the server never reads "sidebar_state" → flash on every reload.
235
+ ```
236
+
237
+ **Why bad**: persistence works (cookie is sent on next request), but the server doesn't use it — the loop is incomplete.
238
+
239
+ **Fix**: complete the loop — server reads in Pattern A.
240
+
241
+ ### A5. localStorage for layout-affecting state
242
+
243
+ ```tsx
244
+ // BAD for state affecting first paint
245
+ localStorage.setItem("sidebar_state", "false")
246
+ ```
247
+
248
+ **Why bad**: localStorage is **client-only** by definition; no server-side reading possible. Inevitable flash.
249
+
250
+ **Fix**: migrate to cookie (Pattern A) or accept blocking script (Pattern C). localStorage is fine for client-only state that doesn't affect first paint.
251
+
252
+ ### A6. Default state assumed identical for all users
253
+
254
+ ```tsx
255
+ // BAD when users have different defaults (locale, timezone, RTL)
256
+ <App defaultLocale="en" />
257
+ ```
258
+
259
+ **Why bad**: ignores `Accept-Language`; first paint is "wrong" for half the users until client correction.
260
+
261
+ **Fix**: Pattern B (read header) + Pattern A (override with user-saved cookie).
262
+
263
+ ---
264
+
265
+ ## Checklist for any persisted UI state
266
+
267
+ Before merging a feature that persists state:
268
+
269
+ - [ ] Does the state affect first paint? If yes:
270
+ - [ ] Is the persistence medium readable on the server (cookie, header)? If no, migrate it.
271
+ - [ ] Does the Server Component / loader pass the persisted value as `defaultX` to the Client Component? If no, add it.
272
+ - [ ] Is there a `useEffect` reading the persisted value to update state? If yes, **remove it** (Pattern A makes it redundant).
273
+ - [ ] Does the toggle UI write to the same persistence medium the server reads? (Cookie path/expiry must match.)
274
+ - [ ] Tested on Slow 3G: zero flash on reload?
275
+ - [ ] Tested in incognito (no cookie): falls back to default cleanly?
276
+ - [ ] Tested on mobile (different state model often required).
277
+
278
+ ---
279
+
280
+ ## Real incidents this library prevents
281
+
282
+ - **Sidebar flash open→closed on reload** (Fractus 2026-05-04, T-51): cookie persisted but read in `useEffect`; fix moved read to `cookies()` in Server Component.
283
+ - **Theme flash light→dark on reload** (common pre-`next-themes`): solved by Pattern C blocking script. `next-themes` automates it.
284
+ - **Wrong locale flash for non-English users**: solved by Pattern B reading `Accept-Language`.
285
+
286
+ ---
287
+
288
+ ## References
289
+
290
+ - Next.js docs: [`cookies()`](https://nextjs.org/docs/app/api-reference/functions/cookies), [`headers()`](https://nextjs.org/docs/app/api-reference/functions/headers).
291
+ - shadcn/ui sidebar: [persistence pattern](https://ui.shadcn.com/docs/components/sidebar) — uses Pattern A by default.
292
+ - `next-themes`: source for Pattern C reference implementation.
293
+ - React docs: [hydration mismatches](https://react.dev/reference/react-dom/client/hydrateRoot#hydrating-server-rendered-html).
294
+
295
+ ---
296
+
297
+ ## Cross-references in this framework
298
+
299
+ - Anti-patterns of writing/content: `.gos/libraries/content/ai-writing-patterns.md` (analogous catalog format).
300
+ - (Add `.gos/libraries/frontend/nextjs-patterns.md` and `react-patterns.md` here when ported.)
@@ -41,26 +41,29 @@ Se houver drift, decidir entre:
41
41
  ```
42
42
  *plan <tela>
43
43
 
44
- OBJETIVO = implantacao | correcao | refactor # obrigatório
45
- FIGMA = <url-frame>
46
- FIGMA+ = [<url-comp>, ...] # opcional
47
- NOTAS = """<prosa livre>""" # opcional
48
- ASSIGNEE = <user-id> # opcional, default 112010775 (Douglas)
44
+ OBJETIVO = implantacao | correcao | refactor # obrigatório
45
+ FIGMA = <url-frame>
46
+ FIGMA+ = [<url-comp>, ...] # opcional
47
+ INTERACOES = """<lista estruturada — obrigatório quando tela tem table-clicável/drawer/modal/popup>"""
48
+ NOTAS = """<prosa livre>""" # opcional
49
+ ASSIGNEE = <user-id> # opcional, default 112010775 (Douglas)
49
50
  ```
50
51
 
51
52
  `gos-master` resolve no comprehension gate (não pedir ao usuário):
52
53
  - `PROJETO` (cwd, ou `~/.claude/.gos-state/last-project.json`)
53
54
  - `WORK_BRANCH` (`dev` para app, `feat/storybook` quando em Storybook)
54
55
  - Indexação de `<PROJETO>/docs/regras-de-negocio/` e `docs/postman/` (registrada em `progress.txt`)
56
+ - **Step 1.2 — Coleta de comportamentos**: detecta se tela tem table-clicável/drawer/modal/popup. Sim E `INTERACOES` ausente → bloqueia + dispara `AskUserQuestion` estruturado pedindo as interações (3 exemplos pré-preenchidos). Telas simples (form linear, lista read-only) não acionam.
55
57
 
56
58
  `plan-blueprint` executa:
57
59
  1. Fase 1 — Mapeamento Visual & Componentização
60
+ 1.4 Comportamentos & Overrides — `## Interações & Estados` + `## Page-level overrides` no plano
58
61
  2. Fase 2 — Aderência à Stack (sem redefinir arquitetura)
59
62
  2.5 Fase 2.5 — Backend gaps → criar tasks ClickUp pro Douglas (`--skip-clickup` desliga)
60
- 3. Fase 3 — Plano de Execução
63
+ 3. Fase 3 — Plano de Execução (gera tasks com `interaction_target` e `override_target` para cobrir comportamentos e overrides)
61
64
 
62
65
  Saídas:
63
- - `<dirs.planos>/PLAN-NNN-<slug>/plan.md` (com seções `## Backend pendings` e `## Knowledge mapped`)
66
+ - `<dirs.planos>/PLAN-NNN-<slug>/plan.md` (com seções `## Backend pendings`, `## Knowledge mapped`, `## Interações & Estados`, `## Page-level overrides`)
64
67
  - `<dirs.planos>/PLAN-NNN-<slug>/context.md`
65
68
  - `<dirs.planos>/PLAN-NNN-<slug>/tasks/T-NNN-NN-*.md`
66
69
  - `progress.txt` atualizado com plano ativo + inventário de knowledge + backend pendings
@@ -80,7 +83,10 @@ Se aprovado, prosseguir. Caso contrário: ajustar manualmente ou rerodar `*plan`
80
83
  *execute-plan PLAN-NNN-<slug>
81
84
  ```
82
85
 
83
- A skill `execute-plan` resolve `dirs.storybook`, indexa `.stories.tsx` disponíveis e confronta cada componente da tabela "Componentes mapeados" do plano. Componente sem story → bloqueia e propõe task de criação ANTES das tasks de implementação.
86
+ A skill `execute-plan` faz dois pre-flights antes da T-01:
87
+
88
+ 1. **Stories disponíveis**: resolve `dirs.storybook`, indexa `.stories.tsx`, confronta cada componente da tabela "Componentes mapeados". Componente sem story → bloqueia e propõe task de criação ANTES das tasks de implementação.
89
+ 2. **Visual smoke** (quando Storybook story-da-página OU Playwright MCP disponível): renderiza a página com seed e compara screenshot vs frame Figma em 3 dimensões básicas (seções presentes, layout grosseiro, cores primárias). Gaps grandes viram tasks `T-000-XX` com `priority: P0` antes do loop. Sem Storybook full-page nem Playwright MCP: skip com warning. Esse pre-flight evita o padrão PLAN-005 (gaps grandes descobertos via 26 rodadas de feedback no fim).
84
90
 
85
91
  > Ambiente recomendado: **Codex IDE Extension** (executor). O `*plan` da etapa 2 roda em Opus 4.7 (planejador). Adapter Codex é gerado por `npm run sync:ides`.
86
92
 
@@ -91,28 +97,38 @@ A skill `execute-plan` resolve `dirs.storybook`, indexa `.stories.tsx` disponív
91
97
  1. `*progress status T-NNN-NN em-andamento` (state machine).
92
98
  2. Despacha agent (`labels: [agent:<slug>]`, default `dev`) para implementar.
93
99
  3. **Visual gate** antes de marcar `validacao`:
94
- - Compara cada componente alterado com `<Componente>.stories.tsx` em 4 dimensões (anatomia, tokens, variants, densidade).
100
+ - Compara cada componente alterado com `<Componente>.stories.tsx` em 5 dimensões (anatomia, tokens, variants, densidade, comportamentos). Tokens divergentes registrados em `## Page-level overrides` do plano não falham — gate confirma aplicação do override.
101
+ - Comportamentos: cada `interaction_target:` declarado na task tem handler/estado observável no diff; cada `override_target:` tem classes/props da decisão (a/b/c) presentes.
95
102
  - Cruza JSX da tela com Figma MCP (árvore vs hierarquia).
96
103
  - Falha → task volta a `em-andamento` com diff em `tasks/T-NNN-NN.notes.md`.
97
104
  4. Sucesso → `*progress status T-NNN-NN validacao`.
98
105
 
99
106
  Para rodar uma task específica fora do loop: `*execute-plan PLAN-NNN-<slug> --task T-NNN-NN`.
100
107
 
101
- ### 5. Validação
108
+ ### 4.5. Validação pós-execute
102
109
 
103
- Quando todas as tasks do plano estiverem `validacao` e o checklist do plano estiver completo:
110
+ Após `*execute-plan` retornar (com tasks em `validacao` e/ou `bloqueada-backend`), rodar:
104
111
 
105
112
  ```
106
- *progress status PLAN-NNN-<slug> validacao
113
+ *validate-plan PLAN-NNN-<slug>
114
+
115
+ NOTAS = """<opcional — desvios conhecidos, contexto de QA>"""
107
116
  ```
108
117
 
109
- Validação humana + QA. Se aprovado:
118
+ Skill `validate-plan` (Opus, revisor):
119
+ - Para cada task em `validacao`: re-roda visual gate curto (anatomia + tokens + comportamentos), confronta `git diff --staged` vs Componentes mapeados, confere checklist da task e `interaction_target`/`override_target`. Tudo bate → auto-marca `concluido`. Falha → mantém `validacao` com nota.
120
+ - Para o plano: cobertura de comportamento (`## Interações & Estados`) + cobertura de overrides (`## Page-level overrides`) + checklist do plano + backend pendings ClickUp todos `concluido` → marca `validated_at:` no plan.md + `*progress status PLAN-NNN-<slug> concluido`. Senão → mantém em `validacao`.
121
+ - Tasks `bloqueada-backend` ficam intocadas — backend tem que fechar primeiro.
122
+
123
+ ### 5. Push manual e fechamento
124
+
125
+ `validate-plan` NÃO dá push. Quando o plano fecha 100%:
110
126
 
111
127
  ```
112
- *progress status PLAN-NNN-<slug> concluido
128
+ git push
113
129
  ```
114
130
 
115
- (Status `concluido` SOMENTE após validação. Antes disso, qualquer task/plano fica em `validacao`.)
131
+ Push é ato consciente do humano. Tasks `bloqueada-backend` aguardam ClickUp fechar quando isso acontece, rodar `*progress status T-NNN-NN em-andamento` (state machine valida ClickUp via MCP) e voltar pra etapa 4.
116
132
 
117
133
  ### 6. Commit & resumo
118
134
 
@@ -140,6 +156,7 @@ Validação humana + QA. Se aprovado:
140
156
  - `stack-profiler` — produz/mantém `stack.md`
141
157
  - `plan-blueprint` — cria plano por tela (Opus, planejamento)
142
158
  - `plan-to-tasks` — decompõe plano em tasks (chamada automaticamente)
143
- - `execute-plan` — executa plano com visual gate (Codex IDE, execução)
144
- - `progress-tracker` — gerencia `progress.txt`
159
+ - `execute-plan` — executa plano com visual gate, non-blocking em backend gaps (Codex IDE, execução)
160
+ - `validate-plan` — valida plano pós-execute, auto-marca concluido (Opus, revisor)
161
+ - `progress-tracker` — gerencia `progress.txt` + state machine (incluindo `bloqueada-backend`)
145
162
  - `clickup` — sync opcional para tracking externo (não obrigatório)
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-plan.js — barreira deterministica de criacao de plano.
4
+ *
5
+ * Roda como ULTIMA acao obrigatoria do *plan (apos plan-blueprint +
6
+ * plan-to-tasks). Valida que o plano e usavel pelo *execute-plan e
7
+ * *validate-plan. Falha = *plan nao terminou — usuario ve erro, nao
8
+ * "plano criado" ilusorio.
9
+ *
10
+ * Uso:
11
+ * node check-plan.js <plan-dir>
12
+ *
13
+ * Verificacoes:
14
+ * 1. plan.md existe e tem frontmatter YAML.
15
+ * 2. context.md existe.
16
+ * 3. tasks/ existe e contem >= 1 T-NN*.md.
17
+ * 4. Cada T-NN*.md: head -1 == "---" E frontmatter contem `status: pendente`
18
+ * E `id:`, `plan_id:`, `seq:`, `title:`.
19
+ * 5. Nenhum T-NN*.md tem secao `## Status` no body (formato bugado legado).
20
+ *
21
+ * Exit code:
22
+ * 0 = plano valido, usavel pelo pipeline
23
+ * 1 = falha — mensagem aponta exatamente o que esta errado
24
+ */
25
+
26
+ const fs = require('node:fs');
27
+ const path = require('node:path');
28
+
29
+ const planDir = process.argv[2];
30
+
31
+ if (!planDir) {
32
+ console.error('uso: check-plan.js <plan-dir>');
33
+ process.exit(1);
34
+ }
35
+
36
+ if (!fs.existsSync(planDir)) {
37
+ console.error(`[check-plan] FALHA: plan-dir nao existe: ${planDir}`);
38
+ process.exit(1);
39
+ }
40
+
41
+ const errors = [];
42
+ const warnings = [];
43
+
44
+ const planFile = path.join(planDir, 'plan.md');
45
+ const contextFile = path.join(planDir, 'context.md');
46
+ const tasksDir = path.join(planDir, 'tasks');
47
+
48
+ if (!fs.existsSync(planFile)) {
49
+ errors.push(`plan.md ausente em ${planDir}`);
50
+ } else {
51
+ const plan = fs.readFileSync(planFile, 'utf8');
52
+ if (!plan.startsWith('---\n')) {
53
+ errors.push(`plan.md sem frontmatter YAML (head -1 != "---")`);
54
+ }
55
+ }
56
+
57
+ if (!fs.existsSync(contextFile)) {
58
+ warnings.push(`context.md ausente — recomendado, nao bloqueia`);
59
+ }
60
+
61
+ if (!fs.existsSync(tasksDir)) {
62
+ errors.push(`tasks/ ausente em ${planDir} — *plan deve invocar plan-to-tasks`);
63
+ } else {
64
+ const taskFiles = fs.readdirSync(tasksDir)
65
+ .filter((f) => /^T-\d+.*\.md$/.test(f))
66
+ .map((f) => path.join(tasksDir, f));
67
+
68
+ if (taskFiles.length === 0) {
69
+ errors.push(`tasks/ vazio — nenhum T-NN*.md encontrado`);
70
+ }
71
+
72
+ for (const file of taskFiles) {
73
+ const rel = path.relative(planDir, file);
74
+ const raw = fs.readFileSync(file, 'utf8');
75
+
76
+ if (!raw.startsWith('---\n')) {
77
+ errors.push(`${rel}: sem frontmatter YAML (head -1 != "---")`);
78
+ continue;
79
+ }
80
+
81
+ const fmEnd = raw.indexOf('\n---', 4);
82
+ if (fmEnd === -1) {
83
+ errors.push(`${rel}: frontmatter aberto sem fechar ("---" final ausente)`);
84
+ continue;
85
+ }
86
+ const frontmatter = raw.slice(4, fmEnd);
87
+ const body = raw.slice(fmEnd + 4);
88
+
89
+ const required = ['id', 'plan_id', 'seq', 'title', 'status'];
90
+ for (const key of required) {
91
+ if (!new RegExp(`^${key}:\\s*\\S`, 'm').test(frontmatter)) {
92
+ errors.push(`${rel}: frontmatter sem campo \`${key}:\``);
93
+ }
94
+ }
95
+
96
+ if (!/^status:\s*pendente\s*$/m.test(frontmatter)) {
97
+ errors.push(`${rel}: frontmatter status: deve ser exatamente "pendente" no plano recem-criado`);
98
+ }
99
+
100
+ if (/^## Status\s*$/m.test(body)) {
101
+ errors.push(`${rel}: contem secao "## Status" no body (formato legado bugado) — rodar migrate-task-status.js`);
102
+ }
103
+ }
104
+ }
105
+
106
+ if (warnings.length) {
107
+ for (const w of warnings) console.error(`[check-plan] aviso: ${w}`);
108
+ }
109
+
110
+ if (errors.length) {
111
+ console.error(`\n[check-plan] FALHA — plano NAO usavel pelo *execute-plan:\n`);
112
+ for (const e of errors) console.error(` - ${e}`);
113
+ console.error(`\nproximo passo: regerar tasks via plan-to-tasks OU rodar migrate-task-status.js ${planDir}`);
114
+ process.exit(1);
115
+ }
116
+
117
+ const taskCount = fs.readdirSync(tasksDir)
118
+ .filter((f) => /^T-\d+.*\.md$/.test(f)).length;
119
+ console.log(`[check-plan] OK — ${planDir}: plan.md + context.md + ${taskCount} tasks (todas com frontmatter status: pendente).`);
120
+ process.exit(0);