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 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+.
@@ -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, 'executor')
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)
@@ -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
  ''
@@ -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
- sections.push(generateContext7Queries(brainDir));
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
- // Case-insensitive, partial match
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() === name.toLowerCase() ||
268
- name.toLowerCase().includes(key.toLowerCase())) {
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(brainDir) {
281
- const detection = readDetection(brainDir);
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>
@@ -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"
@@ -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 | ---"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brain-dev",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "AI-powered development workflow orchestrator",
5
5
  "author": "halilcosdu",
6
6
  "license": "MIT",