brain-dev 1.1.0 → 1.2.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/README.md +10 -0
- package/bin/brain-tools.cjs +6 -0
- package/bin/lib/commands/quick.cjs +1 -1
- package/bin/lib/commands/upgrade.cjs +48 -0
- package/bin/lib/commands/verify.cjs +3 -1
- package/bin/lib/commands.cjs +9 -0
- package/bin/lib/init.cjs +8 -0
- package/bin/lib/stack-expert.cjs +20 -8
- package/commands/brain/upgrade.md +20 -0
- package/hooks/bootstrap.sh +6 -1
- package/hooks/check-update.sh +37 -0
- package/hooks/statusline.sh +13 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -185,6 +185,16 @@ pending → discussing → discussed → planning → executing → executed →
|
|
|
185
185
|
|
|
186
186
|
Every command knows what comes next. `brain-dev progress` always tells you the right next step.
|
|
187
187
|
|
|
188
|
+
## Auto Update Notification
|
|
189
|
+
|
|
190
|
+
Brain checks for newer versions on session start (background, non-blocking). If an update is available, the statusline shows:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
⬆ /brain:upgrade | brain | [########--] 78% | P2: executing
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Type `/brain:upgrade` to see the upgrade command and clear the notification.
|
|
197
|
+
|
|
188
198
|
## Zero Dependencies
|
|
189
199
|
|
|
190
200
|
Brain is a single npm package with zero runtime dependencies. Just Node.js 22+.
|
package/bin/brain-tools.cjs
CHANGED
|
@@ -178,6 +178,12 @@ async function main() {
|
|
|
178
178
|
await require('./lib/commands/update.cjs').run(args.slice(1));
|
|
179
179
|
break;
|
|
180
180
|
|
|
181
|
+
case 'upgrade':
|
|
182
|
+
await require('./lib/commands/upgrade.cjs').run(args.slice(1), {
|
|
183
|
+
brainDir: path.join(process.cwd(), '.brain')
|
|
184
|
+
});
|
|
185
|
+
break;
|
|
186
|
+
|
|
181
187
|
case 'health':
|
|
182
188
|
await require('./lib/commands/health.cjs').run(args.slice(1));
|
|
183
189
|
break;
|
|
@@ -165,7 +165,7 @@ function handleInit(args, brainDir, state) {
|
|
|
165
165
|
output_dir: taskDir,
|
|
166
166
|
phase_number_padded: String(nextNum).padStart(3, '0'),
|
|
167
167
|
phase_slug: slug,
|
|
168
|
-
stack_expertise: generateExpertise(brainDir, '
|
|
168
|
+
stack_expertise: generateExpertise(brainDir, 'planner')
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
const quickConstraints = [
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { output, prefix } = require('../core.cjs');
|
|
5
|
+
|
|
6
|
+
async function run(args = [], opts = {}) {
|
|
7
|
+
const brainDir = opts.brainDir || path.join(process.cwd(), '.brain');
|
|
8
|
+
|
|
9
|
+
// Read cache
|
|
10
|
+
const cachePath = path.join(brainDir, '.update-check.json');
|
|
11
|
+
let cache = null;
|
|
12
|
+
try {
|
|
13
|
+
cache = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
14
|
+
} catch {}
|
|
15
|
+
|
|
16
|
+
if (!cache || !cache.update_available) {
|
|
17
|
+
const lines = [
|
|
18
|
+
prefix('You are on the latest version.'),
|
|
19
|
+
prefix(`Installed: ${cache?.installed || 'unknown'}`)
|
|
20
|
+
];
|
|
21
|
+
output({ action: 'up-to-date', installed: cache?.installed || 'unknown' }, lines.join('\n'));
|
|
22
|
+
return { action: 'up-to-date' };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Show upgrade info
|
|
26
|
+
const lines = [
|
|
27
|
+
prefix(`Update available: ${cache.installed} → ${cache.latest}`),
|
|
28
|
+
'',
|
|
29
|
+
prefix('To upgrade, run:'),
|
|
30
|
+
prefix(` npx brain-dev@${cache.latest} update`),
|
|
31
|
+
'',
|
|
32
|
+
prefix('Or update globally:'),
|
|
33
|
+
prefix(' npm install -g brain-dev@latest'),
|
|
34
|
+
'',
|
|
35
|
+
prefix('After updating, restart Claude Code to pick up new features.')
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
output({
|
|
39
|
+
action: 'upgrade-available',
|
|
40
|
+
installed: cache.installed,
|
|
41
|
+
latest: cache.latest,
|
|
42
|
+
command: `npx brain-dev@${cache.latest} update`
|
|
43
|
+
}, lines.join('\n'));
|
|
44
|
+
|
|
45
|
+
return { action: 'upgrade-available', installed: cache.installed, latest: cache.latest };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { run };
|
|
@@ -10,6 +10,7 @@ const { output, error, success } = require('../core.cjs');
|
|
|
10
10
|
const antiPatterns = require('../anti-patterns.cjs');
|
|
11
11
|
const { buildDebuggerSpawnInstructions } = require('./execute.cjs');
|
|
12
12
|
const { isPathWithinRoot } = require('../security.cjs');
|
|
13
|
+
const { generateExpertise } = require('../stack-expert.cjs');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Find a phase directory under .brain/phases/ matching a phase number.
|
|
@@ -271,7 +272,8 @@ async function run(args = [], opts = {}) {
|
|
|
271
272
|
must_haves: mustHavesFormatted,
|
|
272
273
|
output_path: outputPath,
|
|
273
274
|
anti_pattern_results: formatAntiPatternResults(apResults),
|
|
274
|
-
nyquist_section: nyquistSection
|
|
275
|
+
nyquist_section: nyquistSection,
|
|
276
|
+
stack_expertise: generateExpertise(brainDir, 'verifier')
|
|
275
277
|
});
|
|
276
278
|
|
|
277
279
|
// Read depth config from state (defaults to 'deep' for backward compatibility)
|
package/bin/lib/commands.cjs
CHANGED
|
@@ -234,6 +234,15 @@ const COMMANDS = [
|
|
|
234
234
|
needsState: false,
|
|
235
235
|
args: ' --fix Enable aggressive repair of hooks and templates\n --quick Run safe checks only (used by bootstrap)\n --json Force JSON output'
|
|
236
236
|
},
|
|
237
|
+
{
|
|
238
|
+
name: 'upgrade',
|
|
239
|
+
description: 'Check and apply Brain updates',
|
|
240
|
+
usage: 'brain-dev upgrade',
|
|
241
|
+
group: 'Meta',
|
|
242
|
+
implemented: true,
|
|
243
|
+
needsState: false,
|
|
244
|
+
args: ''
|
|
245
|
+
},
|
|
237
246
|
{
|
|
238
247
|
name: 'version',
|
|
239
248
|
description: 'Show brain-dev version',
|
package/bin/lib/init.cjs
CHANGED
|
@@ -249,10 +249,18 @@ async function run(args = []) {
|
|
|
249
249
|
fs.chmodSync(path.join(brainDir, 'hooks', 'post-tool-use.sh'), 0o755);
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
// Copy update check hook
|
|
253
|
+
const checkUpdateSrc = packagePath('hooks', 'check-update.sh');
|
|
254
|
+
if (fs.existsSync(checkUpdateSrc)) {
|
|
255
|
+
fs.copyFileSync(checkUpdateSrc, path.join(brainDir, 'hooks', 'check-update.sh'));
|
|
256
|
+
fs.chmodSync(path.join(brainDir, 'hooks', 'check-update.sh'), 0o755);
|
|
257
|
+
}
|
|
258
|
+
|
|
252
259
|
// Write .gitignore
|
|
253
260
|
const gitignoreContent = [
|
|
254
261
|
'*.tmp',
|
|
255
262
|
'*.lock',
|
|
263
|
+
'.update-check.json',
|
|
256
264
|
'storm/fragments/',
|
|
257
265
|
'storm/events.jsonl',
|
|
258
266
|
''
|
package/bin/lib/stack-expert.cjs
CHANGED
|
@@ -201,22 +201,27 @@ function generateExpertise(brainDir, role = 'general') {
|
|
|
201
201
|
const conventions = fs.readFileSync(conventionsPath, 'utf8');
|
|
202
202
|
const excerpt = conventions.slice(0, 2000); // Keep concise
|
|
203
203
|
sections.push('### Project-Specific Conventions (from codebase analysis)');
|
|
204
|
+
sections.push('```conventions');
|
|
204
205
|
sections.push(excerpt);
|
|
206
|
+
sections.push('```');
|
|
205
207
|
sections.push('');
|
|
206
208
|
}
|
|
207
209
|
|
|
208
|
-
if (role === 'planner' && fs.existsSync(stackPath)) {
|
|
210
|
+
if ((role === 'planner' || role === 'executor') && fs.existsSync(stackPath)) {
|
|
209
211
|
const stackAnalysis = fs.readFileSync(stackPath, 'utf8');
|
|
210
212
|
const excerpt = stackAnalysis.slice(0, 1500);
|
|
211
213
|
sections.push('### Existing Stack Details');
|
|
214
|
+
sections.push('```stack');
|
|
212
215
|
sections.push(excerpt);
|
|
216
|
+
sections.push('```');
|
|
213
217
|
sections.push('');
|
|
214
218
|
}
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
// LAYER 3: Context7 live documentation queries
|
|
218
222
|
if (framework || lang) {
|
|
219
|
-
|
|
223
|
+
const ctx7 = generateContext7Queries(detection);
|
|
224
|
+
if (ctx7) sections.push(ctx7);
|
|
220
225
|
}
|
|
221
226
|
|
|
222
227
|
// Unknown framework: Context7 becomes PRIMARY (not fallback)
|
|
@@ -262,10 +267,15 @@ function generateExpertise(brainDir, role = 'general') {
|
|
|
262
267
|
* @returns {object|null} Framework pattern object or null
|
|
263
268
|
*/
|
|
264
269
|
function findFrameworkPattern(name) {
|
|
265
|
-
|
|
270
|
+
if (!name) return null;
|
|
271
|
+
const lower = name.toLowerCase();
|
|
272
|
+
// Exact match first
|
|
266
273
|
for (const [key, value] of Object.entries(STACK_PATTERNS.frameworks)) {
|
|
267
|
-
if (key.toLowerCase() ===
|
|
268
|
-
|
|
274
|
+
if (key.toLowerCase() === lower) return value;
|
|
275
|
+
}
|
|
276
|
+
// startsWith match (e.g., "Next.js 15" matches "Next.js")
|
|
277
|
+
for (const [key, value] of Object.entries(STACK_PATTERNS.frameworks)) {
|
|
278
|
+
if (lower.startsWith(key.toLowerCase()) || key.toLowerCase().startsWith(lower)) {
|
|
269
279
|
return value;
|
|
270
280
|
}
|
|
271
281
|
}
|
|
@@ -274,11 +284,13 @@ function findFrameworkPattern(name) {
|
|
|
274
284
|
|
|
275
285
|
/**
|
|
276
286
|
* Generate Context7 lookup instructions for the detected stack.
|
|
277
|
-
* @param {string} brainDir
|
|
287
|
+
* @param {object|string} detectionOrBrainDir - Parsed detection object or brainDir path
|
|
278
288
|
* @returns {string} Context7 query instructions
|
|
279
289
|
*/
|
|
280
|
-
function generateContext7Queries(
|
|
281
|
-
const detection =
|
|
290
|
+
function generateContext7Queries(detectionOrBrainDir) {
|
|
291
|
+
const detection = typeof detectionOrBrainDir === 'string'
|
|
292
|
+
? readDetection(detectionOrBrainDir)
|
|
293
|
+
: detectionOrBrainDir;
|
|
282
294
|
if (!detection) return '';
|
|
283
295
|
|
|
284
296
|
const framework = detection.stack?.framework || detection.stack?.primary?.framework;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brain:upgrade
|
|
3
|
+
description: Check for and apply Brain updates
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Bash
|
|
7
|
+
---
|
|
8
|
+
<objective>
|
|
9
|
+
Check if a newer version of brain-dev is available and show upgrade instructions.
|
|
10
|
+
</objective>
|
|
11
|
+
|
|
12
|
+
<process>
|
|
13
|
+
1. Run: `npx brain-dev upgrade`
|
|
14
|
+
2. Brain will:
|
|
15
|
+
- Check the cached update status from `.brain/.update-check.json`
|
|
16
|
+
- Show current vs latest version if an update is available
|
|
17
|
+
- Provide the exact command to run for upgrading
|
|
18
|
+
- Clear the update notification from the statusline
|
|
19
|
+
3. If already up to date, confirms the current version.
|
|
20
|
+
</process>
|
package/hooks/bootstrap.sh
CHANGED
|
@@ -48,7 +48,7 @@ try {
|
|
|
48
48
|
'Phase: ' + (data.phase && data.phase.current || 0) + ' (' + (data.phase && data.phase.status || 'initialized') + ')',
|
|
49
49
|
'Next: ' + (data.nextAction || '/brain:new-project'),
|
|
50
50
|
'',
|
|
51
|
-
'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:storm, /brain:adr, /brain:phase, /brain:config, /brain:map, /brain:recover, /brain:dashboard, /brain:auto (or execute --auto)',
|
|
51
|
+
'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)',
|
|
52
52
|
'',
|
|
53
53
|
'Instructions for Claude:',
|
|
54
54
|
'- When user types /brain:<command>, run: npx brain-dev <command> [args]',
|
|
@@ -66,3 +66,8 @@ try {
|
|
|
66
66
|
if [ -f "$BRAIN_DIR/brain.json" ]; then
|
|
67
67
|
npx brain-dev health --quick 2>/dev/null
|
|
68
68
|
fi
|
|
69
|
+
|
|
70
|
+
# Background update check (non-blocking)
|
|
71
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
72
|
+
bash "$SCRIPT_DIR/check-update.sh" "$BRAIN_DIR" &>/dev/null &
|
|
73
|
+
disown
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Background npm version check — writes result to .brain/.update-check.json
|
|
3
|
+
# Called by bootstrap.sh with & (background, non-blocking)
|
|
4
|
+
|
|
5
|
+
BRAIN_DIR="${1:-.brain}"
|
|
6
|
+
CACHE_FILE="$BRAIN_DIR/.update-check.json"
|
|
7
|
+
|
|
8
|
+
# Read installed version from package
|
|
9
|
+
INSTALLED=$(node -e "try{console.log(require('brain-dev/package.json').version)}catch{console.log('unknown')}" 2>/dev/null)
|
|
10
|
+
if [ "$INSTALLED" = "unknown" ] || [ -z "$INSTALLED" ]; then
|
|
11
|
+
INSTALLED=$(npx brain-dev version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Query npm for latest (10 second timeout)
|
|
15
|
+
LATEST=$(timeout 10 npm view brain-dev version 2>/dev/null)
|
|
16
|
+
|
|
17
|
+
if [ -z "$LATEST" ]; then
|
|
18
|
+
# npm unavailable or timeout — skip silently
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Trim whitespace to prevent false positives
|
|
23
|
+
INSTALLED=$(echo "$INSTALLED" | tr -d '[:space:]')
|
|
24
|
+
LATEST=$(echo "$LATEST" | tr -d '[:space:]')
|
|
25
|
+
|
|
26
|
+
# Write cache safely using node (prevents JSON injection from malformed version strings)
|
|
27
|
+
node -e "
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const installed = process.argv[1];
|
|
30
|
+
const latest = process.argv[2];
|
|
31
|
+
fs.writeFileSync(process.argv[3], JSON.stringify({
|
|
32
|
+
update_available: installed !== latest,
|
|
33
|
+
installed: installed,
|
|
34
|
+
latest: latest,
|
|
35
|
+
checked: Math.floor(Date.now() / 1000)
|
|
36
|
+
}));
|
|
37
|
+
" "$INSTALLED" "$LATEST" "$CACHE_FILE"
|
package/hooks/statusline.sh
CHANGED
|
@@ -125,6 +125,18 @@ node -e "
|
|
|
125
125
|
phaseDisplay = ' | P' + phaseCurrent + (phaseStatus ? ': ' + phaseStatus : '');
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
// Read update cache for upgrade notification
|
|
129
|
+
let updatePrefix = '';
|
|
130
|
+
try {
|
|
131
|
+
const cachePath = '$BRAIN_DIR/.update-check.json';
|
|
132
|
+
if (fs.existsSync(cachePath)) {
|
|
133
|
+
const cache = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
134
|
+
if (cache.update_available === true) {
|
|
135
|
+
updatePrefix = '\x1b[33m\u2B06 /brain:upgrade\x1b[0m | ';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch {}
|
|
139
|
+
|
|
128
140
|
// Output status line to stdout
|
|
129
|
-
process.stdout.write(projectName + phaseDisplay + ' | ' + bar);
|
|
141
|
+
process.stdout.write(updatePrefix + projectName + phaseDisplay + ' | ' + bar);
|
|
130
142
|
" "$input" 2>/dev/null || echo "brain | ---"
|