@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,151 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PostToolUse Hook: Validator Feedback
5
+ *
6
+ * Event: PostToolUse | Matcher: Bash
7
+ *
8
+ * Fires after `morph-spec task done` completes.
9
+ * Reads validationHistory for the completed task and injects
10
+ * remediation context when validation failed.
11
+ *
12
+ * This closes the Builder-Validator loop:
13
+ * task done → runValidation → validationHistory → hook injects context
14
+ * → LLM sees remediation prompt before next step → auto-fix attempt
15
+ *
16
+ * Fail-open: exits 0 on any error.
17
+ */
18
+
19
+ import { readFileSync, existsSync } from 'fs';
20
+ import { join } from 'path';
21
+ import { readStdin } from '../../shared/stdin-reader.js';
22
+ import { stateExists } from '../../shared/state-reader.js';
23
+ import { injectContext, pass } from '../../shared/hook-response.js';
24
+
25
+ // Standard IDs referenced in remediation messages
26
+ const STANDARD_REFS = {
27
+ 'di': 'backend/dotnet/core.md#dependency-injection',
28
+ 'async': 'backend/dotnet/async.md#cancellation-token-pattern',
29
+ 'security': 'core/architecture.md#security-patterns',
30
+ 'packages': 'backend/dotnet/program-cs-checklist.md#nuget-packages',
31
+ 'design-system': 'frontend/design-system/naming.md#css-class-conventions',
32
+ 'blazor': 'frontend/blazor/pitfalls.md#dbcontext-in-blazor',
33
+ };
34
+
35
+ try {
36
+ if (!stateExists()) pass();
37
+
38
+ const payload = await readStdin();
39
+ if (!payload) pass();
40
+
41
+ const command = payload?.tool_input?.command || '';
42
+ if (!command) pass();
43
+
44
+ // Match: morph-spec task done <feature> <taskId>
45
+ const taskDoneMatch = command.match(/morph-spec\s+task\s+done\s+(\S+)\s+(\S+)/);
46
+ if (!taskDoneMatch) pass();
47
+
48
+ const [, featureName, taskId] = taskDoneMatch;
49
+
50
+ // Load state and find validationHistory for this task
51
+ const statePath = join(process.cwd(), '.morph', 'state.json');
52
+ if (!existsSync(statePath)) pass();
53
+
54
+ let state;
55
+ try {
56
+ state = JSON.parse(readFileSync(statePath, 'utf8'));
57
+ } catch {
58
+ pass();
59
+ }
60
+
61
+ const feature = state?.features?.[featureName];
62
+ if (!feature) pass();
63
+
64
+ const taskHistory = feature.validationHistory?.[taskId];
65
+ if (!taskHistory) pass();
66
+
67
+ // Only act on failed validations (passed = no feedback needed)
68
+ if (taskHistory.status === 'passed') pass();
69
+
70
+ // Build remediation context based on validation issues
71
+ buildRemediationContext(featureName, taskId, taskHistory);
72
+ } catch {
73
+ // Fail-open
74
+ process.exit(0);
75
+ }
76
+
77
+ /**
78
+ * Build and inject remediation context for a failed validation.
79
+ *
80
+ * @param {string} featureName
81
+ * @param {string} taskId
82
+ * @param {Object} taskHistory - validationHistory[taskId]
83
+ */
84
+ function buildRemediationContext(featureName, taskId, taskHistory) {
85
+ const attempt = taskHistory.attempt || 1;
86
+ const validators = taskHistory.validators || {};
87
+
88
+ // Collect all issues across all validators
89
+ const allIssues = [];
90
+ for (const [validatorName, result] of Object.entries(validators)) {
91
+ if (result.passed) continue;
92
+ const issues = result.issues || [];
93
+ for (const issue of issues) {
94
+ allIssues.push({ validator: validatorName, ...issue });
95
+ }
96
+ }
97
+
98
+ if (allIssues.length === 0 && taskHistory.status !== 'blocked') pass();
99
+
100
+ const lines = [];
101
+
102
+ if (taskHistory.status === 'blocked') {
103
+ // Max attempts reached — escalation message
104
+ lines.push(`⛔ ESCALATION REQUIRED — Task ${taskId} in feature '${featureName}'`);
105
+ lines.push(` Validation failed ${attempt} times (max attempts reached).`);
106
+ lines.push(` Human review required before proceeding.`);
107
+ lines.push(` Last failures:`);
108
+ for (const issue of allIssues.slice(0, 5)) {
109
+ lines.push(` • [${issue.validator}] ${issue.message} ${issue.file ? `(${issue.file}${issue.line ? ':' + issue.line : ''})` : ''}`);
110
+ }
111
+ } else {
112
+ // Active remediation — attempt N of 3
113
+ lines.push(`🔄 REMEDIATION REQUIRED — Task ${taskId} (attempt ${attempt}/3)`);
114
+ lines.push(` Validation failed. Fix the following issues and re-run:`);
115
+ lines.push(` morph-spec task done ${featureName} ${taskId}`);
116
+ lines.push(``);
117
+
118
+ for (const issue of allIssues) {
119
+ const refKey = getRefKey(issue.rule || issue.validator);
120
+ const ref = refKey ? ` — Ref: ${STANDARD_REFS[refKey] || refKey}` : '';
121
+ const location = issue.file
122
+ ? ` (${issue.file}${issue.line ? ':' + issue.line : ''})`
123
+ : '';
124
+ lines.push(` • [${issue.validator}] ${issue.message}${location}${ref}`);
125
+ }
126
+
127
+ if (attempt >= 2) {
128
+ lines.push(``);
129
+ lines.push(` ⚠️ If this fails again, it will be escalated (attempt ${attempt + 1} = final).`);
130
+ }
131
+ }
132
+
133
+ injectContext(lines.join('\n'));
134
+ }
135
+
136
+ /**
137
+ * Map a rule/validator name to a STANDARD_REFS key.
138
+ * @param {string} ruleOrValidator
139
+ * @returns {string|null}
140
+ */
141
+ function getRefKey(ruleOrValidator) {
142
+ if (!ruleOrValidator) return null;
143
+ const lower = ruleOrValidator.toLowerCase();
144
+ if (lower.includes('di') || lower.includes('dependency') || lower.includes('injection')) return 'di';
145
+ if (lower.includes('async') || lower.includes('await') || lower.includes('cancellation')) return 'async';
146
+ if (lower.includes('security') || lower.includes('sql') || lower.includes('xss')) return 'security';
147
+ if (lower.includes('package') || lower.includes('nuget')) return 'packages';
148
+ if (lower.includes('design') || lower.includes('css') || lower.includes('naming')) return 'design-system';
149
+ if (lower.includes('blazor') || lower.includes('dbcontext')) return 'blazor';
150
+ return null;
151
+ }
@@ -30,6 +30,12 @@ import {
30
30
  import { block, pass } from '../../shared/hook-response.js';
31
31
 
32
32
  try {
33
+ // Amend mode: bypass phase write enforcement for legitimate corrections
34
+ if (process.env.MORPH_AMEND_PHASE) {
35
+ console.error(`[morph-spec] AMEND MODE: bypassing phase protection for phase '${process.env.MORPH_AMEND_PHASE}'`);
36
+ pass();
37
+ }
38
+
33
39
  if (!stateExists()) pass();
34
40
 
35
41
  const payload = await readStdin();
@@ -24,6 +24,12 @@ import {
24
24
  import { block, pass } from '../../shared/hook-response.js';
25
25
 
26
26
  try {
27
+ // Amend mode: bypass spec protection for legitimate in-implementation corrections
28
+ if (process.env.MORPH_AMEND_PHASE) {
29
+ console.error(`[morph-spec] AMEND MODE: bypassing spec protection for phase '${process.env.MORPH_AMEND_PHASE}'`);
30
+ pass();
31
+ }
32
+
27
33
  if (!stateExists()) pass();
28
34
 
29
35
  const payload = await readStdin();
@@ -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 {
@@ -78,6 +88,23 @@ try {
78
88
  lines.push(' para visibilidade do progresso. Ex: TaskCreate("Gerar spec.md"), TaskUpdate(id, "completed")');
79
89
  }
80
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
+
81
108
  // Active feature spec (truncated for context budget)
82
109
  const specPath = join(process.cwd(), `.morph/features/${name}/1-design/spec.md`);
83
110
  if (existsSync(specPath)) {
@@ -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;
@@ -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',
@@ -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