brain-dev 2.2.0 → 2.3.0
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/agents/brain-reviewer.md +22 -0
- package/bin/brain-tools.cjs +4 -0
- package/bin/lib/agents.cjs +8 -1
- package/bin/lib/commands/discuss.cjs +6 -3
- package/bin/lib/commands/new-task.cjs +26 -2
- package/bin/lib/commands/progress.cjs +6 -0
- package/bin/lib/commands/review.cjs +173 -0
- package/bin/lib/commands/verify.cjs +2 -2
- package/bin/lib/commands.cjs +9 -0
- package/bin/lib/stack-expert.cjs +27 -7
- package/bin/lib/state.cjs +1 -0
- package/bin/templates/overlays/nextjs/planner-overlay.md +30 -0
- package/bin/templates/overlays/react-native/planner-overlay.md +27 -0
- package/bin/templates/reviewer.md +111 -0
- package/commands/brain/review.md +35 -0
- package/hooks/bootstrap.sh +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brain-reviewer
|
|
3
|
+
description: Code quality reviewer — DRY, over-engineering, framework best practices, style consistency
|
|
4
|
+
tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Grep
|
|
7
|
+
- Glob
|
|
8
|
+
- Bash
|
|
9
|
+
model: inherit
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
You are a code reviewer agent for the Brain workflow system.
|
|
13
|
+
|
|
14
|
+
You will receive:
|
|
15
|
+
- A SUMMARY of executed work (changed files, key decisions)
|
|
16
|
+
- Technology context (framework patterns, anti-patterns, project conventions)
|
|
17
|
+
- An output path for your review
|
|
18
|
+
|
|
19
|
+
Your job: Review the changed files for quality issues using confidence-based filtering (>=80% only). Focus on bugs, over-engineering, DRY violations, and framework convention adherence.
|
|
20
|
+
|
|
21
|
+
Read the changed files fully. Compare with existing project files for style consistency.
|
|
22
|
+
Write your findings to REVIEW.md at the specified output path.
|
package/bin/brain-tools.cjs
CHANGED
|
@@ -178,6 +178,10 @@ async function main() {
|
|
|
178
178
|
await require('./lib/commands/update.cjs').run(args.slice(1));
|
|
179
179
|
break;
|
|
180
180
|
|
|
181
|
+
case 'review':
|
|
182
|
+
await require('./lib/commands/review.cjs').run(args.slice(1));
|
|
183
|
+
break;
|
|
184
|
+
|
|
181
185
|
case 'upgrade':
|
|
182
186
|
await require('./lib/commands/upgrade.cjs').run(args.slice(1), {
|
|
183
187
|
brainDir: path.join(process.cwd(), '.brain')
|
package/bin/lib/agents.cjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Constant registry with discovery and validation functions.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const MAX_AGENTS =
|
|
9
|
+
const MAX_AGENTS = 9;
|
|
10
10
|
|
|
11
11
|
const AGENTS = {
|
|
12
12
|
researcher: {
|
|
@@ -59,6 +59,13 @@ const AGENTS = {
|
|
|
59
59
|
model: 'inherit',
|
|
60
60
|
description: 'Combines findings from parallel research agents into a unified SUMMARY.md'
|
|
61
61
|
},
|
|
62
|
+
reviewer: {
|
|
63
|
+
template: 'reviewer',
|
|
64
|
+
inputs: ['summary_content', 'changed_files', 'stack_expertise'],
|
|
65
|
+
outputs: ['REVIEW.md'],
|
|
66
|
+
model: 'inherit',
|
|
67
|
+
description: 'Reviews code quality: DRY, over-engineering, framework best practices, project style consistency'
|
|
68
|
+
},
|
|
62
69
|
mapper: {
|
|
63
70
|
template: 'mapper',
|
|
64
71
|
inputs: ['focus', 'codebase_root'],
|
|
@@ -5,7 +5,7 @@ const path = require('node:path');
|
|
|
5
5
|
const { readState, writeState, atomicWriteSync } = require('../state.cjs');
|
|
6
6
|
const { parseRoadmap } = require('../roadmap.cjs');
|
|
7
7
|
const { loadTemplate, interpolate } = require('../templates.cjs');
|
|
8
|
-
const { output, error, success, pipelineGate } = require('../core.cjs');
|
|
8
|
+
const { output, error, success, prefix, pipelineGate } = require('../core.cjs');
|
|
9
9
|
const { generateExpertise } = require('../stack-expert.cjs');
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -230,8 +230,11 @@ function handleSave(args, brainDir, state) {
|
|
|
230
230
|
nextAction: '/brain:plan'
|
|
231
231
|
};
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
const humanLines = [
|
|
234
|
+
prefix('Context captured.'),
|
|
235
|
+
pipelineGate(`npx brain-dev plan --phase ${phaseNumber}`)
|
|
236
|
+
].join('\n');
|
|
237
|
+
output(result, humanLines);
|
|
235
238
|
|
|
236
239
|
return result;
|
|
237
240
|
}
|
|
@@ -206,7 +206,7 @@ function buildStepInstructions(taskNum, slug, taskDir, brainDir, mode, research,
|
|
|
206
206
|
// VERIFY step
|
|
207
207
|
if (mode !== 'light') {
|
|
208
208
|
steps.verify = [
|
|
209
|
-
`## Step
|
|
209
|
+
`## Step 5: Verify — Task #${taskNum}`,
|
|
210
210
|
'',
|
|
211
211
|
`Verify the execution results against must_haves in: \`${taskDir}/PLAN-1.md\``,
|
|
212
212
|
'',
|
|
@@ -221,9 +221,29 @@ function buildStepInstructions(taskNum, slug, taskDir, brainDir, mode, research,
|
|
|
221
221
|
].join('\n');
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
// REVIEW step
|
|
225
|
+
if (mode !== 'light') {
|
|
226
|
+
steps.review = [
|
|
227
|
+
`## Step 4: Review — Task #${taskNum}`,
|
|
228
|
+
'',
|
|
229
|
+
`Run code review on executed work before verification.`,
|
|
230
|
+
'',
|
|
231
|
+
'Run: `npx brain-dev review`',
|
|
232
|
+
'',
|
|
233
|
+
'Brain will spawn a reviewer agent that checks:',
|
|
234
|
+
'- Code quality and DRY (no unnecessary abstractions)',
|
|
235
|
+
'- Framework best practices',
|
|
236
|
+
'- Over-engineering detection',
|
|
237
|
+
'- Project code style consistency',
|
|
238
|
+
'- Confidence-based filtering (only real issues, ≥80%)',
|
|
239
|
+
'',
|
|
240
|
+
'Then run: `npx brain-dev new-task --continue`'
|
|
241
|
+
].join('\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
224
244
|
// COMPLETE step
|
|
225
245
|
steps.complete = [
|
|
226
|
-
`## Step
|
|
246
|
+
`## Step 6: Complete — Task #${taskNum}`,
|
|
227
247
|
'',
|
|
228
248
|
'Task completion options:',
|
|
229
249
|
'',
|
|
@@ -276,6 +296,7 @@ function handleContinue(brainDir, state) {
|
|
|
276
296
|
const hasResearch = fs.existsSync(path.join(taskDir, 'RESEARCH.md'));
|
|
277
297
|
const hasPlan = fs.existsSync(path.join(taskDir, 'PLAN-1.md'));
|
|
278
298
|
const hasSummary = fs.existsSync(path.join(taskDir, 'SUMMARY-1.md'));
|
|
299
|
+
const hasReview = fs.existsSync(path.join(taskDir, 'REVIEW.md'));
|
|
279
300
|
const hasVerification = fs.existsSync(path.join(taskDir, 'VERIFICATION.md'));
|
|
280
301
|
|
|
281
302
|
const isLight = taskMeta.mode === 'light';
|
|
@@ -294,6 +315,9 @@ function handleContinue(brainDir, state) {
|
|
|
294
315
|
} else if (!hasSummary) {
|
|
295
316
|
nextStep = 'execute';
|
|
296
317
|
newStatus = 'executing';
|
|
318
|
+
} else if (!hasReview && !isLight) {
|
|
319
|
+
nextStep = 'review';
|
|
320
|
+
newStatus = 'reviewing';
|
|
297
321
|
} else if (!hasVerification && !isLight) {
|
|
298
322
|
nextStep = 'verify';
|
|
299
323
|
newStatus = 'verifying';
|
|
@@ -49,7 +49,13 @@ function nextAction(state) {
|
|
|
49
49
|
case 'executing':
|
|
50
50
|
return '/brain:execute';
|
|
51
51
|
case 'executed':
|
|
52
|
+
return '/brain:review';
|
|
53
|
+
case 'reviewing':
|
|
54
|
+
return '/brain:review';
|
|
55
|
+
case 'reviewed':
|
|
52
56
|
return '/brain:verify';
|
|
57
|
+
case 'review-failed':
|
|
58
|
+
return '/brain:review';
|
|
53
59
|
case 'verifying':
|
|
54
60
|
return '/brain:verify';
|
|
55
61
|
case 'verified':
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { readState, writeState } = require('../state.cjs');
|
|
6
|
+
const { loadTemplateWithOverlay, interpolate } = require('../templates.cjs');
|
|
7
|
+
const { getAgent, resolveModel } = require('../agents.cjs');
|
|
8
|
+
const { logEvent } = require('../logger.cjs');
|
|
9
|
+
const { output, error, prefix, pipelineGate } = require('../core.cjs');
|
|
10
|
+
const { generateExpertise, getDetectedFramework } = require('../stack-expert.cjs');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Find the phase directory for a given phase number.
|
|
14
|
+
*/
|
|
15
|
+
function findPhaseDir(brainDir, phaseNumber) {
|
|
16
|
+
const phasesDir = path.join(brainDir, 'phases');
|
|
17
|
+
if (!fs.existsSync(phasesDir)) return null;
|
|
18
|
+
const padded = String(phaseNumber).padStart(2, '0');
|
|
19
|
+
const match = fs.readdirSync(phasesDir).find(d => d.startsWith(`${padded}-`));
|
|
20
|
+
return match ? path.join(phasesDir, match) : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read the latest SUMMARY file from a phase directory.
|
|
25
|
+
*/
|
|
26
|
+
function readLatestSummary(phaseDir) {
|
|
27
|
+
if (!phaseDir || !fs.existsSync(phaseDir)) return null;
|
|
28
|
+
const files = fs.readdirSync(phaseDir)
|
|
29
|
+
.filter(f => f.toUpperCase().startsWith('SUMMARY'))
|
|
30
|
+
.sort();
|
|
31
|
+
if (files.length === 0) return null;
|
|
32
|
+
return fs.readFileSync(path.join(phaseDir, files[files.length - 1]), 'utf8');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run the review command.
|
|
37
|
+
* @param {string[]} args
|
|
38
|
+
* @param {object} [opts]
|
|
39
|
+
*/
|
|
40
|
+
async function run(args = [], opts = {}) {
|
|
41
|
+
const brainDir = opts.brainDir || path.join(process.cwd(), '.brain');
|
|
42
|
+
const state = readState(brainDir);
|
|
43
|
+
|
|
44
|
+
if (!state) {
|
|
45
|
+
error("No brain state found. Run 'brain-dev init' first.");
|
|
46
|
+
return { error: 'no-state' };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Handle --result flag (called after reviewer agent completes)
|
|
50
|
+
const resultIdx = args.indexOf('--result');
|
|
51
|
+
if (resultIdx >= 0) {
|
|
52
|
+
return handleResult(brainDir, state, args);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const phaseIdx = args.indexOf('--phase');
|
|
56
|
+
const phaseNumber = phaseIdx >= 0 ? parseInt(args[phaseIdx + 1], 10) : state.phase.current;
|
|
57
|
+
|
|
58
|
+
const phaseDir = findPhaseDir(brainDir, phaseNumber);
|
|
59
|
+
if (!phaseDir) {
|
|
60
|
+
error(`Phase ${phaseNumber} directory not found.`);
|
|
61
|
+
return { error: 'phase-dir-not-found' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Read latest summary
|
|
65
|
+
const summaryContent = readLatestSummary(phaseDir);
|
|
66
|
+
if (!summaryContent) {
|
|
67
|
+
error(`No SUMMARY file found for phase ${phaseNumber}. Run /brain:execute first.`);
|
|
68
|
+
return { error: 'no-summary' };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Build reviewer prompt
|
|
72
|
+
const framework = getDetectedFramework(brainDir);
|
|
73
|
+
const template = loadTemplateWithOverlay('reviewer', framework);
|
|
74
|
+
const reviewPath = path.join(phaseDir, 'REVIEW.md');
|
|
75
|
+
|
|
76
|
+
const prompt = interpolate(template, {
|
|
77
|
+
stack_expertise: generateExpertise(brainDir, 'reviewer'),
|
|
78
|
+
summary_content: summaryContent,
|
|
79
|
+
output_path: reviewPath,
|
|
80
|
+
phase: String(phaseNumber),
|
|
81
|
+
plan_number: 'all'
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Get reviewer agent metadata
|
|
85
|
+
const reviewerAgent = getAgent('reviewer');
|
|
86
|
+
const model = resolveModel('reviewer', state);
|
|
87
|
+
|
|
88
|
+
// Log spawn event
|
|
89
|
+
logEvent(brainDir, phaseNumber, {
|
|
90
|
+
type: 'spawn',
|
|
91
|
+
agent: 'reviewer',
|
|
92
|
+
phase: phaseNumber
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Update state
|
|
96
|
+
state.phase.status = 'reviewing';
|
|
97
|
+
writeState(brainDir, state);
|
|
98
|
+
|
|
99
|
+
const result = {
|
|
100
|
+
action: 'spawn-reviewer',
|
|
101
|
+
phase: phaseNumber,
|
|
102
|
+
prompt,
|
|
103
|
+
model,
|
|
104
|
+
outputPath: reviewPath,
|
|
105
|
+
nextAction: '/brain:verify'
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const humanLines = [
|
|
109
|
+
prefix(`Code review for Phase ${phaseNumber}`),
|
|
110
|
+
prefix(`Model: ${model}`),
|
|
111
|
+
prefix(`Output: ${reviewPath}`),
|
|
112
|
+
'',
|
|
113
|
+
'IMPORTANT: Use the Agent tool (subagent_type: "brain-reviewer") to spawn the reviewer.',
|
|
114
|
+
'Do NOT review the code yourself. The reviewer agent has framework-specific expertise.',
|
|
115
|
+
'',
|
|
116
|
+
'After the reviewer agent completes, run: npx brain-dev review --result --phase ' + phaseNumber,
|
|
117
|
+
'This finalizes the review status and advances the pipeline.',
|
|
118
|
+
'',
|
|
119
|
+
prompt
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
output(result, humanLines.join('\n'));
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Handle --result flag: finalize review status based on REVIEW.md.
|
|
128
|
+
* Called after reviewer agent writes REVIEW.md.
|
|
129
|
+
*/
|
|
130
|
+
function handleResult(brainDir, state, args) {
|
|
131
|
+
const phaseIdx = args.indexOf('--phase');
|
|
132
|
+
const phaseNumber = phaseIdx >= 0 ? parseInt(args[phaseIdx + 1], 10) : state.phase.current;
|
|
133
|
+
|
|
134
|
+
const phaseDir = findPhaseDir(brainDir, phaseNumber);
|
|
135
|
+
const reviewPath = phaseDir ? path.join(phaseDir, 'REVIEW.md') : null;
|
|
136
|
+
|
|
137
|
+
if (!reviewPath || !fs.existsSync(reviewPath)) {
|
|
138
|
+
error('No REVIEW.md found. Run /brain:review first to spawn the reviewer agent.');
|
|
139
|
+
return { error: 'no-review' };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Parse review status from frontmatter
|
|
143
|
+
const content = fs.readFileSync(reviewPath, 'utf8');
|
|
144
|
+
const statusMatch = content.match(/^status:\s*(passed|needs-work|critical)/m);
|
|
145
|
+
const reviewStatus = statusMatch ? statusMatch[1] : 'passed';
|
|
146
|
+
|
|
147
|
+
if (reviewStatus === 'critical') {
|
|
148
|
+
state.phase.status = 'review-failed';
|
|
149
|
+
writeState(brainDir, state);
|
|
150
|
+
error('Review found critical issues. Fix them and re-run /brain:review.');
|
|
151
|
+
output({ action: 'review-failed', phase: phaseNumber, status: reviewStatus }, '');
|
|
152
|
+
return { action: 'review-failed', phase: phaseNumber };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
state.phase.status = 'reviewed';
|
|
156
|
+
writeState(brainDir, state);
|
|
157
|
+
|
|
158
|
+
const result = {
|
|
159
|
+
action: 'review-complete',
|
|
160
|
+
phase: phaseNumber,
|
|
161
|
+
status: reviewStatus,
|
|
162
|
+
nextAction: '/brain:verify'
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
output(result, [
|
|
166
|
+
prefix(`Review complete: ${reviewStatus}`),
|
|
167
|
+
pipelineGate(`npx brain-dev verify --phase ${phaseNumber}`)
|
|
168
|
+
].join('\n'));
|
|
169
|
+
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = { run };
|
|
@@ -504,12 +504,12 @@ function handleLogRecovery(args, logRecoveryIdx, brainDir, state) {
|
|
|
504
504
|
? parseInt(args[phaseIdx + 1], 10)
|
|
505
505
|
: state.phase.current;
|
|
506
506
|
|
|
507
|
-
logEvent(brainDir, phaseNumber, event);
|
|
508
|
-
|
|
509
507
|
if (!event || typeof event !== 'object') {
|
|
510
508
|
error('--log-recovery requires a JSON object, not null/primitive');
|
|
511
509
|
return { error: 'invalid-json' };
|
|
512
510
|
}
|
|
511
|
+
|
|
512
|
+
logEvent(brainDir, phaseNumber, event);
|
|
513
513
|
output({ action: 'recovery-logged', event }, `[brain] Recovery event logged: ${event.type || 'unknown'}`);
|
|
514
514
|
return { action: 'recovery-logged', event };
|
|
515
515
|
}
|
package/bin/lib/commands.cjs
CHANGED
|
@@ -92,6 +92,15 @@ const COMMANDS = [
|
|
|
92
92
|
needsState: true,
|
|
93
93
|
args: ' --plan <id> Specific plan to execute (e.g. 01-02)'
|
|
94
94
|
},
|
|
95
|
+
{
|
|
96
|
+
name: 'review',
|
|
97
|
+
description: 'Code review after execution',
|
|
98
|
+
usage: 'brain-dev review [--phase <n>]',
|
|
99
|
+
group: 'Lifecycle',
|
|
100
|
+
implemented: true,
|
|
101
|
+
needsState: true,
|
|
102
|
+
args: ' --phase <n> Phase to review'
|
|
103
|
+
},
|
|
95
104
|
{
|
|
96
105
|
name: 'verify',
|
|
97
106
|
description: 'Run verification checks on completed work',
|
package/bin/lib/stack-expert.cjs
CHANGED
|
@@ -30,7 +30,7 @@ const STACK_PATTERNS = {
|
|
|
30
30
|
antiPatterns: 'N+1 queries (use eager loading with/load), fat controllers (extract to Services/Actions), raw SQL in controllers, logic in models (use Services), not using Form Requests for validation',
|
|
31
31
|
testing: 'Feature tests in tests/Feature, Unit tests in tests/Unit, use RefreshDatabase trait, factories for test data',
|
|
32
32
|
migration: 'php artisan make:migration create_X_table, php artisan migrate, php artisan migrate:rollback',
|
|
33
|
-
commonBugs: 'N+1 query on relationships (use ->with()),
|
|
33
|
+
commonBugs: ['N+1 query on relationships (use ->with())', 'Mass assignment vulnerability (use $fillable)', 'Missing foreign key constraints', 'Not using transactions for multi-table writes', 'Queue job serialization with deleted models'],
|
|
34
34
|
verificationRules: ['Migrations exist for all new models', '.env.example has all required keys', 'Routes registered in routes/api.php or routes/web.php', 'Form Requests validate all user input', 'API Resources used for JSON responses', 'Factories exist for all models'],
|
|
35
35
|
planningHints: ['Plan migration tasks BEFORE model/controller tasks', 'Each model needs: migration, model, factory, controller, request, resource, test', 'Group related migrations in same plan', 'Include seeder tasks for test data'],
|
|
36
36
|
testExamples: {
|
|
@@ -44,7 +44,7 @@ const STACK_PATTERNS = {
|
|
|
44
44
|
patterns: 'Server Components by default, use client directive for interactivity, Server Actions for mutations, Route Handlers for API, Metadata API for SEO, Suspense for loading states',
|
|
45
45
|
antiPatterns: 'useEffect for data fetching (use Server Components), client-side state for server data, large client bundles, not using loading.tsx/error.tsx, fetching in client when server component suffices',
|
|
46
46
|
testing: 'Jest + React Testing Library, or Vitest, test in __tests__/ or *.test.tsx',
|
|
47
|
-
commonBugs: 'Hydration mismatch (server vs client render difference),
|
|
47
|
+
commonBugs: ['Hydration mismatch (server vs client render difference)', 'Importing server-only code in client components', 'Not handling loading/error states', 'Stale cache in fetch()', 'Middleware running on every request'],
|
|
48
48
|
verificationRules: ['Page components exist in app/ directory', 'loading.tsx exists for async pages', 'error.tsx exists for error boundaries', 'API Route Handlers use correct HTTP method exports', 'Client components have "use client" directive', 'Metadata exported for SEO pages'],
|
|
49
49
|
planningHints: ['Plan layout components before page components', 'Server Components first, add "use client" only when needed', 'Plan API Route Handlers separate from page components', 'Include loading.tsx and error.tsx for each route group'],
|
|
50
50
|
testExamples: {
|
|
@@ -58,7 +58,7 @@ const STACK_PATTERNS = {
|
|
|
58
58
|
patterns: 'Functional components + hooks, React Navigation/Expo Router, AsyncStorage, StyleSheet.create, Platform.select for platform-specific code',
|
|
59
59
|
antiPatterns: 'Inline styles (use StyleSheet.create), heavy computation on JS thread, large FlatList without getItemLayout/keyExtractor, not using Platform.OS for platform checks, synchronous storage calls',
|
|
60
60
|
testing: 'Jest + React Native Testing Library, detox for E2E',
|
|
61
|
-
commonBugs: 'Platform-specific rendering not tested on both OS,
|
|
61
|
+
commonBugs: ['Platform-specific rendering not tested on both OS', 'Keyboard covering inputs (use KeyboardAvoidingView)', 'Memory leaks from unsubscribed listeners', 'Navigation state persistence issues', 'StatusBar behavior differences iOS vs Android'],
|
|
62
62
|
verificationRules: ['Platform-specific code uses Platform.OS or Platform.select', 'FlatList has keyExtractor and getItemLayout', 'Navigation screens registered in navigator', 'Assets imported correctly (require() for local)', 'No web-only APIs used (document, window)'],
|
|
63
63
|
planningHints: ['Plan shared components before screen-specific ones', 'Consider both iOS and Android in task descriptions', 'Plan navigation structure before individual screens', 'Include platform-specific testing tasks'],
|
|
64
64
|
testExamples: {
|
|
@@ -72,7 +72,7 @@ const STACK_PATTERNS = {
|
|
|
72
72
|
patterns: 'Router middleware chain, error handling middleware, async/await with express-async-errors, request validation with Joi/Zod, controller-service separation',
|
|
73
73
|
antiPatterns: 'Callback hell, no error middleware, sync operations blocking event loop, not validating request body, try/catch in every route (use express-async-errors)',
|
|
74
74
|
testing: 'Jest/Mocha + supertest for HTTP assertions',
|
|
75
|
-
commonBugs: 'Unhandled promise rejections crashing server,
|
|
75
|
+
commonBugs: ['Unhandled promise rejections crashing server', 'Missing error middleware (must have 4 params: err, req, res, next)', 'Route order matters (specific before generic)', 'CORS misconfiguration', 'Not closing DB connections on shutdown'],
|
|
76
76
|
verificationRules: ['Error handling middleware exists (4 params: err, req, res, next)', 'Routes validate input before processing', 'Async handlers properly catch errors', 'CORS configured for production origins', 'Graceful shutdown handler exists'],
|
|
77
77
|
planningHints: ['Plan middleware before routes', 'Error handling middleware is a separate task at the end', 'Group related routes in same router file', 'Plan validation schemas alongside route handlers'],
|
|
78
78
|
testExamples: {
|
|
@@ -87,7 +87,7 @@ const STACK_PATTERNS = {
|
|
|
87
87
|
antiPatterns: 'N+1 queries (use select_related/prefetch_related), logic in views (use services), not using serializers for validation, raw SQL without parameterization',
|
|
88
88
|
testing: 'pytest-django or TestCase, factory_boy for fixtures',
|
|
89
89
|
migration: 'python manage.py makemigrations, python manage.py migrate, python manage.py migrate <app> zero (rollback)',
|
|
90
|
-
commonBugs: 'Circular imports between apps,
|
|
90
|
+
commonBugs: ['Circular imports between apps', 'Migration conflicts in teams', 'Not using select_related causing N+1', 'Signals firing during tests unexpectedly', 'Timezone-naive datetime comparisons'],
|
|
91
91
|
verificationRules: ['Migrations created for all model changes', 'URLs registered in app urls.py and included in root urls.py', 'Admin registered for all models', 'Serializers validate all user input', 'Permissions set on views'],
|
|
92
92
|
planningHints: ['Plan models and migrations before views', 'Each app should be self-contained', 'Plan serializers alongside models', 'Include admin registration tasks', 'Plan management commands for data operations'],
|
|
93
93
|
testExamples: {
|
|
@@ -137,7 +137,7 @@ const STACK_PATTERNS = {
|
|
|
137
137
|
antiPatterns: 'Sync database calls in async endpoints, no Pydantic validation, global mutable state, not using Depends() for shared logic, blocking the event loop',
|
|
138
138
|
testing: 'pytest + httpx AsyncClient',
|
|
139
139
|
migration: 'alembic init, alembic revision --autogenerate -m "description", alembic upgrade head',
|
|
140
|
-
commonBugs: 'Mixing sync/async database drivers, Pydantic v2 model_validator vs v1 validator,
|
|
140
|
+
commonBugs: ['Mixing sync/async database drivers', 'Pydantic v2 model_validator vs v1 validator', 'Circular imports in routers', 'Not closing DB connections in lifespan', 'CORS middleware order matters'],
|
|
141
141
|
verificationRules: ['Pydantic schemas exist for all request/response models', 'Dependencies use Depends() injection', 'Async endpoints use async database driver', 'Alembic migrations exist for schema changes', 'Error handlers return consistent JSON format'],
|
|
142
142
|
planningHints: ['Plan Pydantic schemas before routers', 'Plan database models with Alembic migrations', 'Group related endpoints in same router', 'Plan dependency injection for shared resources (DB, auth)'],
|
|
143
143
|
testExamples: {
|
|
@@ -318,7 +318,8 @@ function generateExpertise(brainDir, role = 'general') {
|
|
|
318
318
|
if (fwInfo.commonBugs) {
|
|
319
319
|
sections.push('');
|
|
320
320
|
sections.push(`### Common ${framework} Bugs to Avoid`);
|
|
321
|
-
|
|
321
|
+
const bugs = Array.isArray(fwInfo.commonBugs) ? fwInfo.commonBugs : fwInfo.commonBugs.split(', ');
|
|
322
|
+
for (const [i, bug] of bugs.entries()) {
|
|
322
323
|
sections.push(`${i + 1}. ${bug}`);
|
|
323
324
|
}
|
|
324
325
|
}
|
|
@@ -344,6 +345,25 @@ function generateExpertise(brainDir, role = 'general') {
|
|
|
344
345
|
}
|
|
345
346
|
}
|
|
346
347
|
sections.push('');
|
|
348
|
+
} else if (role === 'reviewer' && fwInfo) {
|
|
349
|
+
sections.push('### Reviewer Guidance');
|
|
350
|
+
sections.push(`- Check code follows ${framework} conventions`);
|
|
351
|
+
if (fwInfo.antiPatterns) {
|
|
352
|
+
sections.push(`- Scan for anti-patterns: ${fwInfo.antiPatterns}`);
|
|
353
|
+
}
|
|
354
|
+
if (fwInfo.patterns) {
|
|
355
|
+
sections.push(`- Verify patterns used: ${fwInfo.patterns}`);
|
|
356
|
+
}
|
|
357
|
+
sections.push('- Compare naming with existing project files');
|
|
358
|
+
sections.push('- Flag over-engineering: unnecessary abstractions, premature optimization');
|
|
359
|
+
if (fwInfo.verificationRules) {
|
|
360
|
+
sections.push('');
|
|
361
|
+
sections.push(`### ${framework} Review Checklist`);
|
|
362
|
+
for (const rule of fwInfo.verificationRules) {
|
|
363
|
+
sections.push(`- [ ] ${rule}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
sections.push('');
|
|
347
367
|
} else {
|
|
348
368
|
// Generic role guidance (no framework info or general role)
|
|
349
369
|
if (role === 'planner') {
|
package/bin/lib/state.cjs
CHANGED
|
@@ -512,6 +512,7 @@ function today() {
|
|
|
512
512
|
const VALID_PHASE_STATUSES = [
|
|
513
513
|
'initialized', 'pending', 'mapped', 'ready', 'discussing', 'discussed',
|
|
514
514
|
'planning', 'planned', 'executing', 'executed',
|
|
515
|
+
'reviewing', 'reviewed', 'review-failed',
|
|
515
516
|
'verifying', 'verified', 'verification-failed',
|
|
516
517
|
'partial', 'failed', 'paused', 'complete'
|
|
517
518
|
];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## Next.js-Specific Planning
|
|
2
|
+
|
|
3
|
+
### Task Ordering
|
|
4
|
+
1. **Layouts first**: Create layout.tsx before page components
|
|
5
|
+
2. **Server Components first**: Default to Server Components, add "use client" only when needed
|
|
6
|
+
3. **API Routes separate**: Plan Route Handlers as distinct tasks from pages
|
|
7
|
+
4. **Error boundaries**: Include loading.tsx and error.tsx for each route group
|
|
8
|
+
|
|
9
|
+
### Standard Feature Pattern
|
|
10
|
+
For each new route/feature, plan these tasks:
|
|
11
|
+
1. Layout component (if new route group)
|
|
12
|
+
2. Page component (Server Component by default)
|
|
13
|
+
3. Loading state (loading.tsx)
|
|
14
|
+
4. Error boundary (error.tsx)
|
|
15
|
+
5. API Route Handler (if needed)
|
|
16
|
+
6. Client components (only for interactivity)
|
|
17
|
+
7. Tests
|
|
18
|
+
|
|
19
|
+
### must_haves Template for Next.js
|
|
20
|
+
```yaml
|
|
21
|
+
truths:
|
|
22
|
+
- "Page renders correctly as Server Component"
|
|
23
|
+
- "loading.tsx shows skeleton/spinner during data fetch"
|
|
24
|
+
- "error.tsx catches and displays errors gracefully"
|
|
25
|
+
- "No useEffect used for data fetching (Server Component handles it)"
|
|
26
|
+
artifacts:
|
|
27
|
+
- path: "app/{route}/page.tsx"
|
|
28
|
+
- path: "app/{route}/loading.tsx"
|
|
29
|
+
- path: "app/{route}/error.tsx"
|
|
30
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
## React Native-Specific Planning
|
|
2
|
+
|
|
3
|
+
### Task Ordering
|
|
4
|
+
1. **Navigation first**: Define navigation structure before screens
|
|
5
|
+
2. **Shared components**: Plan reusable components before screen-specific ones
|
|
6
|
+
3. **Platform handling**: Consider both iOS and Android in each task
|
|
7
|
+
4. **Performance**: Plan FlatList optimization tasks alongside data display tasks
|
|
8
|
+
|
|
9
|
+
### Standard Feature Pattern
|
|
10
|
+
For each new screen/feature, plan these tasks:
|
|
11
|
+
1. Navigation registration (add to navigator)
|
|
12
|
+
2. Screen component with StyleSheet
|
|
13
|
+
3. Shared sub-components (if needed)
|
|
14
|
+
4. Platform-specific adjustments (Platform.OS checks)
|
|
15
|
+
5. Tests (component + interaction)
|
|
16
|
+
|
|
17
|
+
### must_haves Template for React Native
|
|
18
|
+
```yaml
|
|
19
|
+
truths:
|
|
20
|
+
- "Screen renders correctly on both iOS and Android"
|
|
21
|
+
- "Navigation to/from screen works with correct params"
|
|
22
|
+
- "StyleSheet.create used (no inline styles)"
|
|
23
|
+
- "FlatList has keyExtractor for list screens"
|
|
24
|
+
artifacts:
|
|
25
|
+
- path: "app/{screen}.tsx or src/screens/{Screen}.tsx"
|
|
26
|
+
- path: "components/{Component}.tsx"
|
|
27
|
+
```
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Code Reviewer Agent
|
|
2
|
+
|
|
3
|
+
## Technology Context
|
|
4
|
+
{{stack_expertise}}
|
|
5
|
+
|
|
6
|
+
## Review Scope
|
|
7
|
+
|
|
8
|
+
Review the code changes from the latest execution. Changed files are listed in the SUMMARY below.
|
|
9
|
+
|
|
10
|
+
{{summary_content}}
|
|
11
|
+
|
|
12
|
+
## Review Criteria (Priority Order)
|
|
13
|
+
|
|
14
|
+
### 1. Critical Issues (MUST fix before verify)
|
|
15
|
+
- **Bugs**: Logic errors, unhandled edge cases, race conditions
|
|
16
|
+
- **Security**: SQL injection, XSS, auth bypass, secrets in code
|
|
17
|
+
- **Data loss**: Missing transactions, unchecked deletes, no rollback
|
|
18
|
+
|
|
19
|
+
### 2. Quality Issues (SHOULD fix)
|
|
20
|
+
- **Over-engineering**: Abstractions for things used once, premature optimization, unnecessary design patterns
|
|
21
|
+
- Rule: If code is used in ONE place, inline it. Don't create a helper/utility/service for single use.
|
|
22
|
+
- Rule: If a simple `if/else` works, don't use Strategy Pattern.
|
|
23
|
+
- Rule: 3 similar lines are better than a premature abstraction.
|
|
24
|
+
- Rule: Don't add features, refactor code, or make "improvements" beyond what was asked.
|
|
25
|
+
- **DRY violations**: Same logic in multiple places (>5 lines duplicated)
|
|
26
|
+
- Only flag if the duplication is >5 lines AND in the same module/feature scope
|
|
27
|
+
- Cross-module duplication is acceptable if coupling would be worse
|
|
28
|
+
- **Test quality**: Tests that don't assert behavior, tests that test implementation details
|
|
29
|
+
- Good: `assert.equal(result.status, 'success')`
|
|
30
|
+
- Bad: `assert.equal(mockFn.callCount, 3)` (tests implementation, not behavior)
|
|
31
|
+
- **Error handling**: Missing error handling at system boundaries (user input, external APIs)
|
|
32
|
+
- Do NOT add error handling for internal code or impossible scenarios
|
|
33
|
+
|
|
34
|
+
### 3. Style Issues (NICE to fix)
|
|
35
|
+
- **Naming**: Inconsistent with existing project code style
|
|
36
|
+
- **Formatting**: Inconsistent indentation, bracket style
|
|
37
|
+
- **Comments**: Obvious comments that add no value, or missing comments on complex logic
|
|
38
|
+
|
|
39
|
+
## Review Method
|
|
40
|
+
|
|
41
|
+
1. Read the SUMMARY above to get list of changed/created files
|
|
42
|
+
2. For each file listed in key-files, read the FULL content
|
|
43
|
+
3. Compare naming/style with existing project files (read 2-3 similar existing files for comparison)
|
|
44
|
+
4. Check against framework patterns from Technology Context above
|
|
45
|
+
5. Only report issues with confidence >= 80%
|
|
46
|
+
|
|
47
|
+
## Confidence-Based Filtering
|
|
48
|
+
|
|
49
|
+
For each issue found, assign a confidence level:
|
|
50
|
+
- **95-100%**: Definite bug or security issue — always report
|
|
51
|
+
- **80-94%**: High confidence quality issue — report
|
|
52
|
+
- **60-79%**: Possible issue — skip (don't waste time on maybes)
|
|
53
|
+
- **<60%**: Opinion/preference — skip
|
|
54
|
+
|
|
55
|
+
ONLY include issues with confidence >= 80% in the review output.
|
|
56
|
+
|
|
57
|
+
## Output: REVIEW.md
|
|
58
|
+
|
|
59
|
+
Write results to `{{output_path}}`:
|
|
60
|
+
|
|
61
|
+
### YAML Frontmatter
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
---
|
|
65
|
+
phase: {{phase}}
|
|
66
|
+
plan: {{plan_number}}
|
|
67
|
+
status: passed | needs-work | critical
|
|
68
|
+
issues_count: 0
|
|
69
|
+
critical_count: 0
|
|
70
|
+
quality_count: 0
|
|
71
|
+
style_count: 0
|
|
72
|
+
reviewed_files: []
|
|
73
|
+
---
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Per-Issue Format
|
|
77
|
+
|
|
78
|
+
```markdown
|
|
79
|
+
### [CRITICAL|QUALITY|STYLE] Issue: {title}
|
|
80
|
+
**Confidence:** {80-100}%
|
|
81
|
+
**File:** {path}
|
|
82
|
+
**Lines:** {range}
|
|
83
|
+
**Problem:** {1-2 sentence description}
|
|
84
|
+
**Fix:** {concrete fix suggestion — not vague advice}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Status Determination
|
|
88
|
+
- **passed**: 0 critical issues, ≤2 quality issues — code is good to verify
|
|
89
|
+
- **needs-work**: 0 critical issues, 3+ quality issues — fix before proceeding
|
|
90
|
+
- **critical**: 1+ critical issues — MUST fix, do not proceed
|
|
91
|
+
|
|
92
|
+
## What NOT to Review
|
|
93
|
+
- Do NOT suggest adding docstrings/comments to code you didn't write
|
|
94
|
+
- Do NOT suggest refactoring code outside the changed files
|
|
95
|
+
- Do NOT suggest type annotations if the project doesn't use them
|
|
96
|
+
- Do NOT suggest error handling for internal code paths that can't fail
|
|
97
|
+
- Do NOT flag style preferences that aren't in the project's existing conventions
|
|
98
|
+
- Do NOT suggest "improvements" beyond what was asked — avoid scope creep
|
|
99
|
+
- When in doubt, the existing project code style WINS over your preferences
|
|
100
|
+
|
|
101
|
+
## Output Marker
|
|
102
|
+
|
|
103
|
+
When review is complete:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
## REVIEW COMPLETE
|
|
107
|
+
|
|
108
|
+
**Status:** [passed | needs-work | critical]
|
|
109
|
+
**Issues:** [N total: X critical, Y quality, Z style]
|
|
110
|
+
**Files reviewed:** [count]
|
|
111
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brain:review
|
|
3
|
+
description: Run code review on executed work before verification
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Bash
|
|
7
|
+
- Grep
|
|
8
|
+
- Glob
|
|
9
|
+
- Agent
|
|
10
|
+
---
|
|
11
|
+
<objective>
|
|
12
|
+
Review code quality after execution and before verification. Checks DRY, over-engineering,
|
|
13
|
+
framework best practices, and project code style consistency using confidence-based filtering.
|
|
14
|
+
</objective>
|
|
15
|
+
|
|
16
|
+
<critical-rules>
|
|
17
|
+
PIPELINE ENFORCEMENT (NON-NEGOTIABLE):
|
|
18
|
+
1. Run `npx brain-dev review` FIRST — before reading code, before analyzing, before suggesting fixes
|
|
19
|
+
2. READ the full output — it contains step-by-step instructions specific to this project's stack
|
|
20
|
+
3. FOLLOW the nextAction field — copy the exact command, do not modify it
|
|
21
|
+
4. If output shows an ERROR: follow the recovery path in the output, do NOT debug manually
|
|
22
|
+
5. If output shows a CHECKPOINT: use AskUserQuestion, do NOT pick options yourself
|
|
23
|
+
|
|
24
|
+
CONSEQUENCE OF SKIPPING: Pipeline tracks state, creates audit trails, spawns specialized agents,
|
|
25
|
+
and injects framework-specific expertise. Skipping it means: no state tracking, no audit trail,
|
|
26
|
+
no specialized agents, no framework guidance, potential data loss in .brain/ state.
|
|
27
|
+
</critical-rules>
|
|
28
|
+
|
|
29
|
+
<process>
|
|
30
|
+
1. Run: `npx brain-dev review [--phase N]`
|
|
31
|
+
2. Spawn brain-reviewer agent with the provided prompt
|
|
32
|
+
3. Review REVIEW.md output
|
|
33
|
+
4. If critical issues: re-execute fixes, then re-review
|
|
34
|
+
5. If passed or needs-work (≤2 issues): proceed to /brain:verify
|
|
35
|
+
</process>
|
package/hooks/bootstrap.sh
CHANGED
|
@@ -64,7 +64,7 @@ try {
|
|
|
64
64
|
'Phase: ' + (data.phase && data.phase.current || 0) + ' (' + (data.phase && data.phase.status || 'initialized') + ')',
|
|
65
65
|
'Next: ' + (data.nextAction || '/brain:new-project'),
|
|
66
66
|
'',
|
|
67
|
-
'Commands: /brain:new-project, /brain:story, /brain:discuss, /brain:plan, /brain:execute, /brain:verify, /brain:complete, /brain:quick, /brain:new-task, /brain:progress, /brain:pause, /brain:resume, /brain:help, /brain:health, /brain:update, /brain:upgrade, /brain:storm, /brain:adr, /brain:phase, /brain:config, /brain:map, /brain:recover, /brain:dashboard, /brain:auto (or execute --auto)',
|
|
67
|
+
'Commands: /brain:new-project, /brain:story, /brain:discuss, /brain:plan, /brain:execute, /brain:review, /brain:verify, /brain:complete, /brain:quick, /brain:new-task, /brain:progress, /brain:pause, /brain:resume, /brain:help, /brain:health, /brain:update, /brain:upgrade, /brain:storm, /brain:adr, /brain:phase, /brain:config, /brain:map, /brain:recover, /brain:dashboard, /brain:auto (or execute --auto)',
|
|
68
68
|
'',
|
|
69
69
|
'PIPELINE RULES (NON-NEGOTIABLE):',
|
|
70
70
|
'',
|