@polymorphism-tech/morph-spec 4.8.12 → 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 (76) hide show
  1. package/README.md +379 -379
  2. package/bin/morph-spec.js +23 -2
  3. package/bin/{task-manager.cjs → task-manager.js} +249 -172
  4. package/claude-plugin.json +14 -14
  5. package/docs/CHEATSHEET.md +203 -203
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/agents.json +224 -140
  8. package/framework/hooks/README.md +202 -202
  9. package/framework/hooks/claude-code/post-tool-use/dispatch.js +48 -2
  10. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +151 -0
  11. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +12 -0
  12. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +6 -0
  13. package/framework/hooks/claude-code/session-start/inject-morph-context.js +34 -0
  14. package/framework/hooks/claude-code/statusline.py +6 -0
  15. package/framework/hooks/claude-code/stop/validate-completion.js +38 -4
  16. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +87 -0
  17. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +58 -0
  18. package/framework/hooks/shared/phase-utils.js +4 -1
  19. package/framework/hooks/shared/state-reader.js +1 -0
  20. package/framework/skills/README.md +1 -0
  21. package/framework/skills/level-0-meta/brainstorming/SKILL.md +2 -0
  22. package/framework/skills/level-0-meta/code-review/SKILL.md +16 -0
  23. package/framework/skills/level-0-meta/code-review/references/review-guidelines.md +100 -0
  24. package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +36 -6
  25. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +16 -0
  26. package/framework/skills/level-0-meta/code-review-nextjs/scripts/scan-nextjs.mjs +189 -0
  27. package/framework/skills/level-0-meta/frontend-review/SKILL.md +359 -0
  28. package/framework/skills/level-0-meta/frontend-review/scripts/scan-accessibility.mjs +376 -0
  29. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +1 -1
  30. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +10 -8
  31. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +70 -0
  32. package/framework/skills/level-0-meta/post-implementation/SKILL.md +315 -0
  33. package/framework/skills/level-0-meta/post-implementation/scripts/detect-dev-server.mjs +153 -0
  34. package/framework/skills/level-0-meta/post-implementation/scripts/detect-stack.mjs +234 -0
  35. package/framework/skills/level-0-meta/terminal-title/SKILL.md +61 -0
  36. package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +65 -0
  37. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +50 -188
  38. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +213 -0
  39. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +2 -0
  40. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +4 -7
  41. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
  42. package/framework/skills/level-1-workflows/phase-design/SKILL.md +71 -109
  43. package/framework/skills/level-1-workflows/phase-design/references/architecture-analysis-guide.md +89 -0
  44. package/framework/skills/level-1-workflows/phase-design/references/spec-authoring-guide.md +55 -0
  45. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +171 -114
  46. package/framework/skills/level-1-workflows/phase-implement/references/vsa-implementation-guide.md +92 -0
  47. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -2
  48. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +35 -159
  49. package/framework/skills/level-1-workflows/phase-tasks/references/task-planning-patterns.md +172 -0
  50. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +42 -3
  51. package/framework/squad-templates/backend-only.json +14 -1
  52. package/framework/squad-templates/frontend-only.json +14 -1
  53. package/framework/squad-templates/full-stack.json +25 -8
  54. package/framework/standards/STANDARDS.json +631 -86
  55. package/framework/standards/frontend/design-system/aesthetic-direction.md +213 -0
  56. package/framework/templates/project/validate.js +122 -0
  57. package/framework/workflows/configs/zero-touch.json +7 -0
  58. package/package.json +87 -87
  59. package/src/commands/agents/dispatch-agents.js +53 -10
  60. package/src/commands/state/advance-phase.js +88 -13
  61. package/src/commands/state/index.js +2 -1
  62. package/src/commands/state/phase-runner.js +215 -0
  63. package/src/commands/tasks/task.js +25 -4
  64. package/src/core/paths/output-schema.js +2 -1
  65. package/src/lib/detectors/design-system-detector.js +5 -4
  66. package/src/lib/generators/recap-generator.js +16 -0
  67. package/src/lib/orchestration/team-orchestrator.js +171 -89
  68. package/src/lib/phase-chain/eligibility-checker.js +243 -0
  69. package/src/lib/standards/digest-builder.js +231 -0
  70. package/src/lib/tasks/task-parser.js +94 -0
  71. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +39 -0
  72. package/src/lib/validators/content/content-validator.js +34 -106
  73. package/src/lib/validators/nextjs/next-component-validator.js +2 -0
  74. package/src/lib/validators/validation-runner.js +2 -2
  75. package/src/utils/file-copier.js +1 -0
  76. package/src/utils/hooks-installer.js +31 -7
@@ -30,6 +30,16 @@ function getSpecMaxChars() {
30
30
  }
31
31
  }
32
32
 
33
+ function getProjectConfig() {
34
+ try {
35
+ const configPath = join(process.cwd(), '.morph/config/config.json');
36
+ if (!existsSync(configPath)) return null;
37
+ return JSON.parse(readFileSync(configPath, 'utf-8'));
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
33
43
  const SPEC_MAX_CHARS = getSpecMaxChars();
34
44
 
35
45
  try {
@@ -71,6 +81,30 @@ try {
71
81
  lines.push(`- Last checkpoint: #${lastCp.checkpointNum} (${lastCp.passed ? 'passed' : 'failed'})`);
72
82
  }
73
83
 
84
+ // Task tracking guidance for work phases
85
+ if (['design', 'tasks', 'implement'].includes(phase)) {
86
+ lines.push('');
87
+ lines.push('💡 TaskCreate/TaskUpdate: Para sessões com 3+ ações planejadas, crie tasks Claude Code');
88
+ lines.push(' para visibilidade do progresso. Ex: TaskCreate("Gerar spec.md"), TaskUpdate(id, "completed")');
89
+ }
90
+
91
+ // Trust level visibility — warn when low trust may block approval gates
92
+ const APPROVAL_GATE_PHASES = new Set(['proposal', 'setup', 'design', 'tasks']);
93
+ if (APPROVAL_GATE_PHASES.has(phase)) {
94
+ const config = getProjectConfig();
95
+ const trustLevel = config?.trust?.level || config?.trustLevel || 'low';
96
+ const workflow = feature.workflow || config?.workflow;
97
+ if (trustLevel !== 'high' && trustLevel !== 'maximum') {
98
+ lines.push('');
99
+ lines.push(`⚠ Trust level: ${trustLevel} — approval gates require manual approval`);
100
+ lines.push(` To auto-approve: npx morph-spec trust set ${name} high`);
101
+ }
102
+ if (!workflow && config?.workflowDetection?.confidence === 0) {
103
+ lines.push('');
104
+ lines.push('ℹ Workflow not detected (confidence: 0). Set manually in .morph/config/config.json → workflow');
105
+ }
106
+ }
107
+
74
108
  // Active feature spec (truncated for context budget)
75
109
  const specPath = join(process.cwd(), `.morph/features/${name}/1-design/spec.md`);
76
110
  if (existsSync(specPath)) {
@@ -161,6 +161,12 @@ def get_all_active_features(cwd, entries):
161
161
 
162
162
  # Filter to features belonging to this session (mentioned in transcript)
163
163
  session_names = get_session_feature_names(features, entries)
164
+
165
+ # Auto-detect: if only one feature is in_progress, show it regardless of transcript
166
+ in_progress = [n for n, f in features.items() if f.get('status') == 'in_progress']
167
+ if len(in_progress) == 1:
168
+ session_names.add(in_progress[0])
169
+
164
170
  if not session_names:
165
171
  return []
166
172
 
@@ -21,7 +21,7 @@
21
21
  import { readFileSync, writeFileSync, existsSync } from 'fs';
22
22
  import { join } from 'path';
23
23
  import {
24
- stateExists, getActiveFeature, getMissingOutputs, derivePhaseForFeature,
24
+ stateExists, loadState, getActiveFeature, getMissingOutputs, derivePhaseForFeature,
25
25
  } from '../../shared/state-reader.js';
26
26
  import { injectContext, pass } from '../../shared/hook-response.js';
27
27
 
@@ -37,7 +37,22 @@ try {
37
37
 
38
38
  if (!stateExists()) pass();
39
39
 
40
- const active = getActiveFeature();
40
+ // getActiveFeature() only returns in_progress/draft features.
41
+ // Fall back to the most recently updated feature when all features are 'done'.
42
+ let active = getActiveFeature();
43
+ if (!active) {
44
+ const state = loadState();
45
+ if (state?.features) {
46
+ let latestUpdate = '';
47
+ for (const [name, feature] of Object.entries(state.features)) {
48
+ const updated = feature.updatedAt || feature.createdAt || '';
49
+ if (updated >= latestUpdate) {
50
+ latestUpdate = updated;
51
+ active = { name, feature };
52
+ }
53
+ }
54
+ }
55
+ }
41
56
  if (!active) pass();
42
57
 
43
58
  const { name, feature } = active;
@@ -120,11 +135,30 @@ try {
120
135
  }
121
136
  }
122
137
 
123
- if (warnings.length === 0) pass();
138
+ // ── MEMORY.md reminder ────────────────────────────────────────────────────
139
+ const WORK_PHASES = new Set(['design', 'clarify', 'tasks', 'implement']);
140
+ const memoryLines = [];
141
+ if (WORK_PHASES.has(currentPhase)) {
142
+ memoryLines.push('📝 Sessão encerrando. Considere salvar no MEMORY.md:');
143
+ memoryLines.push(' • Padrões de domínio descobertos (regras, invariants, edge cases)');
144
+ memoryLines.push(' • Decisões arquiteturais tomadas nesta sessão (e o motivo)');
145
+ memoryLines.push(' • Erros frequentes desta stack/projeto e como resolvê-los');
146
+ memoryLines.push(' • Nuances do projeto que diferem do padrão (convenções, libs específicas)');
147
+ memoryLines.push(' • Bugs encontrados e a causa raiz');
148
+ memoryLines.push(' Se nada relevante foi descoberto nesta sessão, ignore este lembrete.');
149
+ }
150
+
151
+ if (warnings.length === 0 && memoryLines.length === 0) pass();
152
+
153
+ const allLines = [...warnings];
154
+ if (memoryLines.length > 0) {
155
+ if (allLines.length > 0) allLines.push('');
156
+ allLines.push(...memoryLines);
157
+ }
124
158
 
125
159
  const message = [
126
160
  'MORPH-SPEC:',
127
- ...warnings.map(w => ` ${w}`),
161
+ ...allLines.map(w => ` ${w}`),
128
162
  '',
129
163
  `Status: morph-spec status ${name}`,
130
164
  ].join('\n');
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * TeammateIdle Hook: Validate feature before teammate becomes idle
5
+ *
6
+ * Event: TeammateIdle
7
+ *
8
+ * When an Agent Team teammate finishes its turn and is about to go idle,
9
+ * this hook runs `morph-spec validate-feature` to check implementation quality.
10
+ *
11
+ * Exit codes:
12
+ * 0 = validation passed — teammate may become idle
13
+ * 2 = validation failed — send feedback to teammate to continue working
14
+ *
15
+ * Fail-open: exits 0 on any error (never blocks teammate incorrectly).
16
+ */
17
+
18
+ import { execSync } from 'child_process';
19
+ import { readStdin } from '../../../shared/stdin-reader.js';
20
+ import { loadState, stateExists, getActiveFeature } from '../../../shared/state-reader.js';
21
+
22
+ try {
23
+ // Read hook payload (TeammateIdle provides teammate info)
24
+ const payload = await readStdin();
25
+
26
+ if (!stateExists()) process.exit(0);
27
+
28
+ // Try to extract feature name from payload or fall back to active feature
29
+ let featureName = payload?.feature_name || payload?.featureName;
30
+
31
+ if (!featureName) {
32
+ const active = getActiveFeature();
33
+ featureName = active?.name;
34
+ }
35
+
36
+ if (!featureName) process.exit(0);
37
+
38
+ // Only validate during implement phase
39
+ const state = loadState();
40
+ const feature = state?.features?.[featureName];
41
+ if (!feature) process.exit(0);
42
+
43
+ // derive phase — only run validation during implement phase
44
+ const phase = feature.phase;
45
+ if (phase && phase !== 'implement') process.exit(0);
46
+
47
+ // Run validate-feature with --quiet flag (suppress non-error output)
48
+ let output = '';
49
+ let exitCode = 0;
50
+
51
+ try {
52
+ output = execSync(
53
+ `npx morph-spec validate-feature "${featureName}" --phase implement --quiet`,
54
+ {
55
+ cwd: process.cwd(),
56
+ encoding: 'utf-8',
57
+ timeout: 30000,
58
+ stdio: ['pipe', 'pipe', 'pipe'],
59
+ }
60
+ );
61
+ } catch (err) {
62
+ // Non-zero exit from validate-feature means validation errors
63
+ exitCode = err.status || 1;
64
+ output = err.stdout || err.stderr || '';
65
+ }
66
+
67
+ if (exitCode !== 0) {
68
+ // Exit 2: send feedback to teammate to continue working
69
+ const feedback = [
70
+ 'MORPH-SPEC validation found issues — please fix before going idle:',
71
+ '',
72
+ output.trim() || 'Run `npx morph-spec validate-feature to see details.',
73
+ '',
74
+ `Run: npx morph-spec validate-feature ${featureName} --phase implement`,
75
+ ].join('\n');
76
+
77
+ // Claude Code reads additionalContext from stdout for exit code 2
78
+ process.stdout.write(JSON.stringify({ additionalContext: feedback }) + '\n');
79
+ process.exit(2);
80
+ }
81
+
82
+ // Exit 0: validation passed, teammate may become idle
83
+ process.exit(0);
84
+ } catch {
85
+ // Fail-open: never block a teammate due to hook errors
86
+ process.exit(0);
87
+ }
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * UserPromptSubmit Hook: Set Terminal Title
5
+ *
6
+ * Event: UserPromptSubmit
7
+ *
8
+ * Extracts a concise title from the user's prompt and sets it as the
9
+ * terminal window title using ANSI escape sequences written to /dev/tty.
10
+ * Also saves to ~/.claude/terminal_title for shell hook integration.
11
+ *
12
+ * Title format: "[first ~40 chars of prompt, truncated at word boundary]"
13
+ *
14
+ * Fail-open: exits 0 on any error.
15
+ */
16
+
17
+ import { readStdin } from '../../shared/stdin-reader.js';
18
+ import { openSync, writeSync, closeSync } from 'fs';
19
+ import { writeFile, mkdir } from 'fs/promises';
20
+ import { homedir } from 'os';
21
+ import { join } from 'path';
22
+
23
+ try {
24
+ const payload = await readStdin();
25
+ if (!payload) process.exit(0);
26
+
27
+ const prompt = payload?.prompt || payload?.user_prompt || payload?.content || '';
28
+ if (!prompt || prompt.length < 3) process.exit(0);
29
+
30
+ // Generate title: clean prompt, truncate at word boundary within 40 chars
31
+ const cleaned = prompt.replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
32
+ const maxLen = 40;
33
+ let title = cleaned.slice(0, maxLen);
34
+ if (cleaned.length > maxLen) {
35
+ const lastSpace = title.lastIndexOf(' ');
36
+ if (lastSpace > 20) title = title.slice(0, lastSpace);
37
+ }
38
+
39
+ const prefix = process.env.CLAUDE_TITLE_PREFIX ? `${process.env.CLAUDE_TITLE_PREFIX} ` : '';
40
+ const finalTitle = `${prefix}${title}`;
41
+
42
+ // Save to ~/.claude/terminal_title (for shell hook integration)
43
+ try {
44
+ const claudeDir = join(homedir(), '.claude');
45
+ await mkdir(claudeDir, { recursive: true });
46
+ await writeFile(join(claudeDir, 'terminal_title'), finalTitle, 'utf-8');
47
+ } catch { /* non-critical */ }
48
+
49
+ // Write ANSI escape to /dev/tty (bypasses stdout capture by Claude Code)
50
+ try {
51
+ const tty = openSync('/dev/tty', 'w');
52
+ writeSync(tty, `\x1b]0;${finalTitle}\x07`);
53
+ closeSync(tty);
54
+ } catch { /* terminal may not support /dev/tty */ }
55
+
56
+ } catch { /* fail-open */ }
57
+
58
+ process.exit(0);
@@ -16,7 +16,7 @@ export const PHASE_DIRS = {
16
16
  setup: '0-proposal',
17
17
  uiux: '2-ui',
18
18
  design: '1-design',
19
- clarify: '1-design',
19
+ clarify: '2-clarify',
20
20
  tasks: '3-tasks',
21
21
  implement: '4-implement',
22
22
  sync: '4-implement',
@@ -29,6 +29,7 @@ export const OUTPUT_PHASE_MAP = {
29
29
  spec: 'design',
30
30
  clarifications: 'clarify',
31
31
  contracts: 'design',
32
+ contractsVsa: 'design',
32
33
  tasks: 'tasks',
33
34
  uiDesignSystem: 'uiux',
34
35
  uiMockups: 'uiux',
@@ -45,6 +46,7 @@ export const FILENAME_TO_OUTPUT = {
45
46
  'spec.md': 'spec',
46
47
  'clarifications.md': 'clarifications',
47
48
  'contracts.cs': 'contracts',
49
+ 'contracts-vsa.cs': 'contractsVsa',
48
50
  'tasks.md': 'tasks',
49
51
  'design-system.md': 'uiDesignSystem',
50
52
  'mockups.md': 'uiMockups',
@@ -59,6 +61,7 @@ export const PROTECTED_SPEC_FILES = {
59
61
  'schema-analysis.md': 'design',
60
62
  'spec.md': 'design',
61
63
  'contracts.cs': 'design',
64
+ 'contracts-vsa.cs': 'design',
62
65
  'tasks.md': 'tasks',
63
66
  'design-system.md': 'uiux',
64
67
  'mockups.md': 'uiux',
@@ -85,6 +85,7 @@ export function derivePhaseForFeature(featureName, projectPath) {
85
85
  const phaseMap = [
86
86
  ['4-implement', 'implement'],
87
87
  ['3-tasks', 'tasks'],
88
+ ['2-clarify', 'clarify'],
88
89
  ['2-ui', 'uiux'],
89
90
  ['1-design', 'design'],
90
91
  ['0-proposal', 'proposal'],
@@ -17,6 +17,7 @@ Each skill is a directory: `{name}/SKILL.md` + optional `scripts/`, `references/
17
17
  | `verification-before-completion` | Gate check before marking any work done |
18
18
  | `code-review` | .NET/C# review checklist (naming, arch, async, DI, DTOs) |
19
19
  | `morph-replicate` | Convert HTML prototypes to Blazor components |
20
+ | `terminal-title` | Manually override the terminal window title for the current task |
20
21
 
21
22
  ### level-0-meta extras
22
23
 
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: brainstorming
3
3
  description: Morph-spec-aware brainstorming that loads project context, explores multiple design approaches, asks clarifying questions, and produces proposal.md or decisions.md. Use before designing a feature, when facing architectural decisions with multiple valid approaches, or when a feature needs requirements exploration before committing to a direction.
4
+ user-invocable: true
5
+ argument-hint: "[feature-name or topic]"
4
6
  ---
5
7
 
6
8
  # Brainstorming — MORPH-SPEC Integrated
@@ -13,6 +13,22 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
13
13
  > **Ref:** `framework/standards/backend/database/ef-core.md` for DbContext patterns and background ops.
14
14
  > **Example:** `references/review-example.md` — filled-in review showing expected finding format and severity levels.
15
15
  > **Script:** `scripts/scan-csharp.mjs` — automated scan for CRITICAL/HIGH violations before manual review.
16
+ > **Ref:** `references/review-guidelines.md` — false-positive rubric, confidence scoring, evidence format.
17
+
18
+ ---
19
+
20
+ ## Step 0 — Eligibility Check
21
+
22
+ ```bash
23
+ git diff --name-only main...HEAD -- "*.cs" | wc -l
24
+ ```
25
+
26
+ **Pular revisão se:**
27
+ - 0 arquivos `.cs` alterados → sem código backend para revisar
28
+ - Apenas mudanças em `*.json`, `*.csproj`, `*.md`, arquivos de migration → escopo de infra
29
+ - Já existe `.morph/features/$ARGUMENTS/4-implement/code-review.md` criado hoje → já revisado
30
+
31
+ Se skip: output `"Skipping code-review: [motivo]"` e parar.
16
32
 
17
33
  ---
18
34
 
@@ -0,0 +1,100 @@
1
+ # Review Guidelines — False-Positive Rubric
2
+
3
+ > Documento de referência compartilhado por todos os review skills.
4
+ > Claude deve ler este documento antes de executar qualquer revisão de código.
5
+
6
+ ---
7
+
8
+ ## Confidence Scoring
9
+
10
+ Antes de reportar um finding, atribua um confidence score de 0–100:
11
+
12
+ | Score | Interpretação | Ação |
13
+ |-------|--------------|------|
14
+ | 90–100 | Definitivamente real — reproduzível mecanicamente | Reportar |
15
+ | 75–89 | Provavelmente real — padrão forte de problema | Reportar com justificativa |
16
+ | 50–74 | Incerto — depende de contexto não visível | Só reportar se CRITICAL/HIGH |
17
+ | 25–49 | Provavelmente false positive | Ignorar |
18
+ | 0–24 | Definitivamente false positive | Nunca reportar |
19
+
20
+ **Threshold: reportar apenas findings com confidence ≥ 75.**
21
+
22
+ O confidence score não aparece no output — aplique o filtro internamente. Output limpo = só findings que passaram ≥ 75.
23
+
24
+ ---
25
+
26
+ ## Categorias de False Positives (sempre ignorar)
27
+
28
+ 1. **Violações pré-existentes** — código não alterado neste PR/branch. Se está no `git diff`, é relevante. Se não está, é dívida técnica para uma task de refactor separada.
29
+
30
+ 2. **Linter/formatter-catchable** — naming trivial, whitespace, import order. Ferramentas automáticas cobrem isso; não gaste tempo de review manual neles.
31
+
32
+ 3. **Preocupações arquiteturais especulativas** — "pode ser problema se escalar para 1M users", "esta abordagem pode causar N+1 se o dataset crescer". Só reporte se o problema for atual e concreto.
33
+
34
+ 4. **Nitpicks abaixo de LOW** — preferências cosméticas sem impacto em corretude, performance ou manutenibilidade. Se não tem severidade, não reportar.
35
+
36
+ 5. **Build errors** — se o código compila, não é build error. Não reporte problemas que o compilador já rejeitaria.
37
+
38
+ ---
39
+
40
+ ## Formato de Evidência Obrigatório
41
+
42
+ Todo finding DEVE incluir:
43
+
44
+ ```
45
+ src/Application/Orders/OrderService.cs:42
46
+ ```csharp
47
+ catch { } // ← snippet do código ofensivo (≤ 5 linhas)
48
+ ```
49
+ Motivo: viola [EMPTY_CATCH] — exceções silenciadas impossibilitam diagnóstico.
50
+ Fix: adicionar logging mínimo + re-throw, ou tratar o caso explicitamente.
51
+ ```
52
+
53
+ Campos obrigatórios por finding:
54
+ - **File:line** — caminho relativo ao projeto + número da linha
55
+ - **Snippet** — trecho do código ofensivo, ≤ 5 linhas
56
+ - **Motivo** — por que viola o padrão (referência de regra ou checklist item)
57
+ - **Fix** — sugestão concreta e acionável
58
+
59
+ Findings sem file:line são invalidos e não devem ser reportados.
60
+
61
+ ---
62
+
63
+ ## Escopo: Apenas Código Alterado
64
+
65
+ Focar exclusivamente em arquivos de:
66
+
67
+ ```bash
68
+ git diff --name-only main...HEAD
69
+ ```
70
+
71
+ Issues pré-existentes = dívida técnica para tasks de refactor dedicadas, **não** para revisões de feature PR.
72
+
73
+ Se usando `scan-csharp.mjs` ou `scan-nextjs.mjs`, prefira `--diff` para limitar o escopo automaticamente.
74
+
75
+ ---
76
+
77
+ ## Saída Esperada
78
+
79
+ Após filtrar por confidence ≥ 75 e ignorar false positives, a saída deve ser uma das duas formas:
80
+
81
+ **Se há findings:**
82
+ ```
83
+ ## Code Review — [stack]
84
+
85
+ ### CRITICAL
86
+ - `src/Foo/Bar.cs:15` — [EMPTY_CATCH] Empty catch block swallows exceptions.
87
+ ```csharp
88
+ catch { }
89
+ ```
90
+ Fix: log + re-throw, ou tratar o caso.
91
+
92
+ ### HIGH
93
+ - `src/Foo/OrderService.cs:88` — [MISSING_CANCELLATION_TOKEN] Async method `ProcessOrderAsync` sem CancellationToken.
94
+ Fix: adicionar `CancellationToken ct = default` como último parâmetro.
95
+ ```
96
+
97
+ **Se sem findings CRITICAL/HIGH:**
98
+ ```
99
+ ✅ Sem CRITICAL/HIGH em [stack] alterado. [N arquivos revisados]
100
+ ```
@@ -6,9 +6,11 @@
6
6
  * Executed locally by Claude via Bash — output returned to Claude.
7
7
  *
8
8
  * Usage:
9
- * node scan-csharp.mjs [path] # scan directory or file
10
- * node scan-csharp.mjs src/ # scan src/ recursively
11
- * node scan-csharp.mjs --summary # counts only, no details
9
+ * node scan-csharp.mjs [path] # scan directory or file
10
+ * node scan-csharp.mjs src/ # scan src/ recursively
11
+ * node scan-csharp.mjs --summary # counts only, no details
12
+ * node scan-csharp.mjs --diff # scan only changed files (git diff main...HEAD)
13
+ * node scan-csharp.mjs --diff --base=develop # use different base branch
12
14
  *
13
15
  * Checks (ref: framework/standards/core/coding.md):
14
16
  * - UPPER_SNAKE_CASE constants → should be PascalCase
@@ -20,9 +22,13 @@
20
22
 
21
23
  import { readdirSync, readFileSync, statSync, existsSync } from 'fs';
22
24
  import { join, extname, relative } from 'path';
25
+ import { execSync } from 'child_process';
23
26
 
24
- const targetPath = process.argv[2] ?? '.';
25
- const summaryOnly = process.argv.includes('--summary');
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('--')) ?? '.';
26
32
 
27
33
  const CHECKS = [
28
34
  {
@@ -76,7 +82,29 @@ function collectCsFiles(dir) {
76
82
  return results;
77
83
  }
78
84
 
79
- const files = collectCsFiles(targetPath);
85
+ function getChangedFiles(base) {
86
+ try {
87
+ const raw = execSync(`git diff --name-only ${base}...HEAD`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
88
+ return raw.split('\n').map(f => f.trim()).filter(f => f.endsWith('.cs') && existsSync(f));
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ let mode = 'full';
95
+ let files;
96
+ if (diffMode) {
97
+ const changed = getChangedFiles(baseBranch);
98
+ if (changed !== null) {
99
+ files = changed;
100
+ mode = 'diff';
101
+ } else {
102
+ files = collectCsFiles(targetPath);
103
+ }
104
+ } else {
105
+ files = collectCsFiles(targetPath);
106
+ }
107
+
80
108
  const findings = [];
81
109
 
82
110
  for (const file of files) {
@@ -110,6 +138,8 @@ const summary = {
110
138
  findings: findings.length,
111
139
  bySeverity,
112
140
  clean: findings.length === 0,
141
+ mode,
142
+ ...(mode === 'diff' && { baseBranch, changedFilesCount: files.length }),
113
143
  };
114
144
 
115
145
  if (summaryOnly) {
@@ -17,6 +17,22 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
17
17
  > **Ref:** `framework/standards/frontend/nextjs/testing.md`
18
18
  > **Example:** `references/review-example-nextjs.md` — filled-in review showing expected finding format.
19
19
  > **Script:** `scripts/scan-nextjs.mjs` — automated scan for CRITICAL/HIGH violations before manual review.
20
+ > **Ref:** `.claude/skills/code-review/references/review-guidelines.md` — false-positive rubric, confidence scoring, evidence format.
21
+
22
+ ---
23
+
24
+ ## Step 0 — Eligibility Check
25
+
26
+ ```bash
27
+ git diff --name-only main...HEAD -- "*.tsx" "*.ts" | wc -l
28
+ ```
29
+
30
+ **Pular revisão se:**
31
+ - 0 arquivos `.tsx`/`.ts` alterados → sem código frontend para revisar
32
+ - Apenas mudanças em `*.json`, `*.md`, arquivos de config → escopo de infra
33
+ - Já existe `.morph/features/$ARGUMENTS/4-implement/code-review-nextjs.md` criado hoje → já revisado
34
+
35
+ Se skip: output `"Skipping code-review-nextjs: [motivo]"` e parar.
20
36
 
21
37
  ---
22
38