@polymorphism-tech/morph-spec 4.8.14 → 4.8.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +23 -2
  3. package/bin/task-manager.js +202 -14
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/agents.json +113 -116
  8. package/framework/hooks/claude-code/post-tool-use/dispatch.js +48 -2
  9. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +151 -0
  10. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +6 -0
  11. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +6 -0
  12. package/framework/hooks/claude-code/session-start/inject-morph-context.js +27 -0
  13. package/framework/hooks/claude-code/stop/validate-completion.js +17 -2
  14. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +87 -0
  15. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +58 -0
  16. package/framework/hooks/shared/phase-utils.js +1 -1
  17. package/framework/hooks/shared/state-reader.js +1 -0
  18. package/framework/skills/README.md +1 -0
  19. package/framework/skills/level-0-meta/brainstorming/SKILL.md +2 -0
  20. package/framework/skills/level-0-meta/code-review/SKILL.md +16 -0
  21. package/framework/skills/level-0-meta/code-review/references/review-guidelines.md +100 -0
  22. package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +36 -6
  23. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +16 -0
  24. package/framework/skills/level-0-meta/code-review-nextjs/scripts/scan-nextjs.mjs +189 -0
  25. package/framework/skills/level-0-meta/frontend-review/SKILL.md +359 -0
  26. package/framework/skills/level-0-meta/frontend-review/scripts/scan-accessibility.mjs +376 -0
  27. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +1 -1
  28. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +10 -8
  29. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +70 -0
  30. package/framework/skills/level-0-meta/post-implementation/SKILL.md +315 -0
  31. package/framework/skills/level-0-meta/post-implementation/scripts/detect-dev-server.mjs +153 -0
  32. package/framework/skills/level-0-meta/post-implementation/scripts/detect-stack.mjs +234 -0
  33. package/framework/skills/level-0-meta/terminal-title/SKILL.md +61 -0
  34. package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +65 -0
  35. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +13 -206
  36. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +213 -0
  37. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +2 -0
  38. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +4 -7
  39. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
  40. package/framework/skills/level-1-workflows/phase-design/SKILL.md +16 -110
  41. package/framework/skills/level-1-workflows/phase-design/references/architecture-analysis-guide.md +89 -0
  42. package/framework/skills/level-1-workflows/phase-design/references/spec-authoring-guide.md +55 -0
  43. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +153 -118
  44. package/framework/skills/level-1-workflows/phase-implement/references/vsa-implementation-guide.md +92 -0
  45. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -2
  46. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +11 -158
  47. package/framework/skills/level-1-workflows/phase-tasks/references/task-planning-patterns.md +172 -0
  48. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +42 -3
  49. package/framework/squad-templates/backend-only.json +14 -1
  50. package/framework/squad-templates/frontend-only.json +14 -1
  51. package/framework/squad-templates/full-stack.json +25 -8
  52. package/framework/standards/STANDARDS.json +631 -86
  53. package/framework/standards/frontend/design-system/aesthetic-direction.md +213 -0
  54. package/framework/templates/project/validate.js +122 -0
  55. package/framework/workflows/configs/zero-touch.json +7 -0
  56. package/package.json +1 -1
  57. package/src/commands/agents/dispatch-agents.js +53 -10
  58. package/src/commands/state/advance-phase.js +56 -0
  59. package/src/commands/state/index.js +2 -1
  60. package/src/commands/state/phase-runner.js +215 -0
  61. package/src/commands/tasks/task.js +23 -2
  62. package/src/core/paths/output-schema.js +1 -1
  63. package/src/lib/generators/recap-generator.js +16 -0
  64. package/src/lib/orchestration/team-orchestrator.js +171 -89
  65. package/src/lib/phase-chain/eligibility-checker.js +243 -0
  66. package/src/lib/standards/digest-builder.js +231 -0
  67. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +39 -0
  68. package/src/lib/validators/nextjs/next-component-validator.js +2 -0
  69. package/src/lib/validators/validation-runner.js +2 -2
  70. package/src/utils/file-copier.js +1 -0
  71. package/src/utils/hooks-installer.js +31 -7
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * scan-nextjs.mjs
4
+ *
5
+ * Scans .tsx/.ts files for MORPH-SPEC Next.js coding standard violations.
6
+ * Executed locally by Claude via Bash — output returned to Claude.
7
+ *
8
+ * Usage:
9
+ * node scan-nextjs.mjs [path] # scan directory or file
10
+ * node scan-nextjs.mjs src/ # scan src/ recursively
11
+ * node scan-nextjs.mjs --summary # counts only, no details
12
+ * node scan-nextjs.mjs --diff # scan only changed files (git diff main...HEAD)
13
+ * node scan-nextjs.mjs --diff --base=develop # use different base branch
14
+ *
15
+ * Checks (ref: framework/standards/frontend/nextjs/):
16
+ * - Non-PascalCase component files → MyComponent.tsx, not myComponent.tsx
17
+ * - Explicit `any` type annotations → avoid untyped code
18
+ * - useEffect with fetch inside → use TanStack Query instead
19
+ * - Importing from components/ui/ → use shadcn/ui without modification
20
+ * - useMutation calling fetch directly → use api client utilities
21
+ */
22
+
23
+ import { readdirSync, readFileSync, statSync, existsSync } from 'fs';
24
+ import { join, extname, basename, relative } from 'path';
25
+ import { execSync } from 'child_process';
26
+
27
+ const args = process.argv.slice(2);
28
+ const diffMode = args.includes('--diff');
29
+ const summaryOnly = args.includes('--summary');
30
+ const baseBranch = args.find(a => a.startsWith('--base='))?.split('=')[1] ?? 'main';
31
+ const targetPath = args.find(a => !a.startsWith('--')) ?? '.';
32
+
33
+ const CHECKS = [
34
+ {
35
+ id: 'NON_PASCAL_CASE_COMPONENT',
36
+ severity: 'CRITICAL',
37
+ fileLevel: true,
38
+ check: (filePath) => {
39
+ const name = basename(filePath, extname(filePath));
40
+ // Only check files that look like components (exclude lowercase utils/hooks/types)
41
+ if (!filePath.endsWith('.tsx')) return null;
42
+ // Skip files starting with lowercase (could be utils)
43
+ if (/^[a-z]/.test(name) && !name.includes('-') && !name.includes('.')) return null;
44
+ // Flag kebab-case or non-PascalCase component files
45
+ if (name.includes('-') || /^[a-z]/.test(name)) {
46
+ return `Component file '${name}.tsx' is not PascalCase → rename to '${toPascalCase(name)}.tsx'`;
47
+ }
48
+ return null;
49
+ },
50
+ },
51
+ {
52
+ id: 'EXPLICIT_ANY_TYPE',
53
+ severity: 'CRITICAL',
54
+ pattern: /:\s*any\b(?!\s*\[)/g,
55
+ message: (m, line) => `Explicit 'any' type annotation on line ${line} → use proper TypeScript type`,
56
+ },
57
+ {
58
+ id: 'USE_EFFECT_FETCH',
59
+ severity: 'CRITICAL',
60
+ pattern: /useEffect\s*\(\s*\(\s*\)\s*=>\s*\{[^}]*fetch\s*\(/gs,
61
+ message: () => 'useEffect with fetch() inside → use TanStack Query (useQuery/useMutation) instead',
62
+ },
63
+ {
64
+ id: 'COMPONENTS_UI_IMPORT_MODIFIED',
65
+ severity: 'CRITICAL',
66
+ pattern: /from\s+['"](?:@\/|\.\.\/)*components\/ui\/(\w+)['"]/g,
67
+ message: (m) => `Import from 'components/ui/${m[1]}' — do not modify shadcn/ui components directly; extend via composition instead`,
68
+ },
69
+ {
70
+ id: 'USE_MUTATION_FETCH',
71
+ severity: 'HIGH',
72
+ pattern: /useMutation\s*\(\s*\{[^}]*mutationFn[^}]*fetch\s*\(/gs,
73
+ message: () => 'useMutation with direct fetch() call → use a typed API client utility function instead',
74
+ },
75
+ ];
76
+
77
+ function toPascalCase(str) {
78
+ return str
79
+ .replace(/-([a-z])/g, (_, c) => c.toUpperCase())
80
+ .replace(/^[a-z]/, (c) => c.toUpperCase());
81
+ }
82
+
83
+ function collectTsFiles(dir) {
84
+ if (!existsSync(dir)) return [];
85
+ const stat = statSync(dir);
86
+ if (stat.isFile()) {
87
+ const ext = extname(dir);
88
+ return ext === '.tsx' || ext === '.ts' ? [dir] : [];
89
+ }
90
+ const results = [];
91
+ const ignored = ['node_modules', '.git', '.next', 'dist', 'build', '.turbo'];
92
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
93
+ if (entry.isDirectory() && !ignored.includes(entry.name)) {
94
+ results.push(...collectTsFiles(join(dir, entry.name)));
95
+ } else if (entry.isFile()) {
96
+ const ext = extname(entry.name);
97
+ if (ext === '.tsx' || ext === '.ts') {
98
+ results.push(join(dir, entry.name));
99
+ }
100
+ }
101
+ }
102
+ return results;
103
+ }
104
+
105
+ function getChangedFiles(base) {
106
+ try {
107
+ const raw = execSync(`git diff --name-only ${base}...HEAD`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
108
+ return raw.split('\n')
109
+ .map(f => f.trim())
110
+ .filter(f => (f.endsWith('.ts') || f.endsWith('.tsx')) && existsSync(f));
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ let mode = 'full';
117
+ let files;
118
+ if (diffMode) {
119
+ const changed = getChangedFiles(baseBranch);
120
+ if (changed !== null) {
121
+ files = changed;
122
+ mode = 'diff';
123
+ } else {
124
+ files = collectTsFiles(targetPath);
125
+ }
126
+ } else {
127
+ files = collectTsFiles(targetPath);
128
+ }
129
+
130
+ const findings = [];
131
+
132
+ for (const file of files) {
133
+ const content = readFileSync(file, 'utf-8');
134
+ const lines = content.split('\n');
135
+ const relFile = relative(process.cwd(), file).replace(/\\/g, '/');
136
+
137
+ for (const check of CHECKS) {
138
+ if (check.fileLevel) {
139
+ const msg = check.check(file);
140
+ if (msg) {
141
+ findings.push({
142
+ severity: check.severity,
143
+ id: check.id,
144
+ file: relFile,
145
+ line: 1,
146
+ message: msg,
147
+ snippet: '',
148
+ });
149
+ }
150
+ continue;
151
+ }
152
+
153
+ const regex = new RegExp(check.pattern.source, check.pattern.flags);
154
+ let match;
155
+ while ((match = regex.exec(content)) !== null) {
156
+ const lineNum = content.slice(0, match.index).split('\n').length;
157
+ const msg = check.message(match, lineNum);
158
+ if (!msg) continue;
159
+ findings.push({
160
+ severity: check.severity,
161
+ id: check.id,
162
+ file: relFile,
163
+ line: lineNum,
164
+ message: msg,
165
+ snippet: lines[lineNum - 1]?.trim().slice(0, 100),
166
+ });
167
+ }
168
+ }
169
+ }
170
+
171
+ const bySeverity = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
172
+ for (const f of findings) bySeverity[f.severity] = (bySeverity[f.severity] ?? 0) + 1;
173
+
174
+ const summary = {
175
+ scanned: files.length,
176
+ findings: findings.length,
177
+ bySeverity,
178
+ clean: findings.length === 0,
179
+ mode,
180
+ ...(mode === 'diff' && { baseBranch, changedFilesCount: files.length }),
181
+ };
182
+
183
+ if (summaryOnly) {
184
+ console.log(JSON.stringify(summary, null, 2));
185
+ } else {
186
+ console.log(JSON.stringify({ summary, findings }, null, 2));
187
+ }
188
+
189
+ process.exit(findings.some(f => f.severity === 'CRITICAL') ? 1 : 0);
@@ -0,0 +1,359 @@
1
+ ---
2
+ name: frontend-review
3
+ description: Auditoria completa do frontend — valida design outputs (design-system, contraste WCAG,
4
+ mockups), scan estático de acessibilidade (TSX + Razor), screenshots responsivos em 3
5
+ breakpoints via Playwright, axe-core a11y em runtime, e SEO (Next.js metadata). Cobre Next.js
6
+ e Blazor. Disparado ao final do phase-uiux e pós-implementação.
7
+ argument-hint: "[feature-name]"
8
+ user-invocable: true
9
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep,
10
+ mcp__playwright__browser_navigate, mcp__playwright__browser_resize,
11
+ mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot,
12
+ mcp__playwright__browser_evaluate, mcp__playwright__browser_console_messages
13
+ cliVersion: "4.8.14"
14
+ ---
15
+
16
+ # Frontend Review
17
+
18
+ > Auditoria completa do frontend: design outputs, acessibilidade estática + runtime, screenshots
19
+ > responsivos e SEO. Disparado ao final do `phase-uiux` (valida design) e pós-implementação
20
+ > (valida código implementado).
21
+
22
+ > **Nota sobre MCP:** O prefixo `mcp__playwright__` corresponde ao nome do servidor Playwright
23
+ > conforme configurado no `settings.json` do projeto. Se o servidor tiver nome diferente (ex:
24
+ > `plugin_playwright_playwright`), ajuste o prefixo nos calls abaixo.
25
+
26
+ ---
27
+
28
+ ## Step 0 — Detectar Contexto e Stack
29
+
30
+ ```bash
31
+ node .claude/skills/post-implementation/scripts/detect-stack.mjs
32
+ ```
33
+
34
+ Output JSON: `{ stack: "DOTNET" | "NEXTJS" | "FULLSTACK" | "UNKNOWN", frontendPath, backendPath, startCommand }`
35
+
36
+ Além do stack, determinar o **contexto** examinando o que existe:
37
+
38
+ ```bash
39
+ # Verificar se design outputs existem
40
+ ls .morph/features/$ARGUMENTS/2-ui/ 2>/dev/null
41
+
42
+ # Verificar se arquivos frontend implementados existem
43
+ find . -name "*.tsx" -o -name "*.razor" 2>/dev/null | head -5
44
+ ```
45
+
46
+ - Se `.morph/features/$ARGUMENTS/2-ui/ui-design-system.md` existe → contexto inclui **design validation**
47
+ - Se arquivos frontend implementados existem (`.tsx`, `.razor`) → contexto inclui **implementation validation**
48
+ - Ambos podem coexistir
49
+
50
+ Guarde `stack`, `frontendPath`, `startCommand` e o contexto detectado — todos os passos dependem deles.
51
+
52
+ ---
53
+
54
+ ## Step 1 — Validação de Design Outputs (se contexto inclui design validation)
55
+
56
+ ```bash
57
+ npx morph-spec validate-feature $ARGUMENTS --phase uiux
58
+ ```
59
+
60
+ Verificar existência e qualidade dos 4 outputs obrigatórios em `.morph/features/$ARGUMENTS/2-ui/`:
61
+
62
+ ```
63
+ [ ] ui-design-system.md — paleta de cores, tokens de tipografia, espaçamento
64
+ [ ] ui-mockups.md — wireframes com estados responsivos
65
+ [ ] ui-components.md — specs técnicas dos componentes
66
+ [ ] ui-flows.md — jornadas de usuário e edge cases
67
+ [ ] Contraste WCAG AA: cores primárias e de texto ≥ 4.5:1 (verificar ui-design-system.md)
68
+ [ ] Touch targets documentados: ≥ 44x44px para mobile
69
+ [ ] Estados de loading/error/empty definidos nos mockups
70
+ ```
71
+
72
+ **🚫 BLOCK** se `validate-feature` falhar ou outputs obrigatórios ausentes.
73
+
74
+ Leia `ui-design-system.md` e verifique manualmente se as cores primária e de texto têm razão de
75
+ contraste ≥ 4.5:1. Ferramentas de referência: WebAIM Contrast Checker (critérios WCAG 2.1 AA).
76
+
77
+ ---
78
+
79
+ ## Step 2 — Scan Estático de Acessibilidade
80
+
81
+ ```bash
82
+ node .claude/skills/frontend-review/scripts/scan-accessibility.mjs $frontendPath
83
+ ```
84
+
85
+ O script detecta automaticamente a extensão dos arquivos para determinar Next.js (`.tsx`) vs
86
+ Blazor (`.razor`). Se `frontendPath` não estiver definido, o script usa `.` (raiz do projeto).
87
+
88
+ **Checks implementados:**
89
+
90
+ | ID | Severidade | Arquivos | Verifica |
91
+ |----|-----------|---------|---------|
92
+ | `IMG_MISSING_ALT` | CRITICAL | .tsx, .razor | `<img` sem atributo `alt=` |
93
+ | `INPUT_MISSING_LABEL` | HIGH | .tsx, .razor | `<input`/`<InputText`/`<FluentTextField` sem `aria-label`, `aria-labelledby`, ou `<label for=` associado |
94
+ | `HEADING_HIERARCHY` | HIGH | .tsx, .razor | Salto de nível em headings (H1→H3 pulando H2) |
95
+ | `LINK_NO_TEXT` | HIGH | .tsx, .razor | `<a` vazio ou com apenas ícone e sem `aria-label` |
96
+ | `BUTTON_NO_TEXT` | HIGH | .tsx, .razor | `<button`/`<FluentButton` sem texto e sem `aria-label` |
97
+ | `AUTOPLAY_MEDIA` | MEDIUM | .tsx, .razor | `<video`/`<audio` com `autoPlay` sem `muted` |
98
+ | `FLUENT_CHECKBOX_NO_LABEL` | MEDIUM | .razor | `<FluentCheckbox` sem `Label=` |
99
+
100
+ **🚫 BLOCK** se qualquer finding CRITICAL ou count de HIGH ≥ 3.
101
+
102
+ Corrija todos os CRITICALs antes de continuar. Para HIGH < 3, registre no resumo como warnings.
103
+
104
+ ---
105
+
106
+ ## Step 3 — Scan de Código Frontend (se contexto inclui implementation validation)
107
+
108
+ ### Se NEXTJS ou FULLSTACK:
109
+
110
+ ```bash
111
+ node .claude/skills/code-review-nextjs/scripts/scan-nextjs.mjs $frontendPath
112
+ ```
113
+
114
+ > Pular se já executado pelo `post-implementation` na mesma sessão.
115
+
116
+ **🚫 BLOCK** se CRITICAL findings encontrados.
117
+
118
+ ### Se DOTNET ou FULLSTACK (Blazor):
119
+
120
+ Verificar manualmente as categorias principais do checklist para o feature implementado:
121
+
122
+ ```
123
+ [ ] Componentes Blazor com parâmetros tipados corretamente
124
+ [ ] EventCallback<T> para eventos — sem Action/Func diretos
125
+ [ ] Dispose implementado onde há subscriptions (IDisposable)
126
+ [ ] Sem lógica de negócio nos arquivos .razor (separar em @code ou classes)
127
+ [ ] StateHasChanged() chamado apenas quando necessário
128
+ [ ] Formulários com EditForm e DataAnnotationsValidator
129
+ [ ] Campos obrigatórios com [Required] e mensagens de erro definidas
130
+ ```
131
+
132
+ **🚫 BLOCK** se qualquer item CRITICAL for encontrado.
133
+
134
+ ---
135
+
136
+ ## Step 4 — Screenshots Responsivos (Playwright MCP)
137
+
138
+ ### 4a. Detectar Dev Server
139
+
140
+ ```bash
141
+ node .claude/skills/post-implementation/scripts/detect-dev-server.mjs "$startCommand"
142
+ ```
143
+
144
+ ### 4b. Se dev server disponível (exit 0):
145
+
146
+ Extraia as URLs principais da feature lendo `spec.md`:
147
+
148
+ ```bash
149
+ Read: .morph/features/$ARGUMENTS/1-design/spec.md
150
+ ```
151
+
152
+ Para cada página principal (máximo 3), capture screenshots nos 3 breakpoints:
153
+
154
+ ```javascript
155
+ // Mobile (375px)
156
+ await mcp__playwright__browser_resize({ width: 375, height: 812 });
157
+ await mcp__playwright__browser_navigate({ url: '<url-da-feature>' });
158
+ await mcp__playwright__browser_take_screenshot({
159
+ type: 'png',
160
+ filename: `.morph/features/${ARGUMENTS}/visual-screenshots/mobile-<page>.png`
161
+ });
162
+
163
+ // Tablet (768px)
164
+ await mcp__playwright__browser_resize({ width: 768, height: 1024 });
165
+ await mcp__playwright__browser_navigate({ url: '<url-da-feature>' });
166
+ await mcp__playwright__browser_take_screenshot({
167
+ type: 'png',
168
+ filename: `.morph/features/${ARGUMENTS}/visual-screenshots/tablet-<page>.png`
169
+ });
170
+
171
+ // Desktop (1440px)
172
+ await mcp__playwright__browser_resize({ width: 1440, height: 900 });
173
+ await mcp__playwright__browser_navigate({ url: '<url-da-feature>' });
174
+ await mcp__playwright__browser_take_screenshot({
175
+ type: 'png',
176
+ filename: `.morph/features/${ARGUMENTS}/visual-screenshots/desktop-<page>.png`
177
+ });
178
+ ```
179
+
180
+ Screenshots salvos em: `.morph/features/$ARGUMENTS/visual-screenshots/`
181
+ (funciona tanto na fase `2-ui` quanto na `4-implement`)
182
+
183
+ ### 4c. Se dev server NÃO disponível (exit 1):
184
+
185
+ **⚠️ ATENÇÃO: Dev server não encontrado.**
186
+
187
+ Solicite confirmação explícita ao usuário antes de pular os screenshots:
188
+
189
+ ```
190
+ Dev server não detectado. Screenshots responsivos via Playwright não podem ser capturados.
191
+
192
+ Opções:
193
+ 1. Inicie manualmente o servidor e re-execute /frontend-review
194
+ 2. Confirme explicitamente que deseja pular os screenshots e por quê
195
+
196
+ Não é possível prosseguir para axe-core sem dev server.
197
+ ```
198
+
199
+ **Aguarde resposta antes de continuar.**
200
+
201
+ ---
202
+
203
+ ## Step 5 — axe-core Runtime (Playwright MCP, se dev server ativo)
204
+
205
+ Para cada página da feature (usar as mesmas URLs do Step 4):
206
+
207
+ ```javascript
208
+ // Navegar para a página
209
+ await mcp__playwright__browser_navigate({ url: '<url-da-feature>' });
210
+
211
+ // Injetar axe-core via CDN e executar scan WCAG 2.1 AA
212
+ await mcp__playwright__browser_evaluate({
213
+ function: `async () => {
214
+ if (!window.axe) {
215
+ await new Promise((resolve, reject) => {
216
+ const s = document.createElement('script');
217
+ s.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.9.1/axe.min.js';
218
+ s.onload = resolve;
219
+ s.onerror = reject;
220
+ document.head.appendChild(s);
221
+ });
222
+ }
223
+ const results = await window.axe.run(document, {
224
+ runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'] }
225
+ });
226
+ return results.violations.map(v => ({
227
+ id: v.id,
228
+ impact: v.impact,
229
+ description: v.description,
230
+ nodes: v.nodes.length,
231
+ helpUrl: v.helpUrl
232
+ }));
233
+ }`
234
+ });
235
+ ```
236
+
237
+ Agrupar violations por impacto:
238
+ - `critical` / `serious` → **🚫 BLOCK** (não criar PR)
239
+ - `moderate` / `minor` → reportar como warnings no resumo, não bloqueiam
240
+
241
+ Verificar também erros de console relacionados a acessibilidade:
242
+
243
+ ```javascript
244
+ await mcp__playwright__browser_console_messages({ level: 'error' });
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Step 6 — SEO Check (Next.js only)
250
+
251
+ Varrer arquivos `page.tsx` da feature para verificar metadata:
252
+
253
+ ```bash
254
+ # Buscar páginas da feature
255
+ find . -name "page.tsx" -path "*$ARGUMENTS*" 2>/dev/null
256
+
257
+ # Verificar se têm metadata
258
+ grep -l "export const metadata\|export async function generateMetadata" $(find . -name "page.tsx" -path "*$ARGUMENTS*" 2>/dev/null)
259
+ ```
260
+
261
+ Checklist por página:
262
+
263
+ ```
264
+ [ ] export const metadata com title e description
265
+ [ ] og:title e og:description (Open Graph)
266
+ [ ] og:image definida (para compartilhamento social)
267
+ [ ] Sem título duplicado entre páginas
268
+ [ ] robots não bloqueia indexação das páginas públicas
269
+ ```
270
+
271
+ > SEO não bloqueia PR — reportar como warnings no resumo final.
272
+
273
+ ---
274
+
275
+ ## Step 7 — Checklist de Design Responsivo
276
+
277
+ Após visualizar os screenshots do Step 4:
278
+
279
+ ```
280
+ [ ] Mobile (375px): sem scroll horizontal, botões ≥ 44px, texto legível
281
+ [ ] Tablet (768px): layout adapta (sidebar colapsa, grid ajusta colunas)
282
+ [ ] Desktop (1440px): aproveita espaço disponível, sem conteúdo esticado
283
+ [ ] Navegação funciona em todos os breakpoints
284
+ [ ] Formulários acessíveis em mobile (labels visíveis, inputs grandes o suficiente)
285
+ ```
286
+
287
+ Se screenshots não foram capturados (dev server indisponível), marcar como "não verificado" e
288
+ registrar no resumo.
289
+
290
+ ---
291
+
292
+ ## Step 8 — Resumo e Artefatos
293
+
294
+ Criar arquivo de resumo consolidado:
295
+
296
+ ```bash
297
+ mkdir -p ".morph/features/$ARGUMENTS/visual-screenshots"
298
+ ```
299
+
300
+ Salvar em `.morph/features/$ARGUMENTS/visual-screenshots/frontend-review-summary.md`:
301
+
302
+ ```markdown
303
+ # Frontend Review — {feature}
304
+
305
+ **Data:** {data}
306
+ **Stack:** {stack detectado}
307
+ **Contexto:** design validation | implementation validation | ambos
308
+
309
+ ## Resultados
310
+
311
+ ### Step 1 — Design Outputs
312
+ - validate-feature: ✅ PASS / 🚫 BLOCK
313
+ - Outputs verificados: ui-design-system.md, ui-mockups.md, ui-components.md, ui-flows.md
314
+ - Contraste WCAG: {resultado}
315
+
316
+ ### Step 2 — Scan de Acessibilidade Estática
317
+ - Arquivos escaneados: {N}
318
+ - Findings: {total} ({CRITICAL: N, HIGH: N, MEDIUM: N})
319
+ - Status: ✅ PASS / 🚫 BLOCK
320
+
321
+ ### Step 3 — Scan de Código Frontend
322
+ - Status: ✅ PASS / 🚫 BLOCK / ⏭️ pulado (já executado)
323
+
324
+ ### Step 4 — Screenshots Responsivos
325
+ - Mobile (375px): {paths ou "não capturado"}
326
+ - Tablet (768px): {paths ou "não capturado"}
327
+ - Desktop (1440px): {paths ou "não capturado"}
328
+
329
+ ### Step 5 — axe-core Runtime
330
+ - Violations críticas/sérias: {N} → ✅ / 🚫
331
+ - Warnings (moderate/minor): {N}
332
+
333
+ ### Step 6 — SEO (Next.js)
334
+ - Páginas com metadata: {N}/{total}
335
+ - Gaps: {lista ou "nenhum"}
336
+
337
+ ## Status Geral
338
+
339
+ **{✅ PASS / 🚫 BLOCK}**
340
+
341
+ {Se BLOCK: motivo(s) listados aqui}
342
+ ```
343
+
344
+ ---
345
+
346
+ ## Resumo dos BLOCKs
347
+
348
+ | Step | Condição de BLOCK |
349
+ |------|-------------------|
350
+ | Step 1 | `validate-feature` falhou ou outputs obrigatórios ausentes |
351
+ | Step 2 | Qualquer finding CRITICAL ou HIGH ≥ 3 no scan estático |
352
+ | Step 3 | Qualquer finding CRITICAL no scan de código |
353
+ | Step 5 | Dev server ativo + axe-core violations `critical` ou `serious` |
354
+
355
+ **Todos os BLOCKs devem ser resolvidos antes de criar o PR (quando executado pós-implementação).**
356
+
357
+ ---
358
+
359
+ *MORPH-SPEC by Polymorphism Tech*