myshell-tools 1.0.0 → 2.0.0-alpha.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/CHANGELOG.md +44 -69
- package/LICENSE +21 -21
- package/README.md +178 -318
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +106 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/cost.d.ts +36 -0
- package/dist/commands/cost.js +103 -0
- package/dist/commands/cost.js.map +1 -0
- package/dist/commands/doctor.d.ts +36 -0
- package/dist/commands/doctor.js +115 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/core/assess.d.ts +25 -0
- package/dist/core/assess.js +142 -0
- package/dist/core/assess.js.map +1 -0
- package/dist/core/classify.d.ts +19 -0
- package/dist/core/classify.js +80 -0
- package/dist/core/classify.js.map +1 -0
- package/dist/core/escalate.d.ts +32 -0
- package/dist/core/escalate.js +57 -0
- package/dist/core/escalate.js.map +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/orchestrate.d.ts +42 -0
- package/dist/core/orchestrate.js +439 -0
- package/dist/core/orchestrate.js.map +1 -0
- package/dist/core/policy.d.ts +9 -0
- package/dist/core/policy.js +27 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/prompt.d.ts +26 -0
- package/dist/core/prompt.js +125 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/review.d.ts +46 -0
- package/dist/core/review.js +148 -0
- package/dist/core/review.js.map +1 -0
- package/dist/core/route.d.ts +28 -0
- package/dist/core/route.js +52 -0
- package/dist/core/route.js.map +1 -0
- package/dist/core/types.d.ts +141 -0
- package/dist/core/types.js +14 -0
- package/dist/core/types.js.map +1 -0
- package/dist/infra/atomic.d.ts +53 -0
- package/dist/infra/atomic.js +171 -0
- package/dist/infra/atomic.js.map +1 -0
- package/dist/infra/clock.d.ts +9 -0
- package/dist/infra/clock.js +15 -0
- package/dist/infra/clock.js.map +1 -0
- package/dist/infra/index.d.ts +9 -0
- package/dist/infra/index.js +7 -0
- package/dist/infra/index.js.map +1 -0
- package/dist/infra/ledger.d.ts +49 -0
- package/dist/infra/ledger.js +90 -0
- package/dist/infra/ledger.js.map +1 -0
- package/dist/infra/paths.d.ts +28 -0
- package/dist/infra/paths.js +38 -0
- package/dist/infra/paths.js.map +1 -0
- package/dist/infra/pricing.d.ts +47 -0
- package/dist/infra/pricing.js +151 -0
- package/dist/infra/pricing.js.map +1 -0
- package/dist/infra/session.d.ts +28 -0
- package/dist/infra/session.js +61 -0
- package/dist/infra/session.js.map +1 -0
- package/dist/interface/render.d.ts +27 -0
- package/dist/interface/render.js +134 -0
- package/dist/interface/render.js.map +1 -0
- package/dist/interface/repl.d.ts +23 -0
- package/dist/interface/repl.js +90 -0
- package/dist/interface/repl.js.map +1 -0
- package/dist/interface/run.d.ts +20 -0
- package/dist/interface/run.js +31 -0
- package/dist/interface/run.js.map +1 -0
- package/dist/providers/claude-parse.d.ts +24 -0
- package/dist/providers/claude-parse.js +113 -0
- package/dist/providers/claude-parse.js.map +1 -0
- package/dist/providers/claude.d.ts +45 -0
- package/dist/providers/claude.js +122 -0
- package/dist/providers/claude.js.map +1 -0
- package/dist/providers/codex-parse.d.ts +32 -0
- package/dist/providers/codex-parse.js +145 -0
- package/dist/providers/codex-parse.js.map +1 -0
- package/dist/providers/codex.d.ts +44 -0
- package/dist/providers/codex.js +124 -0
- package/dist/providers/codex.js.map +1 -0
- package/dist/providers/detect.d.ts +49 -0
- package/dist/providers/detect.js +125 -0
- package/dist/providers/detect.js.map +1 -0
- package/dist/providers/errors.d.ts +49 -0
- package/dist/providers/errors.js +189 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.js +7 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/port.d.ts +74 -0
- package/dist/providers/port.js +16 -0
- package/dist/providers/port.js.map +1 -0
- package/dist/providers/registry.d.ts +21 -0
- package/dist/providers/registry.js +34 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/ui/banner.d.ts +19 -0
- package/dist/ui/banner.js +32 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/spinner.d.ts +27 -0
- package/dist/ui/spinner.js +67 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/theme.d.ts +32 -0
- package/dist/ui/theme.js +56 -0
- package/dist/ui/theme.js.map +1 -0
- package/package.json +55 -49
- package/data/orchestrator.json +0 -113
- package/src/auth/recovery.mjs +0 -328
- package/src/auth/refresh.mjs +0 -373
- package/src/chef.mjs +0 -348
- package/src/cli/doctor.mjs +0 -568
- package/src/cli/reset.mjs +0 -447
- package/src/cli/status.mjs +0 -379
- package/src/cli.mjs +0 -429
- package/src/commands/doctor.mjs +0 -375
- package/src/commands/help.mjs +0 -324
- package/src/commands/status.mjs +0 -331
- package/src/monitor/health.mjs +0 -486
- package/src/monitor/performance.mjs +0 -442
- package/src/monitor/report.mjs +0 -535
- package/src/orchestrator/classify.mjs +0 -391
- package/src/orchestrator/confidence.mjs +0 -151
- package/src/orchestrator/handoffs.mjs +0 -231
- package/src/orchestrator/review.mjs +0 -222
- package/src/providers/balance.mjs +0 -201
- package/src/providers/claude.mjs +0 -236
- package/src/providers/codex.mjs +0 -255
- package/src/providers/detect.mjs +0 -185
- package/src/providers/errors.mjs +0 -373
- package/src/providers/select.mjs +0 -162
- package/src/repl-enhanced.mjs +0 -417
- package/src/repl.mjs +0 -321
- package/src/state/archive.mjs +0 -366
- package/src/state/atomic.mjs +0 -116
- package/src/state/cleanup.mjs +0 -440
- package/src/state/recovery.mjs +0 -461
- package/src/state/session.mjs +0 -147
- package/src/ui/errors.mjs +0 -456
- package/src/ui/formatter.mjs +0 -327
- package/src/ui/icons.mjs +0 -318
- package/src/ui/progress.mjs +0 -468
- package/templates/prompts/confidence-format.txt +0 -14
- package/templates/prompts/ic-with-feedback.txt +0 -41
- package/templates/prompts/ic.txt +0 -13
- package/templates/prompts/manager-review.txt +0 -40
- package/templates/prompts/manager.txt +0 -14
- package/templates/prompts/worker.txt +0 -12
package/src/state/atomic.mjs
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atomic.mjs — Atomic file operations for safe concurrent access
|
|
3
|
-
* Adapted from archive/dual-brain/hooks/atomic-write.mjs
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { openSync, closeSync, readFileSync, writeFileSync, renameSync, unlinkSync, statSync } from 'fs';
|
|
7
|
-
import { constants } from 'fs';
|
|
8
|
-
|
|
9
|
-
const LOCK_TIMEOUT_MS = 5000;
|
|
10
|
-
const STALE_LOCK_MS = 10000;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Atomically write JSON data to filePath using tmp-file + rename
|
|
14
|
-
* Tmp file is in the same directory to avoid cross-device rename issues
|
|
15
|
-
*/
|
|
16
|
-
export function atomicWriteJSON(filePath, data) {
|
|
17
|
-
const tmp = filePath + '.tmp.' + process.pid;
|
|
18
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n');
|
|
19
|
-
renameSync(tmp, filePath);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Atomically append a line to a JSONL file
|
|
24
|
-
*/
|
|
25
|
-
export function atomicAppendJSONL(filePath, data) {
|
|
26
|
-
const line = JSON.stringify(data) + '\n';
|
|
27
|
-
const tmp = filePath + '.tmp.' + process.pid;
|
|
28
|
-
|
|
29
|
-
// Read existing content if file exists
|
|
30
|
-
let existing = '';
|
|
31
|
-
try {
|
|
32
|
-
existing = readFileSync(filePath, 'utf8');
|
|
33
|
-
} catch {
|
|
34
|
-
// File doesn't exist, start with empty
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Write existing + new line to tmp file
|
|
38
|
-
writeFileSync(tmp, existing + line);
|
|
39
|
-
renameSync(tmp, filePath);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Acquire a .lock file using O_EXCL for atomic creation
|
|
44
|
-
* Returns true if lock acquired, false otherwise
|
|
45
|
-
* Steals stale locks (older than STALE_LOCK_MS)
|
|
46
|
-
*/
|
|
47
|
-
function acquireLock(lockPath) {
|
|
48
|
-
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
49
|
-
|
|
50
|
-
while (Date.now() < deadline) {
|
|
51
|
-
try {
|
|
52
|
-
const fd = openSync(lockPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL);
|
|
53
|
-
writeFileSync(fd, JSON.stringify({ pid: process.pid, ts: Date.now() }));
|
|
54
|
-
closeSync(fd);
|
|
55
|
-
return true;
|
|
56
|
-
} catch (err) {
|
|
57
|
-
if (err.code !== 'EEXIST') throw err;
|
|
58
|
-
|
|
59
|
-
// Check for stale lock
|
|
60
|
-
try {
|
|
61
|
-
const stat = statSync(lockPath);
|
|
62
|
-
if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
|
|
63
|
-
// Stale lock — process likely died, steal it
|
|
64
|
-
try { unlinkSync(lockPath); } catch {}
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
} catch {
|
|
68
|
-
// Lock disappeared between our check — retry
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Wait briefly before retrying
|
|
73
|
-
const waitMs = 10 + Math.floor(Math.random() * 20);
|
|
74
|
-
const end = Date.now() + waitMs;
|
|
75
|
-
while (Date.now() < end) { /* spin */ }
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function releaseLock(lockPath) {
|
|
82
|
-
try { unlinkSync(lockPath); } catch {}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Locked read-modify-write cycle
|
|
87
|
-
*
|
|
88
|
-
* 1. Acquire .lock file (O_EXCL atomic creation)
|
|
89
|
-
* 2. Read current JSON (or use defaultValue if missing/corrupt)
|
|
90
|
-
* 3. Call modifyFn(currentData) → newData
|
|
91
|
-
* 4. Atomic write newData via tmp+rename
|
|
92
|
-
* 5. Release lock
|
|
93
|
-
*/
|
|
94
|
-
export function lockedReadModifyWrite(filePath, modifyFn, defaultValue = {}) {
|
|
95
|
-
const lockPath = filePath + '.lock';
|
|
96
|
-
const locked = acquireLock(lockPath);
|
|
97
|
-
|
|
98
|
-
if (!locked) {
|
|
99
|
-
throw new Error(`Lock acquisition timed out after ${LOCK_TIMEOUT_MS}ms for ${filePath}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
let current;
|
|
104
|
-
try {
|
|
105
|
-
current = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
106
|
-
} catch {
|
|
107
|
-
current = typeof defaultValue === 'function' ? defaultValue() : defaultValue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const updated = modifyFn(current);
|
|
111
|
-
atomicWriteJSON(filePath, updated);
|
|
112
|
-
return updated;
|
|
113
|
-
} finally {
|
|
114
|
-
releaseLock(lockPath);
|
|
115
|
-
}
|
|
116
|
-
}
|
package/src/state/cleanup.mjs
DELETED
|
@@ -1,440 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cleanup.mjs — State maintenance and cleanup operations
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { existsSync, readFileSync, readdirSync, statSync, unlinkSync, rmSync } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
7
|
-
import { cleanupOldArchives } from './archive.mjs';
|
|
8
|
-
|
|
9
|
-
// Temporary mock for getStorageStats until it's implemented
|
|
10
|
-
function getStorageStats(workspace) {
|
|
11
|
-
return {
|
|
12
|
-
totalSize: 0,
|
|
13
|
-
fileCount: 0,
|
|
14
|
-
sessionCount: 0,
|
|
15
|
-
archiveCount: 0
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export { getStorageStats };
|
|
20
|
-
import { cleanupStaleLocks, validateSessionIntegrity } from './recovery.mjs';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Comprehensive state cleanup
|
|
24
|
-
*/
|
|
25
|
-
export function performStateCleanup(options = {}) {
|
|
26
|
-
const {
|
|
27
|
-
workspace = process.cwd(),
|
|
28
|
-
maxArchiveAge = 30, // days
|
|
29
|
-
cleanLocks = true,
|
|
30
|
-
cleanTemps = true,
|
|
31
|
-
validateSessions = true,
|
|
32
|
-
verbose = false
|
|
33
|
-
} = options;
|
|
34
|
-
|
|
35
|
-
const results = {
|
|
36
|
-
startTime: new Date().toISOString(),
|
|
37
|
-
workspace,
|
|
38
|
-
operations: [],
|
|
39
|
-
errors: [],
|
|
40
|
-
beforeStats: getStorageStats(workspace),
|
|
41
|
-
afterStats: null
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
function log(operation, details) {
|
|
45
|
-
results.operations.push({ operation, ...details, timestamp: new Date().toISOString() });
|
|
46
|
-
if (verbose) {
|
|
47
|
-
console.log(`🧹 ${operation}: ${details.message || 'completed'}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function error(operation, err) {
|
|
52
|
-
const errorMsg = err.message || err.toString();
|
|
53
|
-
results.errors.push({ operation, error: errorMsg, timestamp: new Date().toISOString() });
|
|
54
|
-
if (verbose) {
|
|
55
|
-
console.warn(`❌ ${operation}: ${errorMsg}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
// Clean up stale locks
|
|
61
|
-
if (cleanLocks) {
|
|
62
|
-
try {
|
|
63
|
-
const staleLocks = cleanupStaleLocks(workspace);
|
|
64
|
-
log('Lock Cleanup', {
|
|
65
|
-
message: `Removed ${staleLocks.length} stale lock files`,
|
|
66
|
-
count: staleLocks.length,
|
|
67
|
-
files: staleLocks
|
|
68
|
-
});
|
|
69
|
-
} catch (err) {
|
|
70
|
-
error('Lock Cleanup', err);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Clean up temporary files
|
|
75
|
-
if (cleanTemps) {
|
|
76
|
-
try {
|
|
77
|
-
const tempCount = cleanupTempFiles(workspace);
|
|
78
|
-
log('Temp File Cleanup', {
|
|
79
|
-
message: `Removed ${tempCount} temporary files`,
|
|
80
|
-
count: tempCount
|
|
81
|
-
});
|
|
82
|
-
} catch (err) {
|
|
83
|
-
error('Temp File Cleanup', err);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Clean old archives
|
|
88
|
-
try {
|
|
89
|
-
const archiveCleanup = cleanupOldArchives(maxArchiveAge, workspace);
|
|
90
|
-
log('Archive Cleanup', {
|
|
91
|
-
message: `Deleted ${archiveCleanup.deleted} old archives, preserved ${archiveCleanup.preserved}`,
|
|
92
|
-
deleted: archiveCleanup.deleted,
|
|
93
|
-
preserved: archiveCleanup.preserved,
|
|
94
|
-
errors: archiveCleanup.errors
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
if (archiveCleanup.errors.length > 0) {
|
|
98
|
-
for (const err of archiveCleanup.errors) {
|
|
99
|
-
error('Archive Cleanup', { message: err });
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
} catch (err) {
|
|
103
|
-
error('Archive Cleanup', err);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Validate session integrity
|
|
107
|
-
if (validateSessions) {
|
|
108
|
-
try {
|
|
109
|
-
const integrity = validateSessionIntegrity(workspace);
|
|
110
|
-
log('Session Validation', {
|
|
111
|
-
message: `Session validation ${integrity.valid ? 'passed' : 'failed'}`,
|
|
112
|
-
valid: integrity.valid,
|
|
113
|
-
issues: integrity.issues,
|
|
114
|
-
repairs: integrity.repairs
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
if (!integrity.valid) {
|
|
118
|
-
for (const issue of integrity.issues) {
|
|
119
|
-
error('Session Validation', { message: issue });
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
} catch (err) {
|
|
123
|
-
error('Session Validation', err);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Clean up orphaned plan files
|
|
128
|
-
try {
|
|
129
|
-
const orphanCount = cleanupOrphanedPlans(workspace);
|
|
130
|
-
log('Plan Cleanup', {
|
|
131
|
-
message: `Removed ${orphanCount} orphaned plan files`,
|
|
132
|
-
count: orphanCount
|
|
133
|
-
});
|
|
134
|
-
} catch (err) {
|
|
135
|
-
error('Plan Cleanup', err);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Final storage stats
|
|
139
|
-
results.afterStats = getStorageStats(workspace);
|
|
140
|
-
results.spaceSaved = results.beforeStats.totalSize - results.afterStats.totalSize;
|
|
141
|
-
|
|
142
|
-
log('Cleanup Complete', {
|
|
143
|
-
message: `Cleanup completed, saved ${formatBytes(results.spaceSaved)}`,
|
|
144
|
-
spaceSaved: results.spaceSaved,
|
|
145
|
-
operationCount: results.operations.length,
|
|
146
|
-
errorCount: results.errors.length
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
} catch (globalError) {
|
|
150
|
-
error('Global Cleanup', globalError);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
results.endTime = new Date().toISOString();
|
|
154
|
-
results.duration = new Date(results.endTime) - new Date(results.startTime);
|
|
155
|
-
|
|
156
|
-
return results;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Clean up temporary files (.tmp, .lock, etc.)
|
|
161
|
-
*/
|
|
162
|
-
function cleanupTempFiles(workspace) {
|
|
163
|
-
const cortexDir = join(workspace, '.cortex');
|
|
164
|
-
if (!existsSync(cortexDir)) return 0;
|
|
165
|
-
|
|
166
|
-
let count = 0;
|
|
167
|
-
|
|
168
|
-
function cleanDir(dir) {
|
|
169
|
-
try {
|
|
170
|
-
const entries = readdirSync(dir);
|
|
171
|
-
|
|
172
|
-
for (const entry of entries) {
|
|
173
|
-
const fullPath = join(dir, entry);
|
|
174
|
-
const stat = statSync(fullPath);
|
|
175
|
-
|
|
176
|
-
if (stat.isDirectory()) {
|
|
177
|
-
cleanDir(fullPath);
|
|
178
|
-
} else if (isTempFile(entry)) {
|
|
179
|
-
try {
|
|
180
|
-
unlinkSync(fullPath);
|
|
181
|
-
count++;
|
|
182
|
-
} catch {
|
|
183
|
-
// File might be in use
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
} catch {
|
|
188
|
-
// Directory access error
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
cleanDir(cortexDir);
|
|
193
|
-
return count;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Check if file is temporary
|
|
198
|
-
*/
|
|
199
|
-
function isTempFile(filename) {
|
|
200
|
-
return filename.endsWith('.tmp') ||
|
|
201
|
-
filename.includes('.tmp.') ||
|
|
202
|
-
filename.endsWith('.bak') ||
|
|
203
|
-
filename.endsWith('~') ||
|
|
204
|
-
filename.startsWith('.#');
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Clean up orphaned plan files
|
|
209
|
-
*/
|
|
210
|
-
function cleanupOrphanedPlans(workspace) {
|
|
211
|
-
const plansDir = join(workspace, '.cortex', 'plans');
|
|
212
|
-
if (!existsSync(plansDir)) return 0;
|
|
213
|
-
|
|
214
|
-
let count = 0;
|
|
215
|
-
const oneWeekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
const planFiles = readdirSync(plansDir)
|
|
219
|
-
.filter(f => f.endsWith('.json'))
|
|
220
|
-
.map(f => join(plansDir, f));
|
|
221
|
-
|
|
222
|
-
for (const file of planFiles) {
|
|
223
|
-
try {
|
|
224
|
-
const plan = JSON.parse(readFileSync(file, 'utf8'));
|
|
225
|
-
const updated = new Date(plan.updated || plan.created).getTime();
|
|
226
|
-
|
|
227
|
-
// Remove plans that are old and completed/failed
|
|
228
|
-
if (updated < oneWeekAgo && (plan.state === 'completed' || plan.state === 'failed')) {
|
|
229
|
-
unlinkSync(file);
|
|
230
|
-
count++;
|
|
231
|
-
}
|
|
232
|
-
} catch {
|
|
233
|
-
// Invalid plan file, remove it
|
|
234
|
-
try {
|
|
235
|
-
unlinkSync(file);
|
|
236
|
-
count++;
|
|
237
|
-
} catch {
|
|
238
|
-
// Can't remove, skip
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
} catch {
|
|
243
|
-
// Directory access error
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return count;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Reset all state (nuclear option)
|
|
251
|
-
*/
|
|
252
|
-
export function resetAllState(workspace = process.cwd(), preserveAuth = true) {
|
|
253
|
-
const cortexDir = join(workspace, '.cortex');
|
|
254
|
-
|
|
255
|
-
if (!existsSync(cortexDir)) {
|
|
256
|
-
return { reset: false, reason: 'No .cortex directory found' };
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const results = {
|
|
260
|
-
reset: true,
|
|
261
|
-
preservedAuth: preserveAuth,
|
|
262
|
-
removedDirs: [],
|
|
263
|
-
preservedDirs: [],
|
|
264
|
-
errors: []
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
try {
|
|
268
|
-
const entries = readdirSync(cortexDir);
|
|
269
|
-
|
|
270
|
-
for (const entry of entries) {
|
|
271
|
-
const fullPath = join(cortexDir, entry);
|
|
272
|
-
const stat = statSync(fullPath);
|
|
273
|
-
|
|
274
|
-
if (stat.isDirectory()) {
|
|
275
|
-
if (preserveAuth && entry === 'auth') {
|
|
276
|
-
results.preservedDirs.push(entry);
|
|
277
|
-
} else {
|
|
278
|
-
try {
|
|
279
|
-
rmSync(fullPath, { recursive: true, force: true });
|
|
280
|
-
results.removedDirs.push(entry);
|
|
281
|
-
} catch (error) {
|
|
282
|
-
results.errors.push(`Failed to remove ${entry}: ${error.message}`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
} else {
|
|
286
|
-
// Remove loose files
|
|
287
|
-
try {
|
|
288
|
-
unlinkSync(fullPath);
|
|
289
|
-
} catch (error) {
|
|
290
|
-
results.errors.push(`Failed to remove file ${entry}: ${error.message}`);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
} catch (error) {
|
|
296
|
-
results.errors.push(`Failed to read .cortex directory: ${error.message}`);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return results;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Get detailed cleanup status and recommendations
|
|
304
|
-
*/
|
|
305
|
-
export function getCleanupStatus(workspace = process.cwd()) {
|
|
306
|
-
const stats = getStorageStats(workspace);
|
|
307
|
-
const cortexDir = join(workspace, '.cortex');
|
|
308
|
-
|
|
309
|
-
const status = {
|
|
310
|
-
totalSize: stats.totalSize,
|
|
311
|
-
breakdown: stats.breakdown,
|
|
312
|
-
recommendations: [],
|
|
313
|
-
issues: []
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
// Check for large archive directories
|
|
317
|
-
if (stats.breakdown.archives > 10 * 1024 * 1024) { // > 10MB
|
|
318
|
-
status.recommendations.push({
|
|
319
|
-
type: 'cleanup',
|
|
320
|
-
priority: 'medium',
|
|
321
|
-
message: `Archive directory is ${formatBytes(stats.breakdown.archives)}. Consider cleaning old archives.`,
|
|
322
|
-
action: 'Run cleanup with archive age limit'
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Check for many session files
|
|
327
|
-
const sessionDir = join(cortexDir, 'sessions');
|
|
328
|
-
if (existsSync(sessionDir)) {
|
|
329
|
-
try {
|
|
330
|
-
const sessionFiles = readdirSync(sessionDir).length;
|
|
331
|
-
if (sessionFiles > 50) {
|
|
332
|
-
status.recommendations.push({
|
|
333
|
-
type: 'archive',
|
|
334
|
-
priority: 'low',
|
|
335
|
-
message: `${sessionFiles} session files found. Consider archiving old sessions.`,
|
|
336
|
-
action: 'Archive completed sessions'
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
} catch {}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Check for stale locks
|
|
343
|
-
const staleLocks = findStaleLocks(workspace);
|
|
344
|
-
if (staleLocks.length > 0) {
|
|
345
|
-
status.issues.push({
|
|
346
|
-
type: 'locks',
|
|
347
|
-
severity: 'warning',
|
|
348
|
-
message: `${staleLocks.length} stale lock files found`,
|
|
349
|
-
action: 'Run cleanup to remove stale locks'
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Check session integrity
|
|
354
|
-
try {
|
|
355
|
-
const integrity = validateSessionIntegrity(workspace);
|
|
356
|
-
if (!integrity.valid) {
|
|
357
|
-
status.issues.push({
|
|
358
|
-
type: 'integrity',
|
|
359
|
-
severity: 'error',
|
|
360
|
-
message: `Session integrity issues: ${integrity.issues.join(', ')}`,
|
|
361
|
-
action: 'Run cleanup with session validation'
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
} catch {}
|
|
365
|
-
|
|
366
|
-
return status;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Find stale lock files
|
|
371
|
-
*/
|
|
372
|
-
function findStaleLocks(workspace) {
|
|
373
|
-
const staleLocks = [];
|
|
374
|
-
const staleThreshold = 10 * 60 * 1000; // 10 minutes
|
|
375
|
-
const now = Date.now();
|
|
376
|
-
|
|
377
|
-
function findLocks(dir) {
|
|
378
|
-
if (!existsSync(dir)) return;
|
|
379
|
-
|
|
380
|
-
try {
|
|
381
|
-
const entries = readdirSync(dir);
|
|
382
|
-
|
|
383
|
-
for (const entry of entries) {
|
|
384
|
-
const fullPath = join(dir, entry);
|
|
385
|
-
const stat = statSync(fullPath);
|
|
386
|
-
|
|
387
|
-
if (stat.isDirectory()) {
|
|
388
|
-
findLocks(fullPath);
|
|
389
|
-
} else if (entry.endsWith('.lock')) {
|
|
390
|
-
const age = now - stat.mtimeMs;
|
|
391
|
-
if (age > staleThreshold) {
|
|
392
|
-
staleLocks.push(fullPath);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
} catch {}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
findLocks(join(workspace, '.cortex'));
|
|
400
|
-
return staleLocks;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Format bytes for human-readable display
|
|
405
|
-
*/
|
|
406
|
-
function formatBytes(bytes) {
|
|
407
|
-
if (bytes === 0) return '0 B';
|
|
408
|
-
|
|
409
|
-
const k = 1024;
|
|
410
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
411
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
412
|
-
|
|
413
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Schedule automatic cleanup
|
|
418
|
-
*/
|
|
419
|
-
export function scheduleAutoCleanup(interval = 24 * 60 * 60 * 1000, options = {}) {
|
|
420
|
-
const cleanup = () => {
|
|
421
|
-
console.log('🧹 Running automatic state cleanup...');
|
|
422
|
-
const results = performStateCleanup({ ...options, verbose: false });
|
|
423
|
-
|
|
424
|
-
if (results.errors.length > 0) {
|
|
425
|
-
console.warn(`⚠️ Cleanup completed with ${results.errors.length} errors`);
|
|
426
|
-
} else {
|
|
427
|
-
console.log(`✅ Cleanup completed, saved ${formatBytes(results.spaceSaved)}`);
|
|
428
|
-
}
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
// Run cleanup on interval
|
|
432
|
-
const intervalId = setInterval(cleanup, interval);
|
|
433
|
-
|
|
434
|
-
// Also run cleanup on process exit
|
|
435
|
-
process.on('exit', () => {
|
|
436
|
-
clearInterval(intervalId);
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
return intervalId;
|
|
440
|
-
}
|