@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.
- package/README.md +379 -379
- package/bin/morph-spec.js +23 -2
- package/bin/{task-manager.cjs → task-manager.js} +249 -172
- package/claude-plugin.json +14 -14
- package/docs/CHEATSHEET.md +203 -203
- package/docs/QUICKSTART.md +1 -1
- package/framework/agents.json +224 -140
- package/framework/hooks/README.md +202 -202
- 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 +12 -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 +34 -0
- package/framework/hooks/claude-code/statusline.py +6 -0
- package/framework/hooks/claude-code/stop/validate-completion.js +38 -4
- 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 +4 -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 +50 -188
- 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 +71 -109
- 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 +171 -114
- 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 +35 -159
- 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 +87 -87
- package/src/commands/agents/dispatch-agents.js +53 -10
- package/src/commands/state/advance-phase.js +88 -13
- package/src/commands/state/index.js +2 -1
- package/src/commands/state/phase-runner.js +215 -0
- package/src/commands/tasks/task.js +25 -4
- package/src/core/paths/output-schema.js +2 -1
- package/src/lib/detectors/design-system-detector.js +5 -4
- 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/tasks/task-parser.js +94 -0
- package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +39 -0
- package/src/lib/validators/content/content-validator.js +34 -106
- 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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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: '
|
|
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',
|
|
@@ -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]
|
|
10
|
-
* node scan-csharp.mjs src/
|
|
11
|
-
* node scan-csharp.mjs --summary
|
|
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
|
|
25
|
-
const
|
|
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
|
-
|
|
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
|
|