brain-dev 0.1.1 → 0.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/bin/brain-tools.cjs +21 -1
- package/bin/lib/bridge.cjs +47 -0
- package/bin/lib/commands/auto.cjs +337 -0
- package/bin/lib/commands/dashboard.cjs +177 -0
- package/bin/lib/commands/execute.cjs +18 -0
- package/bin/lib/commands/progress.cjs +37 -1
- package/bin/lib/commands/recover.cjs +155 -0
- package/bin/lib/commands/update.cjs +148 -0
- package/bin/lib/commands/verify.cjs +15 -5
- package/bin/lib/commands.cjs +27 -0
- package/bin/lib/config.cjs +23 -2
- package/bin/lib/context.cjs +397 -0
- package/bin/lib/cost.cjs +273 -0
- package/bin/lib/dashboard-collector.cjs +98 -0
- package/bin/lib/dashboard-server.cjs +33 -0
- package/bin/lib/hook-dispatcher.cjs +99 -0
- package/bin/lib/init.cjs +1 -1
- package/bin/lib/lock.cjs +163 -0
- package/bin/lib/logger.cjs +18 -0
- package/bin/lib/recovery.cjs +468 -0
- package/bin/lib/security.cjs +16 -2
- package/bin/lib/state.cjs +118 -8
- package/bin/lib/stuck.cjs +269 -0
- package/bin/lib/tokens.cjs +32 -0
- package/commands/brain/auto.md +31 -0
- package/commands/brain/dashboard.md +18 -0
- package/commands/brain/recover.md +19 -0
- package/commands/brain/update.md +22 -0
- package/hooks/bootstrap.sh +15 -1
- package/package.json +1 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { readLog, tailLog } = require('./logger.cjs');
|
|
6
|
+
const { writeBridge, readBridge } = require('./bridge.cjs');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect progress by checking multiple signals.
|
|
10
|
+
* @param {string} brainDir
|
|
11
|
+
* @param {number} phaseNumber
|
|
12
|
+
* @returns {{ lastActivity: string|null, idleSeconds: number, signals: object, isIdle: boolean }}
|
|
13
|
+
*/
|
|
14
|
+
function detectProgress(brainDir, phaseNumber) {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
let lastActivity = null;
|
|
17
|
+
const signals = { events: null, files: null, commits: null };
|
|
18
|
+
|
|
19
|
+
// Signal 1: Last JSONL event timestamp
|
|
20
|
+
const lastEventTs = getLastEventTimestamp(brainDir, phaseNumber);
|
|
21
|
+
if (lastEventTs) {
|
|
22
|
+
signals.events = lastEventTs;
|
|
23
|
+
if (!lastActivity || new Date(lastEventTs) > new Date(lastActivity)) {
|
|
24
|
+
lastActivity = lastEventTs;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Signal 2: Phase directory file modification times
|
|
29
|
+
const phaseDir = findPhaseDir(brainDir, phaseNumber);
|
|
30
|
+
if (phaseDir) {
|
|
31
|
+
try {
|
|
32
|
+
const files = fs.readdirSync(phaseDir);
|
|
33
|
+
for (const f of files) {
|
|
34
|
+
const stat = fs.statSync(path.join(phaseDir, f));
|
|
35
|
+
const mtime = stat.mtime.toISOString();
|
|
36
|
+
if (!signals.files || new Date(mtime) > new Date(signals.files)) {
|
|
37
|
+
signals.files = mtime;
|
|
38
|
+
}
|
|
39
|
+
if (!lastActivity || new Date(mtime) > new Date(lastActivity)) {
|
|
40
|
+
lastActivity = mtime;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch { /* phase dir may not exist */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const idleSeconds = lastActivity ? Math.round((now - new Date(lastActivity).getTime()) / 1000) : Infinity;
|
|
47
|
+
const isIdle = idleSeconds > 300; // default 5 minutes
|
|
48
|
+
|
|
49
|
+
return { lastActivity, idleSeconds, signals, isIdle };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check timeout tiers based on elapsed time and configuration.
|
|
54
|
+
* @param {string} brainDir
|
|
55
|
+
* @param {object} state - brain.json state
|
|
56
|
+
* @returns {{ tier: 'none'|'soft'|'idle'|'hard', elapsedMinutes: number, idleMinutes: number, message: string|null }}
|
|
57
|
+
*/
|
|
58
|
+
function checkTimeouts(brainDir, state) {
|
|
59
|
+
const timeoutConfig = state.timeout || { soft: 20, idle: 5, hard: 45, enabled: true };
|
|
60
|
+
|
|
61
|
+
if (!timeoutConfig.enabled) {
|
|
62
|
+
return { tier: 'none', elapsedMinutes: 0, idleMinutes: 0, message: null };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const startedAt = state.phase?.execution_started_at;
|
|
66
|
+
if (!startedAt) {
|
|
67
|
+
return { tier: 'none', elapsedMinutes: 0, idleMinutes: 0, message: null };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const elapsedMs = Date.now() - new Date(startedAt).getTime();
|
|
71
|
+
const elapsedMinutes = Math.round(elapsedMs / 60000);
|
|
72
|
+
|
|
73
|
+
// Check progress for idle detection
|
|
74
|
+
const progress = detectProgress(brainDir, state.phase?.current || 1);
|
|
75
|
+
const idleMinutes = Math.round(progress.idleSeconds / 60);
|
|
76
|
+
|
|
77
|
+
// Hard timeout takes precedence
|
|
78
|
+
if (elapsedMinutes >= timeoutConfig.hard) {
|
|
79
|
+
return {
|
|
80
|
+
tier: 'hard',
|
|
81
|
+
elapsedMinutes,
|
|
82
|
+
idleMinutes,
|
|
83
|
+
message: `TIME LIMIT REACHED (${elapsedMinutes}m). Execute recovery protocol: commit all WIP, write SUMMARY with status "partial", output ## EXECUTION PARTIAL`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Idle timeout
|
|
88
|
+
if (idleMinutes >= timeoutConfig.idle) {
|
|
89
|
+
return {
|
|
90
|
+
tier: 'idle',
|
|
91
|
+
elapsedMinutes,
|
|
92
|
+
idleMinutes,
|
|
93
|
+
message: `No progress detected for ${idleMinutes}m. Agent may be stuck. Check output or extend timeout: brain-dev config set timeout.idle ${timeoutConfig.idle * 2}`
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Soft timeout
|
|
98
|
+
if (elapsedMinutes >= timeoutConfig.soft) {
|
|
99
|
+
return {
|
|
100
|
+
tier: 'soft',
|
|
101
|
+
elapsedMinutes,
|
|
102
|
+
idleMinutes,
|
|
103
|
+
message: `Agent running for ${elapsedMinutes}m (soft timeout: ${timeoutConfig.soft}m). Consider wrapping up current task.`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { tier: 'none', elapsedMinutes, idleMinutes, message: null };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check for execution non-convergence.
|
|
112
|
+
* Detects: same plan attempted 3+ times, repeated spawns without spot-check pass.
|
|
113
|
+
* @param {string} brainDir
|
|
114
|
+
* @param {number} phaseNumber
|
|
115
|
+
* @returns {{ converging: boolean, reason: string|null, repeatCount: number }}
|
|
116
|
+
*/
|
|
117
|
+
function checkExecutionConvergence(brainDir, phaseNumber) {
|
|
118
|
+
const events = readLog(brainDir, phaseNumber);
|
|
119
|
+
|
|
120
|
+
// Count spawn events per plan
|
|
121
|
+
const spawnsByPlan = {};
|
|
122
|
+
const passedPlans = new Set();
|
|
123
|
+
|
|
124
|
+
for (const event of events) {
|
|
125
|
+
if (event.type === 'spawn' && event.agent === 'executor' && event.plan) {
|
|
126
|
+
spawnsByPlan[event.plan] = (spawnsByPlan[event.plan] || 0) + 1;
|
|
127
|
+
}
|
|
128
|
+
if (event.type === 'spot-check' && event.passed && event.plan) {
|
|
129
|
+
passedPlans.add(event.plan);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check for repeated attempts on the same plan that haven't passed
|
|
134
|
+
for (const [plan, count] of Object.entries(spawnsByPlan)) {
|
|
135
|
+
if (count >= 3 && !passedPlans.has(plan)) {
|
|
136
|
+
return {
|
|
137
|
+
converging: false,
|
|
138
|
+
reason: `Plan ${plan} attempted ${count} times without passing spot-check`,
|
|
139
|
+
repeatCount: count
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { converging: true, reason: null, repeatCount: 0 };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Capture diagnostic information when stuck is detected.
|
|
149
|
+
* Writes to .brain/debug/stuck-{phase}-{timestamp}.md
|
|
150
|
+
* @param {string} brainDir
|
|
151
|
+
* @param {number} phaseNumber
|
|
152
|
+
* @param {object} stuckInfo - { tier, elapsedMinutes, idleMinutes, message }
|
|
153
|
+
* @returns {string} Path to diagnostic file
|
|
154
|
+
*/
|
|
155
|
+
function captureDiagnostics(brainDir, phaseNumber, stuckInfo) {
|
|
156
|
+
const debugDir = path.join(brainDir, 'debug');
|
|
157
|
+
try { fs.mkdirSync(debugDir, { recursive: true }); } catch {}
|
|
158
|
+
|
|
159
|
+
const timestamp = new Date().toISOString().replace(/:/g, '-').slice(0, 19);
|
|
160
|
+
const filename = `stuck-${String(phaseNumber).padStart(2, '0')}-${timestamp}.md`;
|
|
161
|
+
const filePath = path.join(debugDir, filename);
|
|
162
|
+
|
|
163
|
+
const events = tailLog(brainDir, phaseNumber, null);
|
|
164
|
+
const convergence = checkExecutionConvergence(brainDir, phaseNumber);
|
|
165
|
+
|
|
166
|
+
const content = `---
|
|
167
|
+
phase: ${phaseNumber}
|
|
168
|
+
triggered_at: ${new Date().toISOString()}
|
|
169
|
+
elapsed_minutes: ${stuckInfo.elapsedMinutes}
|
|
170
|
+
idle_minutes: ${stuckInfo.idleMinutes}
|
|
171
|
+
timeout_tier: ${stuckInfo.tier}
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
# Stuck Diagnostic
|
|
175
|
+
|
|
176
|
+
## Timeout Info
|
|
177
|
+
- Tier: ${stuckInfo.tier}
|
|
178
|
+
- Elapsed: ${stuckInfo.elapsedMinutes} minutes
|
|
179
|
+
- Idle: ${stuckInfo.idleMinutes} minutes
|
|
180
|
+
- Message: ${stuckInfo.message || 'none'}
|
|
181
|
+
|
|
182
|
+
## Convergence
|
|
183
|
+
- Converging: ${convergence.converging}
|
|
184
|
+
${convergence.reason ? `- Issue: ${convergence.reason}` : ''}
|
|
185
|
+
|
|
186
|
+
## Last ${events.length} Events
|
|
187
|
+
${events.map(e => `- ${e.timestamp || '?'} [${e.type || '?'}] ${JSON.stringify(e)}`).join('\n')}
|
|
188
|
+
|
|
189
|
+
## Recommendation
|
|
190
|
+
${stuckInfo.tier === 'hard' ? 'Agent should commit partial work and output EXECUTION PARTIAL.' : ''}
|
|
191
|
+
${stuckInfo.tier === 'idle' ? 'Check if agent is waiting for user input or stuck on an error.' : ''}
|
|
192
|
+
${!convergence.converging ? `Non-convergent: ${convergence.reason}. Consider manual intervention.` : ''}
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
fs.writeFileSync(filePath, content);
|
|
196
|
+
return filePath;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Build wrap-up instructions for injection via PostToolUse hook.
|
|
201
|
+
* @param {number} phase
|
|
202
|
+
* @param {string|null} plan
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
function buildWrapUpInstructions(phase, plan) {
|
|
206
|
+
const planId = plan ? `P${phase}-${plan}` : `P${phase}`;
|
|
207
|
+
return `[brain] TIME LIMIT REACHED. Execute the following recovery protocol:
|
|
208
|
+
1. Stop current task implementation
|
|
209
|
+
2. Commit all work in progress with message "wip(${planId}): partial - stuck timeout"
|
|
210
|
+
3. Write SUMMARY.md with status "partial" and list completed vs remaining tasks
|
|
211
|
+
4. Output ## EXECUTION PARTIAL with the same structure as EXECUTION FAILED`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get the timestamp of the most recent JSONL event.
|
|
216
|
+
* @param {string} brainDir
|
|
217
|
+
* @param {number} phaseNumber
|
|
218
|
+
* @returns {string|null} ISO timestamp or null
|
|
219
|
+
*/
|
|
220
|
+
function getLastEventTimestamp(brainDir, phaseNumber) {
|
|
221
|
+
const events = readLog(brainDir, phaseNumber);
|
|
222
|
+
if (events.length === 0) return null;
|
|
223
|
+
const last = events[events.length - 1];
|
|
224
|
+
return last.timestamp || null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Find the phase directory for a given phase number.
|
|
229
|
+
* @param {string} brainDir
|
|
230
|
+
* @param {number} phaseNumber
|
|
231
|
+
* @returns {string|null}
|
|
232
|
+
*/
|
|
233
|
+
function findPhaseDir(brainDir, phaseNumber) {
|
|
234
|
+
const phasesDir = path.join(brainDir, 'phases');
|
|
235
|
+
if (!fs.existsSync(phasesDir)) return null;
|
|
236
|
+
const padded = String(phaseNumber).padStart(2, '0');
|
|
237
|
+
const dirs = fs.readdirSync(phasesDir).filter(d => d.startsWith(padded + '-'));
|
|
238
|
+
return dirs.length > 0 ? path.join(phasesDir, dirs[0]) : null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Update the stuck bridge file with current stuck detection state.
|
|
243
|
+
* Called from hook-dispatcher during StatusLine.
|
|
244
|
+
* @param {string} brainDir
|
|
245
|
+
* @param {object} state
|
|
246
|
+
*/
|
|
247
|
+
function updateStuckBridge(brainDir, state) {
|
|
248
|
+
const timeoutResult = checkTimeouts(brainDir, state);
|
|
249
|
+
writeBridge(brainDir, 'stuck', {
|
|
250
|
+
phase_started_at: state.phase?.execution_started_at || null,
|
|
251
|
+
last_activity_at: detectProgress(brainDir, state.phase?.current || 1).lastActivity,
|
|
252
|
+
elapsed_minutes: timeoutResult.elapsedMinutes,
|
|
253
|
+
idle_minutes: timeoutResult.idleMinutes,
|
|
254
|
+
timeout_tier: timeoutResult.tier,
|
|
255
|
+
message: timeoutResult.message,
|
|
256
|
+
wrap_up: timeoutResult.tier === 'hard'
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = {
|
|
261
|
+
detectProgress,
|
|
262
|
+
checkTimeouts,
|
|
263
|
+
checkExecutionConvergence,
|
|
264
|
+
captureDiagnostics,
|
|
265
|
+
buildWrapUpInstructions,
|
|
266
|
+
getLastEventTimestamp,
|
|
267
|
+
findPhaseDir,
|
|
268
|
+
updateStuckBridge
|
|
269
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
|
|
5
|
+
const CHARS_PER_TOKEN = 4;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Estimate token count from text content.
|
|
9
|
+
* Uses chars/4 heuristic (~15-20% accuracy for English + code).
|
|
10
|
+
* @param {string} text
|
|
11
|
+
* @returns {number}
|
|
12
|
+
*/
|
|
13
|
+
function estimateTokens(text) {
|
|
14
|
+
if (!text) return 0;
|
|
15
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Estimate token count from a file on disk.
|
|
20
|
+
* @param {string} filePath - Absolute path to file
|
|
21
|
+
* @returns {number} Estimated tokens, 0 if file missing
|
|
22
|
+
*/
|
|
23
|
+
function estimateFileTokens(filePath) {
|
|
24
|
+
try {
|
|
25
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
26
|
+
return estimateTokens(content);
|
|
27
|
+
} catch {
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { estimateTokens, estimateFileTokens, CHARS_PER_TOKEN };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brain:auto
|
|
3
|
+
description: Run autonomous phase execution (discuss -> plan -> execute -> verify -> complete loop)
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Write
|
|
7
|
+
- Edit
|
|
8
|
+
- Bash
|
|
9
|
+
- Grep
|
|
10
|
+
- Glob
|
|
11
|
+
- Agent
|
|
12
|
+
- AskUserQuestion
|
|
13
|
+
---
|
|
14
|
+
<objective>
|
|
15
|
+
Execute multiple phases automatically without human intervention.
|
|
16
|
+
Chains: discuss -> plan -> execute -> verify -> complete -> next phase.
|
|
17
|
+
</objective>
|
|
18
|
+
|
|
19
|
+
<context>
|
|
20
|
+
Auto mode generates a runbook of steps. Follow each step's instructions sequentially.
|
|
21
|
+
Each step tells you which agent to spawn and what prompt to use.
|
|
22
|
+
</context>
|
|
23
|
+
|
|
24
|
+
<process>
|
|
25
|
+
1. Run: `npx brain-dev execute --auto` (or `--dry-run` to preview)
|
|
26
|
+
2. Follow the generated runbook instructions step by step
|
|
27
|
+
3. Each step outputs the exact `npx brain-dev` command to run next
|
|
28
|
+
4. On error: retry once, then run `npx brain-dev execute --auto --stop`
|
|
29
|
+
5. Check progress: `npx brain-dev progress --cost`
|
|
30
|
+
6. When done or stopped: `npx brain-dev execute --auto --stop` to release the lock
|
|
31
|
+
</process>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brain:dashboard
|
|
3
|
+
description: Live monitoring dashboard showing phase progress, costs, and stuck detection
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Bash
|
|
7
|
+
---
|
|
8
|
+
<objective>
|
|
9
|
+
Display a live dashboard with phase progress, cost tracking, stuck detection, and recent events.
|
|
10
|
+
Default mode shows a terminal (TUI) dashboard. Use --browser for web view.
|
|
11
|
+
</objective>
|
|
12
|
+
|
|
13
|
+
<process>
|
|
14
|
+
1. Run: `npx brain-dev dashboard`
|
|
15
|
+
2. Review the dashboard output
|
|
16
|
+
3. Use `npx brain-dev dashboard --browser` for the web dashboard
|
|
17
|
+
4. Use `npx brain-dev dashboard --stop` to stop a running web server
|
|
18
|
+
</process>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brain:recover
|
|
3
|
+
description: Detect and recover from crashes or interrupted sessions
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Write
|
|
7
|
+
- Bash
|
|
8
|
+
- AskUserQuestion
|
|
9
|
+
---
|
|
10
|
+
<objective>
|
|
11
|
+
Check for interrupted sessions and recover safely.
|
|
12
|
+
Default mode shows what happened. Use --fix to auto-resume or --rollback to revert.
|
|
13
|
+
</objective>
|
|
14
|
+
|
|
15
|
+
<process>
|
|
16
|
+
1. Run: `npx brain-dev recover`
|
|
17
|
+
2. Review the recovery briefing
|
|
18
|
+
3. Choose action: `npx brain-dev recover --fix` or `npx brain-dev recover --rollback` or `npx brain-dev recover --dismiss`
|
|
19
|
+
</process>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brain:update
|
|
3
|
+
description: Update brain tool files while preserving project state
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Bash
|
|
7
|
+
---
|
|
8
|
+
<objective>
|
|
9
|
+
Update brain's hooks, agents, and commands to the latest version without losing project state (brain.json, phases, roadmap, etc.).
|
|
10
|
+
</objective>
|
|
11
|
+
|
|
12
|
+
<process>
|
|
13
|
+
1. Run: `npx brain-dev@latest update`
|
|
14
|
+
2. Brain will:
|
|
15
|
+
- Backup brain.json to brain.json.pre-update
|
|
16
|
+
- Update hook scripts (.brain/hooks/)
|
|
17
|
+
- Update agent definitions (.claude/agents/)
|
|
18
|
+
- Update command files (.claude/commands/brain/)
|
|
19
|
+
- Migrate brain.json schema (add new fields, preserve existing)
|
|
20
|
+
- Regenerate STATE.md
|
|
21
|
+
3. All project data is preserved: phases, plans, roadmap, research, quick tasks.
|
|
22
|
+
</process>
|
package/hooks/bootstrap.sh
CHANGED
|
@@ -13,6 +13,20 @@ if [ ! -f "$BRAIN_DIR/brain.json" ]; then
|
|
|
13
13
|
exit 0
|
|
14
14
|
fi
|
|
15
15
|
|
|
16
|
+
# Crash recovery check
|
|
17
|
+
RECOVERY=$(npx brain-dev recover --json 2>/dev/null)
|
|
18
|
+
if [ $? -eq 0 ] && echo "$RECOVERY" | node -e "
|
|
19
|
+
try {
|
|
20
|
+
let d=''; process.stdin.on('data',c=>d+=c); process.stdin.on('end',()=>{
|
|
21
|
+
const r=JSON.parse(d);
|
|
22
|
+
if(r.action==='recovery-detected') { console.log('[brain] WARNING: Previous session ended unexpectedly. Run /brain:recover to check state.'); process.exit(0); }
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
} catch { process.exit(1); }
|
|
26
|
+
" 2>/dev/null; then
|
|
27
|
+
true # Warning was printed by the inline script
|
|
28
|
+
fi
|
|
29
|
+
|
|
16
30
|
# Get status and format for context injection
|
|
17
31
|
# Use npx to ensure brain-dev is found regardless of install method
|
|
18
32
|
STATUS=$(npx brain-dev status --json 2>/dev/null)
|
|
@@ -34,7 +48,7 @@ try {
|
|
|
34
48
|
'Phase: ' + (data.phase && data.phase.current || 0) + ' (' + (data.phase && data.phase.status || 'initialized') + ')',
|
|
35
49
|
'Next: ' + (data.nextAction || '/brain:new-project'),
|
|
36
50
|
'',
|
|
37
|
-
'Commands: /brain:new-project, /brain:discuss, /brain:plan, /brain:execute, /brain:verify, /brain:complete, /brain:quick, /brain:progress, /brain:pause, /brain:resume, /brain:help, /brain:health, /brain:storm, /brain:adr, /brain:phase, /brain:config, /brain:map',
|
|
51
|
+
'Commands: /brain:new-project, /brain:discuss, /brain:plan, /brain:execute, /brain:verify, /brain:complete, /brain:quick, /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)',
|
|
38
52
|
'',
|
|
39
53
|
'Instructions for Claude:',
|
|
40
54
|
'- When user types /brain:<command>, run: npx brain-dev <command> [args]',
|