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,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { parseArgs } = require('node:util');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { output, error, success, prefix } = require('../core.cjs');
|
|
6
|
+
const { readState } = require('../state.cjs');
|
|
7
|
+
const { readLock, isLockStale } = require('../lock.cjs');
|
|
8
|
+
const recovery = require('../recovery.cjs');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Recover command handler.
|
|
12
|
+
* Detects and recovers from crashed/interrupted executions.
|
|
13
|
+
*
|
|
14
|
+
* Flags:
|
|
15
|
+
* --fix Attempt auto-resume from last checkpoint
|
|
16
|
+
* --rollback Revert state to pre-execution
|
|
17
|
+
* --dismiss Clear stale lock without recovery
|
|
18
|
+
* --json Force JSON output
|
|
19
|
+
*
|
|
20
|
+
* Default (no flags): check mode — show recovery briefing
|
|
21
|
+
*
|
|
22
|
+
* @param {string[]} args - CLI arguments
|
|
23
|
+
* @param {object} [opts] - Options (brainDir override)
|
|
24
|
+
* @returns {object} Result object
|
|
25
|
+
*/
|
|
26
|
+
async function run(args = [], opts = {}) {
|
|
27
|
+
const { values } = parseArgs({
|
|
28
|
+
args,
|
|
29
|
+
options: {
|
|
30
|
+
fix: { type: 'boolean', default: false },
|
|
31
|
+
rollback: { type: 'boolean', default: false },
|
|
32
|
+
dismiss: { type: 'boolean', default: false },
|
|
33
|
+
json: { type: 'boolean', default: false }
|
|
34
|
+
},
|
|
35
|
+
strict: false
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const brainDir = opts.brainDir || path.join(process.cwd(), '.brain');
|
|
39
|
+
|
|
40
|
+
// Validate brainDir exists
|
|
41
|
+
const state = readState(brainDir);
|
|
42
|
+
if (!state) {
|
|
43
|
+
const result = { action: 'recover', status: 'error', message: 'No .brain directory or brain.json found' };
|
|
44
|
+
if (values.json) {
|
|
45
|
+
console.log(JSON.stringify(result));
|
|
46
|
+
} else {
|
|
47
|
+
error('No .brain directory found. Run `brain init` first.');
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Mutually exclusive flags check
|
|
53
|
+
const flagCount = [values.fix, values.rollback, values.dismiss].filter(Boolean).length;
|
|
54
|
+
if (flagCount > 1) {
|
|
55
|
+
const result = { action: 'recover', status: 'error', message: 'Use only one of --fix, --rollback, or --dismiss' };
|
|
56
|
+
if (values.json) {
|
|
57
|
+
console.log(JSON.stringify(result));
|
|
58
|
+
} else {
|
|
59
|
+
error('Use only one of --fix, --rollback, or --dismiss.');
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Dismiss mode: clear stale lock without recovery
|
|
65
|
+
if (values.dismiss) {
|
|
66
|
+
const result = recovery.recover(brainDir, { dismiss: true });
|
|
67
|
+
const resultObj = { action: 'recover', status: result.recovered ? 'dismissed' : 'no-action', ...result };
|
|
68
|
+
|
|
69
|
+
if (values.json) {
|
|
70
|
+
console.log(JSON.stringify(resultObj, null, 2));
|
|
71
|
+
} else if (result.recovered) {
|
|
72
|
+
success('Stale lock dismissed. No state changes made.');
|
|
73
|
+
} else {
|
|
74
|
+
output(resultObj, prefix(result.briefing));
|
|
75
|
+
}
|
|
76
|
+
return resultObj;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Fix mode: auto-resume recovery
|
|
80
|
+
if (values.fix) {
|
|
81
|
+
const result = recovery.recover(brainDir, { fix: true });
|
|
82
|
+
const resultObj = { action: 'recover', status: result.recovered ? 'fixed' : 'no-action', ...result };
|
|
83
|
+
|
|
84
|
+
if (values.json) {
|
|
85
|
+
console.log(JSON.stringify(resultObj, null, 2));
|
|
86
|
+
} else if (result.recovered) {
|
|
87
|
+
// Show briefing then success
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(result.briefing);
|
|
90
|
+
console.log('');
|
|
91
|
+
success(`Recovery complete. ${result.nextAction}`);
|
|
92
|
+
if (result.result?.changes) {
|
|
93
|
+
for (const change of result.result.changes) {
|
|
94
|
+
console.log(prefix(` - ${change}`));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
output(resultObj, prefix(result.briefing));
|
|
99
|
+
}
|
|
100
|
+
return resultObj;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Rollback mode: revert state
|
|
104
|
+
if (values.rollback) {
|
|
105
|
+
const result = recovery.recover(brainDir, { rollback: true });
|
|
106
|
+
const resultObj = { action: 'recover', status: result.recovered ? 'rolled-back' : 'no-action', ...result };
|
|
107
|
+
|
|
108
|
+
if (values.json) {
|
|
109
|
+
console.log(JSON.stringify(resultObj, null, 2));
|
|
110
|
+
} else if (result.recovered) {
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(result.briefing);
|
|
113
|
+
console.log('');
|
|
114
|
+
success(`Rollback complete. ${result.nextAction}`);
|
|
115
|
+
if (result.result?.changes) {
|
|
116
|
+
for (const change of result.result.changes) {
|
|
117
|
+
console.log(prefix(` - ${change}`));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
output(resultObj, prefix(result.briefing));
|
|
122
|
+
}
|
|
123
|
+
return resultObj;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Default: check mode — show briefing without making changes
|
|
127
|
+
const result = recovery.recover(brainDir, {});
|
|
128
|
+
const resultObj = { action: 'recover', status: result.mode, ...result };
|
|
129
|
+
|
|
130
|
+
if (values.json) {
|
|
131
|
+
console.log(JSON.stringify(resultObj, null, 2));
|
|
132
|
+
} else {
|
|
133
|
+
console.log('');
|
|
134
|
+
console.log(result.briefing);
|
|
135
|
+
console.log('');
|
|
136
|
+
|
|
137
|
+
if (result.mode === 'clean' || result.mode === 'active' || result.mode === 'none') {
|
|
138
|
+
success(result.briefing);
|
|
139
|
+
} else {
|
|
140
|
+
// There is something to recover
|
|
141
|
+
const recommendation = result.result?.recommended;
|
|
142
|
+
if (recommendation === 'auto-resume') {
|
|
143
|
+
console.log(prefix('Suggested: brain recover --fix'));
|
|
144
|
+
} else if (recommendation === 'rollback') {
|
|
145
|
+
console.log(prefix('Suggested: brain recover --rollback'));
|
|
146
|
+
} else {
|
|
147
|
+
console.log(prefix('Review the briefing above and choose: --fix, --rollback, or --dismiss'));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return resultObj;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { run };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { readState, writeState, migrateState } = require('../state.cjs');
|
|
6
|
+
const { detectPlatform } = require('../platform.cjs');
|
|
7
|
+
const { packagePath, registerClaudeHooks, registerAgents, registerCommands, cleanupLegacySkills } = require('../init.cjs');
|
|
8
|
+
const { output, error, success, prefix } = require('../core.cjs');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Copy hook scripts from package to .brain/hooks/.
|
|
12
|
+
* Preserves executable permissions.
|
|
13
|
+
* @param {string} brainDir
|
|
14
|
+
*/
|
|
15
|
+
function copyHooks(brainDir) {
|
|
16
|
+
const hooksDir = path.join(brainDir, 'hooks');
|
|
17
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
18
|
+
|
|
19
|
+
const hookFiles = ['bootstrap.sh', 'statusline.sh', 'post-tool-use.sh'];
|
|
20
|
+
for (const file of hookFiles) {
|
|
21
|
+
const src = packagePath('hooks', file);
|
|
22
|
+
if (fs.existsSync(src)) {
|
|
23
|
+
const dest = path.join(hooksDir, file);
|
|
24
|
+
fs.copyFileSync(src, dest);
|
|
25
|
+
try { fs.chmodSync(dest, 0o755); } catch { /* skip on unsupported FS */ }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Update .brain/.gitignore with current patterns.
|
|
32
|
+
* @param {string} brainDir
|
|
33
|
+
*/
|
|
34
|
+
function updateGitignore(brainDir) {
|
|
35
|
+
const content = [
|
|
36
|
+
'*.tmp',
|
|
37
|
+
'*.lock',
|
|
38
|
+
'storm/fragments/',
|
|
39
|
+
'storm/events.jsonl',
|
|
40
|
+
''
|
|
41
|
+
].join('\n');
|
|
42
|
+
fs.writeFileSync(path.join(brainDir, '.gitignore'), content, 'utf8');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run the update command.
|
|
47
|
+
* Updates tool files (hooks, agents, commands) while preserving project state.
|
|
48
|
+
*
|
|
49
|
+
* brain-dev update → Update tool files + migrate state
|
|
50
|
+
* brain-dev update --check → Only check version, don't update
|
|
51
|
+
*
|
|
52
|
+
* @param {string[]} args
|
|
53
|
+
* @param {object} [opts]
|
|
54
|
+
*/
|
|
55
|
+
async function run(args = [], opts = {}) {
|
|
56
|
+
const brainDir = opts.brainDir || path.join(process.cwd(), '.brain');
|
|
57
|
+
const cwd = opts.cwd || process.cwd();
|
|
58
|
+
|
|
59
|
+
// 1. Validate .brain/ exists
|
|
60
|
+
if (!fs.existsSync(brainDir)) {
|
|
61
|
+
error("No brain project found. Run 'npx brain-dev init' first.");
|
|
62
|
+
return { error: 'no-project' };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 2. Read current state
|
|
66
|
+
const state = readState(brainDir);
|
|
67
|
+
if (!state) {
|
|
68
|
+
error("Corrupted brain.json. Run 'npx brain-dev init --force' to reset.");
|
|
69
|
+
return { error: 'corrupted-state' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 3. Get versions
|
|
73
|
+
const installedVersion = state.version || '0.0.0';
|
|
74
|
+
const packageVersion = require(path.join(__dirname, '..', '..', '..', 'package.json')).version;
|
|
75
|
+
|
|
76
|
+
// --check mode
|
|
77
|
+
if (args.includes('--check')) {
|
|
78
|
+
const upToDate = installedVersion === packageVersion;
|
|
79
|
+
const msg = upToDate
|
|
80
|
+
? `brain-dev v${installedVersion} is up to date.`
|
|
81
|
+
: `Update available: v${installedVersion} → v${packageVersion}. Run: npx brain-dev update`;
|
|
82
|
+
|
|
83
|
+
output({
|
|
84
|
+
action: 'version-check',
|
|
85
|
+
installed: installedVersion,
|
|
86
|
+
package: packageVersion,
|
|
87
|
+
upToDate
|
|
88
|
+
}, prefix(msg));
|
|
89
|
+
|
|
90
|
+
return { action: 'version-check', installed: installedVersion, package: packageVersion, upToDate };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 4. Backup state before update
|
|
94
|
+
const backupPath = path.join(brainDir, 'brain.json.pre-update');
|
|
95
|
+
try {
|
|
96
|
+
fs.copyFileSync(path.join(brainDir, 'brain.json'), backupPath);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
error(`Could not backup brain.json: ${e.message}`);
|
|
99
|
+
return { error: 'backup-failed' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 5. Ensure required directories exist
|
|
103
|
+
const requiredDirs = ['hooks', 'debug', 'specs', 'codebase'];
|
|
104
|
+
for (const dir of requiredDirs) {
|
|
105
|
+
fs.mkdirSync(path.join(brainDir, dir), { recursive: true });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 6. Update hook scripts
|
|
109
|
+
copyHooks(brainDir);
|
|
110
|
+
|
|
111
|
+
// 7. Platform-specific updates
|
|
112
|
+
const platform = detectPlatform({ cwd });
|
|
113
|
+
if (platform === 'claude-code') {
|
|
114
|
+
registerClaudeHooks(cwd);
|
|
115
|
+
cleanupLegacySkills(cwd);
|
|
116
|
+
registerCommands(cwd);
|
|
117
|
+
registerAgents(cwd);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 8. Migrate state (adds new fields, preserves all existing data)
|
|
121
|
+
const migrated = migrateState(state);
|
|
122
|
+
writeState(brainDir, migrated);
|
|
123
|
+
|
|
124
|
+
// 9. Update .gitignore
|
|
125
|
+
updateGitignore(brainDir);
|
|
126
|
+
|
|
127
|
+
// 10. Report
|
|
128
|
+
const isUpgrade = installedVersion !== packageVersion;
|
|
129
|
+
const msg = isUpgrade
|
|
130
|
+
? `Updated brain-dev v${installedVersion} → v${packageVersion}. State preserved. Backup: brain.json.pre-update`
|
|
131
|
+
: `brain-dev v${packageVersion} tool files refreshed. State preserved.`;
|
|
132
|
+
|
|
133
|
+
success(msg);
|
|
134
|
+
|
|
135
|
+
const result = {
|
|
136
|
+
action: 'updated',
|
|
137
|
+
from: installedVersion,
|
|
138
|
+
to: packageVersion,
|
|
139
|
+
backup: backupPath,
|
|
140
|
+
statePreserved: true,
|
|
141
|
+
nextAction: '/brain:progress'
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
output(result, '');
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { run };
|
|
@@ -9,6 +9,7 @@ const { logEvent } = require('../logger.cjs');
|
|
|
9
9
|
const { output, error, success } = require('../core.cjs');
|
|
10
10
|
const antiPatterns = require('../anti-patterns.cjs');
|
|
11
11
|
const { buildDebuggerSpawnInstructions } = require('./execute.cjs');
|
|
12
|
+
const { isPathWithinRoot } = require('../security.cjs');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Find a phase directory under .brain/phases/ matching a phase number.
|
|
@@ -72,9 +73,10 @@ function extractMustHaves(content) {
|
|
|
72
73
|
* Extract phase-modified file paths from PLAN and SUMMARY frontmatter.
|
|
73
74
|
* @param {string[]} planFiles - Full paths to plan files
|
|
74
75
|
* @param {string[]} summaryFiles - Full paths to summary files
|
|
76
|
+
* @param {string} [rootDir] - Project root directory for path traversal guard
|
|
75
77
|
* @returns {string[]} Deduplicated array of file paths
|
|
76
78
|
*/
|
|
77
|
-
function extractPhaseFiles(planFiles, summaryFiles) {
|
|
79
|
+
function extractPhaseFiles(planFiles, summaryFiles, rootDir) {
|
|
78
80
|
const files = new Set();
|
|
79
81
|
|
|
80
82
|
for (const planPath of planFiles) {
|
|
@@ -90,7 +92,10 @@ function extractPhaseFiles(planFiles, summaryFiles) {
|
|
|
90
92
|
for (const line of lines) {
|
|
91
93
|
const itemMatch = line.match(/^\s+-\s+(.+)/);
|
|
92
94
|
if (itemMatch) {
|
|
93
|
-
|
|
95
|
+
const filePath = itemMatch[1].trim();
|
|
96
|
+
// Guard against path traversal
|
|
97
|
+
if (rootDir && !isPathWithinRoot(filePath, rootDir)) continue;
|
|
98
|
+
files.add(filePath);
|
|
94
99
|
} else if (line.trim() && !line.match(/^\s+-/) && !line.match(/^\s*$/)) {
|
|
95
100
|
break; // Hit next YAML key
|
|
96
101
|
}
|
|
@@ -117,7 +122,10 @@ function extractPhaseFiles(planFiles, summaryFiles) {
|
|
|
117
122
|
for (const line of lines) {
|
|
118
123
|
const itemMatch = line.match(/^\s+-\s+(.+)/);
|
|
119
124
|
if (itemMatch) {
|
|
120
|
-
|
|
125
|
+
const filePath = itemMatch[1].trim().replace(/^["']|["']$/g, '');
|
|
126
|
+
// Guard against path traversal
|
|
127
|
+
if (rootDir && !isPathWithinRoot(filePath, rootDir)) continue;
|
|
128
|
+
files.add(filePath);
|
|
121
129
|
} else if (line.trim() && !line.match(/^\s+-/) && !line.match(/^\s*$/) && !line.match(/^\s+\w+:/)) {
|
|
122
130
|
break;
|
|
123
131
|
}
|
|
@@ -213,11 +221,11 @@ async function run(args = [], opts = {}) {
|
|
|
213
221
|
const summaryPaths = summaryFiles.map(f => path.join(phaseDir, f));
|
|
214
222
|
|
|
215
223
|
// Extract phase-modified files for anti-pattern scanning
|
|
224
|
+
const projectDir = path.dirname(brainDir);
|
|
216
225
|
const planFullPaths = planFiles.map(f => path.join(phaseDir, f));
|
|
217
|
-
const phaseFiles = extractPhaseFiles(planFullPaths, summaryPaths);
|
|
226
|
+
const phaseFiles = extractPhaseFiles(planFullPaths, summaryPaths, projectDir);
|
|
218
227
|
|
|
219
228
|
// Run anti-pattern scan on phase files
|
|
220
|
-
const projectDir = path.dirname(brainDir);
|
|
221
229
|
const apResults = antiPatterns.scanFiles(projectDir, { files: phaseFiles });
|
|
222
230
|
|
|
223
231
|
// Build Nyquist section based on state config
|
|
@@ -400,6 +408,8 @@ function handleSaveResults(args, saveIdx, brainDir, state) {
|
|
|
400
408
|
|
|
401
409
|
// Update state
|
|
402
410
|
state.phase.status = results.passed ? 'verified' : 'verification-failed';
|
|
411
|
+
// Clear execution timer after verification completes
|
|
412
|
+
state.phase.execution_started_at = null;
|
|
403
413
|
writeState(brainDir, state);
|
|
404
414
|
|
|
405
415
|
const msg = results.passed
|
package/bin/lib/commands.cjs
CHANGED
|
@@ -148,6 +148,15 @@ const COMMANDS = [
|
|
|
148
148
|
needsState: true,
|
|
149
149
|
args: ' --session <id> Resume a specific session by timestamp ID'
|
|
150
150
|
},
|
|
151
|
+
{
|
|
152
|
+
name: 'recover',
|
|
153
|
+
description: 'Detect and recover from crashes',
|
|
154
|
+
usage: 'brain-dev recover [--fix|--rollback|--dismiss]',
|
|
155
|
+
group: 'Session',
|
|
156
|
+
implemented: true,
|
|
157
|
+
needsState: true,
|
|
158
|
+
args: ' --fix Auto-resume from last checkpoint\n --rollback Revert to pre-crash state\n --dismiss Clear stale lock, move on'
|
|
159
|
+
},
|
|
151
160
|
// Tools
|
|
152
161
|
{
|
|
153
162
|
name: 'storm',
|
|
@@ -158,6 +167,15 @@ const COMMANDS = [
|
|
|
158
167
|
needsState: true,
|
|
159
168
|
args: ' <topic> Topic to brainstorm\n --stop Stop running server\n --port <n> Custom port number\n --finalize Generate output.md from fragments'
|
|
160
169
|
},
|
|
170
|
+
{
|
|
171
|
+
name: 'dashboard',
|
|
172
|
+
description: 'Live monitoring dashboard',
|
|
173
|
+
usage: 'brain-dev dashboard [--browser|--stop|--status]',
|
|
174
|
+
group: 'Tools',
|
|
175
|
+
implemented: true,
|
|
176
|
+
needsState: true,
|
|
177
|
+
args: ' --browser Open web dashboard in browser\n --stop Stop running dashboard server\n --status Show dashboard URL if running'
|
|
178
|
+
},
|
|
161
179
|
{
|
|
162
180
|
name: 'adr',
|
|
163
181
|
description: 'Manage architecture decision records',
|
|
@@ -180,6 +198,15 @@ const COMMANDS = [
|
|
|
180
198
|
},
|
|
181
199
|
|
|
182
200
|
// Meta
|
|
201
|
+
{
|
|
202
|
+
name: 'update',
|
|
203
|
+
description: 'Update tool files while preserving project state',
|
|
204
|
+
usage: 'brain-dev update [--check]',
|
|
205
|
+
group: 'Meta',
|
|
206
|
+
implemented: true,
|
|
207
|
+
needsState: false,
|
|
208
|
+
args: ' --check Only check for new version, don\'t update'
|
|
209
|
+
},
|
|
183
210
|
{
|
|
184
211
|
name: 'health',
|
|
185
212
|
description: 'Run health diagnostics and auto-repair safe issues',
|
package/bin/lib/config.cjs
CHANGED
|
@@ -7,10 +7,10 @@ const { readState, writeState, atomicWriteSync } = require('./state.cjs');
|
|
|
7
7
|
|
|
8
8
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
9
9
|
|
|
10
|
-
const CATEGORIES = ['Workflow', 'Models', 'Enforcement', 'Monitoring', 'Complexity', 'Storm', 'ADR'];
|
|
10
|
+
const CATEGORIES = ['Workflow', 'Models', 'Enforcement', 'Monitoring', 'Complexity', 'Storm', 'ADR', 'Budget', 'Timeout', 'Dashboard'];
|
|
11
11
|
|
|
12
12
|
const SCHEMA = {
|
|
13
|
-
'mode': { type: 'enum', values: ['interactive', 'yolo'], default: 'interactive', category: 'Workflow', description: 'Execution mode preset (sets multiple flags)' },
|
|
13
|
+
'mode': { type: 'enum', values: ['interactive', 'yolo', 'auto'], default: 'interactive', category: 'Workflow', description: 'Execution mode preset (sets multiple flags)' },
|
|
14
14
|
'depth': { type: 'enum', values: ['shallow', 'standard', 'deep'], default: 'deep', category: 'Workflow', description: 'Verification depth (shallow=L1, standard=L1+L2, deep=L1+L2+L3)' },
|
|
15
15
|
'workflow.parallelization': { type: 'boolean', default: false, category: 'Workflow', description: 'Enable parallel wave execution' },
|
|
16
16
|
'workflow.mapper_parallelization': { type: 'boolean', default: true, category: 'Workflow', description: 'Enable parallel codebase mapping' },
|
|
@@ -29,6 +29,21 @@ const SCHEMA = {
|
|
|
29
29
|
'storm.auto_open': { type: 'boolean', default: true, category: 'Storm', description: 'Auto-open browser on storm start', local: true },
|
|
30
30
|
'adr.auto_create': { type: 'boolean', default: true, category: 'ADR', description: 'Auto-create ADRs for significant decisions' },
|
|
31
31
|
'adr.status_lifecycle': { type: 'boolean', default: true, category: 'ADR', description: 'Enable ADR status transitions' },
|
|
32
|
+
'budget.ceiling_usd': { type: 'number', default: null, min: 0, category: 'Budget', description: 'Maximum spend in USD (null = unlimited)' },
|
|
33
|
+
'budget.warn_at_pct': { type: 'number', default: 80, min: 10, max: 100, category: 'Budget', description: 'Warn when this % of ceiling is reached' },
|
|
34
|
+
'budget.mode': { type: 'enum', values: ['warn', 'pause', 'hard-stop'], default: 'warn', category: 'Budget', description: 'Action when budget exceeded' },
|
|
35
|
+
'timeout.enabled': { type: 'boolean', default: true, category: 'Timeout', description: 'Enable stuck detection and timeouts' },
|
|
36
|
+
'timeout.soft': { type: 'number', default: 20, min: 5, max: 120, category: 'Timeout', description: 'Soft timeout in minutes (warning)' },
|
|
37
|
+
'timeout.idle': { type: 'number', default: 5, min: 1, max: 30, category: 'Timeout', description: 'Idle timeout in minutes (no progress)' },
|
|
38
|
+
'timeout.hard': { type: 'number', default: 45, min: 10, max: 180, category: 'Timeout', description: 'Hard timeout in minutes (force wrap-up)' },
|
|
39
|
+
'timeout.max_retries': { type: 'number', default: 1, min: 0, max: 3, category: 'Timeout', description: 'Max resume attempts after stuck timeout' },
|
|
40
|
+
'dashboard.port': { type: 'number', default: 3457, min: 1024, max: 65535, category: 'Dashboard', description: 'Dashboard server port' },
|
|
41
|
+
'dashboard.auto_open': { type: 'boolean', default: true, category: 'Dashboard', description: 'Auto-open browser on dashboard start' },
|
|
42
|
+
'auto.max_errors': { type: 'number', default: 3, min: 1, max: 10, category: 'Workflow', description: 'Max consecutive errors before auto mode pauses' },
|
|
43
|
+
'auto.max_phases': { type: 'number', default: 0, min: 0, max: 100, category: 'Workflow', description: 'Max phases to process in auto mode (0 = unlimited)' },
|
|
44
|
+
'auto.skip_discuss': { type: 'boolean', default: false, category: 'Workflow', description: 'Skip discuss step in auto mode when CONTEXT.md exists' },
|
|
45
|
+
'auto.max_operations': { type: 'number', default: 100, min: 10, max: 1000, category: 'Workflow', description: 'Max operations (commits/writes) per auto session' },
|
|
46
|
+
'auto.max_duration_minutes': { type: 'number', default: 120, min: 10, max: 480, category: 'Workflow', description: 'Max auto session duration in minutes' },
|
|
32
47
|
};
|
|
33
48
|
|
|
34
49
|
const PROFILES = {
|
|
@@ -69,6 +84,12 @@ const MODE_PRESETS = {
|
|
|
69
84
|
'workflow.auto_recover': false,
|
|
70
85
|
'workflow.parallelization': false,
|
|
71
86
|
'enforcement.level': 'hard'
|
|
87
|
+
},
|
|
88
|
+
auto: {
|
|
89
|
+
'workflow.advocate': false,
|
|
90
|
+
'workflow.auto_recover': true,
|
|
91
|
+
'workflow.parallelization': false,
|
|
92
|
+
'enforcement.level': 'balanced'
|
|
72
93
|
}
|
|
73
94
|
};
|
|
74
95
|
|