@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.
- package/README.md +2 -2
- package/bin/morph-spec.js +23 -2
- package/bin/task-manager.js +202 -14
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents.json +113 -116
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +48 -2
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +151 -0
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +6 -0
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +6 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +27 -0
- package/framework/hooks/claude-code/stop/validate-completion.js +17 -2
- package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +87 -0
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +58 -0
- package/framework/hooks/shared/phase-utils.js +1 -1
- package/framework/hooks/shared/state-reader.js +1 -0
- package/framework/skills/README.md +1 -0
- package/framework/skills/level-0-meta/brainstorming/SKILL.md +2 -0
- package/framework/skills/level-0-meta/code-review/SKILL.md +16 -0
- package/framework/skills/level-0-meta/code-review/references/review-guidelines.md +100 -0
- package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +36 -6
- package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +16 -0
- package/framework/skills/level-0-meta/code-review-nextjs/scripts/scan-nextjs.mjs +189 -0
- package/framework/skills/level-0-meta/frontend-review/SKILL.md +359 -0
- package/framework/skills/level-0-meta/frontend-review/scripts/scan-accessibility.mjs +376 -0
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +1 -1
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +10 -8
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +70 -0
- package/framework/skills/level-0-meta/post-implementation/SKILL.md +315 -0
- package/framework/skills/level-0-meta/post-implementation/scripts/detect-dev-server.mjs +153 -0
- package/framework/skills/level-0-meta/post-implementation/scripts/detect-stack.mjs +234 -0
- package/framework/skills/level-0-meta/terminal-title/SKILL.md +61 -0
- package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +65 -0
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +13 -206
- package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +213 -0
- package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +2 -0
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +4 -7
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +16 -110
- package/framework/skills/level-1-workflows/phase-design/references/architecture-analysis-guide.md +89 -0
- package/framework/skills/level-1-workflows/phase-design/references/spec-authoring-guide.md +55 -0
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +153 -118
- package/framework/skills/level-1-workflows/phase-implement/references/vsa-implementation-guide.md +92 -0
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -2
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +11 -158
- package/framework/skills/level-1-workflows/phase-tasks/references/task-planning-patterns.md +172 -0
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +42 -3
- package/framework/squad-templates/backend-only.json +14 -1
- package/framework/squad-templates/frontend-only.json +14 -1
- package/framework/squad-templates/full-stack.json +25 -8
- package/framework/standards/STANDARDS.json +631 -86
- package/framework/standards/frontend/design-system/aesthetic-direction.md +213 -0
- package/framework/templates/project/validate.js +122 -0
- package/framework/workflows/configs/zero-touch.json +7 -0
- package/package.json +1 -1
- package/src/commands/agents/dispatch-agents.js +53 -10
- package/src/commands/state/advance-phase.js +56 -0
- package/src/commands/state/index.js +2 -1
- package/src/commands/state/phase-runner.js +215 -0
- package/src/commands/tasks/task.js +23 -2
- package/src/core/paths/output-schema.js +1 -1
- package/src/lib/generators/recap-generator.js +16 -0
- package/src/lib/orchestration/team-orchestrator.js +171 -89
- package/src/lib/phase-chain/eligibility-checker.js +243 -0
- package/src/lib/standards/digest-builder.js +231 -0
- package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +39 -0
- package/src/lib/validators/nextjs/next-component-validator.js +2 -0
- package/src/lib/validators/validation-runner.js +2 -2
- package/src/utils/file-copier.js +1 -0
- 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*
|