@litmers/cursorflow-orchestrator 0.1.20 → 0.1.26
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 +9 -0
- package/commands/cursorflow-clean.md +19 -0
- package/commands/cursorflow-runs.md +59 -0
- package/commands/cursorflow-stop.md +55 -0
- package/dist/cli/clean.js +171 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +83 -42
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1007 -189
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +4 -3
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +188 -236
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +8 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs.d.ts +5 -0
- package/dist/cli/runs.js +214 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/setup-commands.js +0 -0
- package/dist/cli/signal.js +1 -1
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/stop.d.ts +5 -0
- package/dist/cli/stop.js +215 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/cli/tasks.d.ts +10 -0
- package/dist/cli/tasks.js +165 -0
- package/dist/cli/tasks.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +212 -0
- package/dist/core/auto-recovery.js +737 -0
- package/dist/core/auto-recovery.js.map +1 -0
- package/dist/core/failure-policy.d.ts +156 -0
- package/dist/core/failure-policy.js +488 -0
- package/dist/core/failure-policy.js.map +1 -0
- package/dist/core/orchestrator.d.ts +15 -2
- package/dist/core/orchestrator.js +392 -15
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +2 -0
- package/dist/core/reviewer.js +2 -0
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +33 -10
- package/dist/core/runner.js +321 -146
- package/dist/core/runner.js.map +1 -1
- package/dist/services/logging/buffer.d.ts +67 -0
- package/dist/services/logging/buffer.js +309 -0
- package/dist/services/logging/buffer.js.map +1 -0
- package/dist/services/logging/console.d.ts +89 -0
- package/dist/services/logging/console.js +169 -0
- package/dist/services/logging/console.js.map +1 -0
- package/dist/services/logging/file-writer.d.ts +71 -0
- package/dist/services/logging/file-writer.js +516 -0
- package/dist/services/logging/file-writer.js.map +1 -0
- package/dist/services/logging/formatter.d.ts +39 -0
- package/dist/services/logging/formatter.js +227 -0
- package/dist/services/logging/formatter.js.map +1 -0
- package/dist/services/logging/index.d.ts +11 -0
- package/dist/services/logging/index.js +30 -0
- package/dist/services/logging/index.js.map +1 -0
- package/dist/services/logging/parser.d.ts +31 -0
- package/dist/services/logging/parser.js +222 -0
- package/dist/services/logging/parser.js.map +1 -0
- package/dist/services/process/index.d.ts +59 -0
- package/dist/services/process/index.js +257 -0
- package/dist/services/process/index.js.map +1 -0
- package/dist/types/agent.d.ts +20 -0
- package/dist/types/agent.js +6 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/config.d.ts +65 -0
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/events.d.ts +125 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lane.d.ts +43 -0
- package/dist/types/lane.js +6 -0
- package/dist/types/lane.js.map +1 -0
- package/dist/types/logging.d.ts +71 -0
- package/dist/types/logging.js +16 -0
- package/dist/types/logging.js.map +1 -0
- package/dist/types/review.d.ts +17 -0
- package/dist/types/review.js +6 -0
- package/dist/types/review.js.map +1 -0
- package/dist/types/run.d.ts +32 -0
- package/dist/types/run.js +6 -0
- package/dist/types/run.js.map +1 -0
- package/dist/types/task.d.ts +71 -0
- package/dist/types/task.js +6 -0
- package/dist/types/task.js.map +1 -0
- package/dist/ui/components.d.ts +134 -0
- package/dist/ui/components.js +389 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/log-viewer.d.ts +49 -0
- package/dist/ui/log-viewer.js +449 -0
- package/dist/ui/log-viewer.js.map +1 -0
- package/dist/utils/checkpoint.d.ts +87 -0
- package/dist/utils/checkpoint.js +317 -0
- package/dist/utils/checkpoint.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.js +11 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/dependency.d.ts +74 -0
- package/dist/utils/dependency.js +420 -0
- package/dist/utils/dependency.js.map +1 -0
- package/dist/utils/doctor.js +10 -5
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +94 -9
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +322 -2
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/health.d.ts +91 -0
- package/dist/utils/health.js +556 -0
- package/dist/utils/health.js.map +1 -0
- package/dist/utils/lock.d.ts +95 -0
- package/dist/utils/lock.js +332 -0
- package/dist/utils/lock.js.map +1 -0
- package/dist/utils/log-buffer.d.ts +17 -0
- package/dist/utils/log-buffer.js +14 -0
- package/dist/utils/log-buffer.js.map +1 -0
- package/dist/utils/log-constants.d.ts +23 -0
- package/dist/utils/log-constants.js +28 -0
- package/dist/utils/log-constants.js.map +1 -0
- package/dist/utils/log-formatter.d.ts +9 -0
- package/dist/utils/log-formatter.js +113 -70
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/log-service.d.ts +19 -0
- package/dist/utils/log-service.js +47 -0
- package/dist/utils/log-service.js.map +1 -0
- package/dist/utils/logger.d.ts +46 -27
- package/dist/utils/logger.js +82 -60
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/process-manager.d.ts +21 -0
- package/dist/utils/process-manager.js +138 -0
- package/dist/utils/process-manager.js.map +1 -0
- package/dist/utils/retry.d.ts +121 -0
- package/dist/utils/retry.js +374 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/run-service.d.ts +88 -0
- package/dist/utils/run-service.js +412 -0
- package/dist/utils/run-service.js.map +1 -0
- package/dist/utils/state.d.ts +58 -2
- package/dist/utils/state.js +306 -3
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +82 -0
- package/dist/utils/task-service.js +348 -0
- package/dist/utils/task-service.js.map +1 -0
- package/dist/utils/types.d.ts +2 -272
- package/dist/utils/types.js +16 -0
- package/dist/utils/types.js.map +1 -1
- package/package.json +38 -23
- package/scripts/ai-security-check.js +0 -1
- package/scripts/local-security-gate.sh +0 -0
- package/scripts/monitor-lanes.sh +94 -0
- package/scripts/patches/test-cursor-agent.js +0 -1
- package/scripts/release.sh +0 -0
- package/scripts/setup-security.sh +0 -0
- package/scripts/stream-logs.sh +72 -0
- package/scripts/verify-and-fix.sh +0 -0
- package/src/cli/clean.ts +180 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/init.ts +1 -1
- package/src/cli/logs.ts +79 -42
- package/src/cli/monitor.ts +1815 -899
- package/src/cli/prepare.ts +4 -3
- package/src/cli/resume.ts +220 -277
- package/src/cli/run.ts +9 -3
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +1 -1
- package/src/cli/stop.ts +209 -0
- package/src/cli/tasks.ts +154 -0
- package/src/core/auto-recovery.ts +909 -0
- package/src/core/failure-policy.ts +592 -0
- package/src/core/orchestrator.ts +1131 -675
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +388 -162
- package/src/services/logging/buffer.ts +326 -0
- package/src/services/logging/console.ts +193 -0
- package/src/services/logging/file-writer.ts +526 -0
- package/src/services/logging/formatter.ts +268 -0
- package/src/services/logging/index.ts +16 -0
- package/src/services/logging/parser.ts +232 -0
- package/src/services/process/index.ts +261 -0
- package/src/types/agent.ts +24 -0
- package/src/types/config.ts +79 -0
- package/src/types/events.ts +156 -0
- package/src/types/index.ts +29 -0
- package/src/types/lane.ts +56 -0
- package/src/types/logging.ts +96 -0
- package/src/types/review.ts +20 -0
- package/src/types/run.ts +37 -0
- package/src/types/task.ts +79 -0
- package/src/ui/components.ts +430 -0
- package/src/ui/log-viewer.ts +485 -0
- package/src/utils/checkpoint.ts +374 -0
- package/src/utils/config.ts +11 -2
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +11 -5
- package/src/utils/enhanced-logger.ts +108 -49
- package/src/utils/git.ts +374 -2
- package/src/utils/health.ts +596 -0
- package/src/utils/lock.ts +346 -0
- package/src/utils/log-buffer.ts +28 -0
- package/src/utils/log-constants.ts +26 -0
- package/src/utils/log-formatter.ts +120 -37
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- package/src/utils/process-manager.ts +100 -0
- package/src/utils/retry.ts +413 -0
- package/src/utils/run-service.ts +433 -0
- package/src/utils/state.ts +369 -3
- package/src/utils/task-service.ts +370 -0
- package/src/utils/types.ts +2 -315
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health check system for CursorFlow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { spawnSync } from 'child_process';
|
|
9
|
+
import { safeJoin } from './path';
|
|
10
|
+
import * as git from './git';
|
|
11
|
+
import { cleanStaleLocks, getLockDir, getLockStatus } from './lock';
|
|
12
|
+
import { checkCursorAgentInstalled, checkCursorAuth } from './cursor-agent';
|
|
13
|
+
import * as logger from './logger';
|
|
14
|
+
|
|
15
|
+
export interface HealthCheckResult {
|
|
16
|
+
name: string;
|
|
17
|
+
ok: boolean;
|
|
18
|
+
message: string;
|
|
19
|
+
details?: Record<string, any>;
|
|
20
|
+
latencyMs?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SystemHealth {
|
|
24
|
+
healthy: boolean;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
checks: HealthCheckResult[];
|
|
27
|
+
summary: {
|
|
28
|
+
passed: number;
|
|
29
|
+
failed: number;
|
|
30
|
+
warnings: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PreflightResult {
|
|
35
|
+
canProceed: boolean;
|
|
36
|
+
blockers: string[];
|
|
37
|
+
warnings: string[];
|
|
38
|
+
recommendations: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if cursor-agent is installed and responsive
|
|
43
|
+
*/
|
|
44
|
+
export async function checkAgentHealth(): Promise<HealthCheckResult> {
|
|
45
|
+
const start = Date.now();
|
|
46
|
+
|
|
47
|
+
if (!checkCursorAgentInstalled()) {
|
|
48
|
+
return {
|
|
49
|
+
name: 'cursor-agent',
|
|
50
|
+
ok: false,
|
|
51
|
+
message: 'cursor-agent CLI is not installed',
|
|
52
|
+
latencyMs: Date.now() - start,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const result = spawnSync('cursor-agent', ['--version'], {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
stdio: 'pipe',
|
|
60
|
+
timeout: 5000,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (result.status === 0) {
|
|
64
|
+
const version = result.stdout.trim();
|
|
65
|
+
return {
|
|
66
|
+
name: 'cursor-agent',
|
|
67
|
+
ok: true,
|
|
68
|
+
message: `cursor-agent installed (${version})`,
|
|
69
|
+
details: { version },
|
|
70
|
+
latencyMs: Date.now() - start,
|
|
71
|
+
};
|
|
72
|
+
} else {
|
|
73
|
+
return {
|
|
74
|
+
name: 'cursor-agent',
|
|
75
|
+
ok: false,
|
|
76
|
+
message: `cursor-agent error: ${result.stderr}`,
|
|
77
|
+
latencyMs: Date.now() - start,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
} catch (error: any) {
|
|
81
|
+
return {
|
|
82
|
+
name: 'cursor-agent',
|
|
83
|
+
ok: false,
|
|
84
|
+
message: `cursor-agent check failed: ${error.message}`,
|
|
85
|
+
latencyMs: Date.now() - start,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check cursor authentication status
|
|
92
|
+
*/
|
|
93
|
+
export async function checkAuthHealth(): Promise<HealthCheckResult> {
|
|
94
|
+
const start = Date.now();
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const authStatus = checkCursorAuth();
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
name: 'cursor-auth',
|
|
101
|
+
ok: authStatus.authenticated,
|
|
102
|
+
message: authStatus.message,
|
|
103
|
+
details: authStatus.details ? { details: authStatus.details } : undefined,
|
|
104
|
+
latencyMs: Date.now() - start,
|
|
105
|
+
};
|
|
106
|
+
} catch (error: any) {
|
|
107
|
+
return {
|
|
108
|
+
name: 'cursor-auth',
|
|
109
|
+
ok: false,
|
|
110
|
+
message: `Auth check failed: ${error.message}`,
|
|
111
|
+
latencyMs: Date.now() - start,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check Git repository status
|
|
118
|
+
*/
|
|
119
|
+
export async function checkGitHealth(cwd?: string): Promise<HealthCheckResult> {
|
|
120
|
+
const start = Date.now();
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
if (!git.isGitRepo(cwd)) {
|
|
124
|
+
return {
|
|
125
|
+
name: 'git-repo',
|
|
126
|
+
ok: false,
|
|
127
|
+
message: 'Not a Git repository',
|
|
128
|
+
latencyMs: Date.now() - start,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const branch = git.getCurrentBranch(cwd);
|
|
133
|
+
const hasRemote = git.remoteExists('origin', { cwd });
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
name: 'git-repo',
|
|
137
|
+
ok: true,
|
|
138
|
+
message: `Git repository OK (branch: ${branch})`,
|
|
139
|
+
details: {
|
|
140
|
+
branch,
|
|
141
|
+
hasRemote,
|
|
142
|
+
},
|
|
143
|
+
latencyMs: Date.now() - start,
|
|
144
|
+
};
|
|
145
|
+
} catch (error: any) {
|
|
146
|
+
return {
|
|
147
|
+
name: 'git-repo',
|
|
148
|
+
ok: false,
|
|
149
|
+
message: `Git check failed: ${error.message}`,
|
|
150
|
+
latencyMs: Date.now() - start,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check Git remote connectivity
|
|
157
|
+
*/
|
|
158
|
+
export async function checkGitRemoteHealth(cwd?: string): Promise<HealthCheckResult> {
|
|
159
|
+
const start = Date.now();
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
if (!git.remoteExists('origin', { cwd })) {
|
|
163
|
+
return {
|
|
164
|
+
name: 'git-remote',
|
|
165
|
+
ok: true, // Not an error, just not configured
|
|
166
|
+
message: 'No remote "origin" configured',
|
|
167
|
+
latencyMs: Date.now() - start,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Try to fetch with timeout
|
|
172
|
+
const result = spawnSync('git', ['ls-remote', '--exit-code', 'origin', 'HEAD'], {
|
|
173
|
+
cwd: cwd || process.cwd(),
|
|
174
|
+
encoding: 'utf8',
|
|
175
|
+
stdio: 'pipe',
|
|
176
|
+
timeout: 10000,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (result.status === 0) {
|
|
180
|
+
return {
|
|
181
|
+
name: 'git-remote',
|
|
182
|
+
ok: true,
|
|
183
|
+
message: 'Git remote is reachable',
|
|
184
|
+
latencyMs: Date.now() - start,
|
|
185
|
+
};
|
|
186
|
+
} else {
|
|
187
|
+
return {
|
|
188
|
+
name: 'git-remote',
|
|
189
|
+
ok: false,
|
|
190
|
+
message: `Git remote unreachable: ${result.stderr}`,
|
|
191
|
+
latencyMs: Date.now() - start,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
} catch (error: any) {
|
|
195
|
+
return {
|
|
196
|
+
name: 'git-remote',
|
|
197
|
+
ok: false,
|
|
198
|
+
message: `Git remote check failed: ${error.message}`,
|
|
199
|
+
latencyMs: Date.now() - start,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Check available disk space
|
|
206
|
+
*/
|
|
207
|
+
export async function checkDiskSpace(minMb: number = 500): Promise<HealthCheckResult> {
|
|
208
|
+
const start = Date.now();
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const cwd = process.cwd();
|
|
212
|
+
|
|
213
|
+
// Use df command to check disk space
|
|
214
|
+
const result = spawnSync('df', ['-m', cwd], {
|
|
215
|
+
encoding: 'utf8',
|
|
216
|
+
stdio: 'pipe',
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (result.status === 0) {
|
|
220
|
+
const lines = result.stdout.trim().split('\n');
|
|
221
|
+
if (lines.length >= 2) {
|
|
222
|
+
const parts = lines[1]!.split(/\s+/);
|
|
223
|
+
const availableMb = parseInt(parts[3] || '0');
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
name: 'disk-space',
|
|
227
|
+
ok: availableMb >= minMb,
|
|
228
|
+
message: availableMb >= minMb
|
|
229
|
+
? `${availableMb} MB available`
|
|
230
|
+
: `Low disk space: ${availableMb} MB (minimum: ${minMb} MB)`,
|
|
231
|
+
details: { availableMb, minMb },
|
|
232
|
+
latencyMs: Date.now() - start,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Fallback: assume OK if we can't check
|
|
238
|
+
return {
|
|
239
|
+
name: 'disk-space',
|
|
240
|
+
ok: true,
|
|
241
|
+
message: 'Could not determine disk space (assuming OK)',
|
|
242
|
+
latencyMs: Date.now() - start,
|
|
243
|
+
};
|
|
244
|
+
} catch (error: any) {
|
|
245
|
+
return {
|
|
246
|
+
name: 'disk-space',
|
|
247
|
+
ok: true, // Don't block on disk check failures
|
|
248
|
+
message: `Disk check skipped: ${error.message}`,
|
|
249
|
+
latencyMs: Date.now() - start,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Check for stale lock files
|
|
256
|
+
*/
|
|
257
|
+
export async function checkLockFiles(basePath?: string): Promise<HealthCheckResult> {
|
|
258
|
+
const start = Date.now();
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const lockDir = getLockDir(basePath || process.cwd());
|
|
262
|
+
|
|
263
|
+
if (!fs.existsSync(lockDir)) {
|
|
264
|
+
return {
|
|
265
|
+
name: 'lock-files',
|
|
266
|
+
ok: true,
|
|
267
|
+
message: 'No lock directory (OK)',
|
|
268
|
+
latencyMs: Date.now() - start,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const status = getLockStatus(lockDir);
|
|
273
|
+
const staleCount = status.filter(s => s.stale).length;
|
|
274
|
+
const activeCount = status.filter(s => !s.stale).length;
|
|
275
|
+
|
|
276
|
+
if (staleCount > 0) {
|
|
277
|
+
return {
|
|
278
|
+
name: 'lock-files',
|
|
279
|
+
ok: false,
|
|
280
|
+
message: `${staleCount} stale lock(s) found`,
|
|
281
|
+
details: { staleCount, activeCount, locks: status },
|
|
282
|
+
latencyMs: Date.now() - start,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
name: 'lock-files',
|
|
288
|
+
ok: true,
|
|
289
|
+
message: activeCount > 0 ? `${activeCount} active lock(s)` : 'No locks',
|
|
290
|
+
details: { activeCount },
|
|
291
|
+
latencyMs: Date.now() - start,
|
|
292
|
+
};
|
|
293
|
+
} catch (error: any) {
|
|
294
|
+
return {
|
|
295
|
+
name: 'lock-files',
|
|
296
|
+
ok: true, // Don't block on lock check failures
|
|
297
|
+
message: `Lock check skipped: ${error.message}`,
|
|
298
|
+
latencyMs: Date.now() - start,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Check system resources (CPU, memory)
|
|
305
|
+
*/
|
|
306
|
+
export async function checkSystemResources(): Promise<HealthCheckResult> {
|
|
307
|
+
const start = Date.now();
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
const totalMem = os.totalmem() / (1024 * 1024 * 1024); // GB
|
|
311
|
+
const freeMem = os.freemem() / (1024 * 1024 * 1024); // GB
|
|
312
|
+
const usedPercent = ((totalMem - freeMem) / totalMem) * 100;
|
|
313
|
+
const cpuCount = os.cpus().length;
|
|
314
|
+
const loadAvg = os.loadavg()[0]!; // 1-minute average
|
|
315
|
+
|
|
316
|
+
const memoryOk = freeMem > 1; // At least 1GB free
|
|
317
|
+
const cpuOk = loadAvg < cpuCount * 2; // Load less than 2x CPU count
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
name: 'system-resources',
|
|
321
|
+
ok: memoryOk && cpuOk,
|
|
322
|
+
message: memoryOk && cpuOk
|
|
323
|
+
? 'System resources OK'
|
|
324
|
+
: `System under load (Memory: ${usedPercent.toFixed(0)}%, Load: ${loadAvg.toFixed(1)})`,
|
|
325
|
+
details: {
|
|
326
|
+
totalMemoryGb: totalMem.toFixed(1),
|
|
327
|
+
freeMemoryGb: freeMem.toFixed(1),
|
|
328
|
+
memoryUsedPercent: usedPercent.toFixed(1),
|
|
329
|
+
cpuCount,
|
|
330
|
+
loadAverage: loadAvg.toFixed(2),
|
|
331
|
+
},
|
|
332
|
+
latencyMs: Date.now() - start,
|
|
333
|
+
};
|
|
334
|
+
} catch (error: any) {
|
|
335
|
+
return {
|
|
336
|
+
name: 'system-resources',
|
|
337
|
+
ok: true, // Don't block on resource check failures
|
|
338
|
+
message: `Resource check skipped: ${error.message}`,
|
|
339
|
+
latencyMs: Date.now() - start,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Check worktrees status
|
|
346
|
+
*/
|
|
347
|
+
export async function checkWorktrees(cwd?: string): Promise<HealthCheckResult> {
|
|
348
|
+
const start = Date.now();
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const worktrees = git.listWorktrees(cwd);
|
|
352
|
+
const orphaned: string[] = [];
|
|
353
|
+
|
|
354
|
+
for (const wt of worktrees) {
|
|
355
|
+
if (wt.path && !fs.existsSync(wt.path)) {
|
|
356
|
+
orphaned.push(wt.path);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (orphaned.length > 0) {
|
|
361
|
+
return {
|
|
362
|
+
name: 'worktrees',
|
|
363
|
+
ok: false,
|
|
364
|
+
message: `${orphaned.length} orphaned worktree(s) found`,
|
|
365
|
+
details: { total: worktrees.length, orphaned },
|
|
366
|
+
latencyMs: Date.now() - start,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
name: 'worktrees',
|
|
372
|
+
ok: true,
|
|
373
|
+
message: `${worktrees.length} worktree(s) OK`,
|
|
374
|
+
details: { count: worktrees.length },
|
|
375
|
+
latencyMs: Date.now() - start,
|
|
376
|
+
};
|
|
377
|
+
} catch (error: any) {
|
|
378
|
+
return {
|
|
379
|
+
name: 'worktrees',
|
|
380
|
+
ok: true, // Don't block on worktree check failures
|
|
381
|
+
message: `Worktree check skipped: ${error.message}`,
|
|
382
|
+
latencyMs: Date.now() - start,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Run all health checks
|
|
389
|
+
*/
|
|
390
|
+
export async function runHealthCheck(options: {
|
|
391
|
+
cwd?: string;
|
|
392
|
+
skipRemote?: boolean;
|
|
393
|
+
skipAuth?: boolean;
|
|
394
|
+
} = {}): Promise<SystemHealth> {
|
|
395
|
+
const checks: HealthCheckResult[] = [];
|
|
396
|
+
|
|
397
|
+
// Run checks in parallel where possible
|
|
398
|
+
const [agentHealth, gitHealth, diskSpace, lockFiles, sysResources, worktrees] = await Promise.all([
|
|
399
|
+
checkAgentHealth(),
|
|
400
|
+
checkGitHealth(options.cwd),
|
|
401
|
+
checkDiskSpace(),
|
|
402
|
+
checkLockFiles(options.cwd),
|
|
403
|
+
checkSystemResources(),
|
|
404
|
+
checkWorktrees(options.cwd),
|
|
405
|
+
]);
|
|
406
|
+
|
|
407
|
+
checks.push(agentHealth, gitHealth, diskSpace, lockFiles, sysResources, worktrees);
|
|
408
|
+
|
|
409
|
+
// Remote checks (might be slow, so optional)
|
|
410
|
+
if (!options.skipRemote) {
|
|
411
|
+
checks.push(await checkGitRemoteHealth(options.cwd));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Auth check (requires network, so optional)
|
|
415
|
+
if (!options.skipAuth) {
|
|
416
|
+
checks.push(await checkAuthHealth());
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const passed = checks.filter(c => c.ok).length;
|
|
420
|
+
const failed = checks.filter(c => !c.ok).length;
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
healthy: failed === 0,
|
|
424
|
+
timestamp: Date.now(),
|
|
425
|
+
checks,
|
|
426
|
+
summary: {
|
|
427
|
+
passed,
|
|
428
|
+
failed,
|
|
429
|
+
warnings: 0, // Could be extended to include warnings
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Run preflight checks before starting orchestration
|
|
436
|
+
*/
|
|
437
|
+
export async function preflightCheck(options: {
|
|
438
|
+
cwd?: string;
|
|
439
|
+
requireRemote?: boolean;
|
|
440
|
+
requireAuth?: boolean;
|
|
441
|
+
} = {}): Promise<PreflightResult> {
|
|
442
|
+
const blockers: string[] = [];
|
|
443
|
+
const warnings: string[] = [];
|
|
444
|
+
const recommendations: string[] = [];
|
|
445
|
+
|
|
446
|
+
// Check cursor-agent
|
|
447
|
+
const agentHealth = await checkAgentHealth();
|
|
448
|
+
if (!agentHealth.ok) {
|
|
449
|
+
blockers.push(`cursor-agent: ${agentHealth.message}`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Check Git repository
|
|
453
|
+
const gitHealth = await checkGitHealth(options.cwd);
|
|
454
|
+
if (!gitHealth.ok) {
|
|
455
|
+
blockers.push(`Git: ${gitHealth.message}`);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Check authentication
|
|
459
|
+
if (options.requireAuth !== false) {
|
|
460
|
+
const authHealth = await checkAuthHealth();
|
|
461
|
+
if (!authHealth.ok) {
|
|
462
|
+
blockers.push(`Authentication: ${authHealth.message}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Check Git remote (warning only unless required)
|
|
467
|
+
const remoteHealth = await checkGitRemoteHealth(options.cwd);
|
|
468
|
+
if (!remoteHealth.ok) {
|
|
469
|
+
if (options.requireRemote) {
|
|
470
|
+
blockers.push(`Git remote: ${remoteHealth.message}`);
|
|
471
|
+
} else {
|
|
472
|
+
warnings.push(`Git remote: ${remoteHealth.message}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Check disk space
|
|
477
|
+
const diskHealth = await checkDiskSpace();
|
|
478
|
+
if (!diskHealth.ok) {
|
|
479
|
+
warnings.push(`Disk space: ${diskHealth.message}`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Check for stale locks
|
|
483
|
+
const lockHealth = await checkLockFiles(options.cwd);
|
|
484
|
+
if (!lockHealth.ok) {
|
|
485
|
+
warnings.push(`Locks: ${lockHealth.message}`);
|
|
486
|
+
recommendations.push('Run `cursorflow clean locks` to remove stale locks');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Check worktrees
|
|
490
|
+
const worktreeHealth = await checkWorktrees(options.cwd);
|
|
491
|
+
if (!worktreeHealth.ok) {
|
|
492
|
+
warnings.push(`Worktrees: ${worktreeHealth.message}`);
|
|
493
|
+
recommendations.push('Run `cursorflow clean worktrees` to clean up orphaned worktrees');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Check system resources
|
|
497
|
+
const resourceHealth = await checkSystemResources();
|
|
498
|
+
if (!resourceHealth.ok) {
|
|
499
|
+
warnings.push(`Resources: ${resourceHealth.message}`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
canProceed: blockers.length === 0,
|
|
504
|
+
blockers,
|
|
505
|
+
warnings,
|
|
506
|
+
recommendations,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Print health check results to console
|
|
512
|
+
*/
|
|
513
|
+
export function printHealthReport(health: SystemHealth): void {
|
|
514
|
+
logger.section('🏥 System Health Check');
|
|
515
|
+
|
|
516
|
+
for (const check of health.checks) {
|
|
517
|
+
const icon = check.ok ? '✅' : '❌';
|
|
518
|
+
const latency = check.latencyMs ? ` (${check.latencyMs}ms)` : '';
|
|
519
|
+
console.log(`${icon} ${check.name}: ${check.message}${latency}`);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
console.log('');
|
|
523
|
+
console.log(`Summary: ${health.summary.passed} passed, ${health.summary.failed} failed`);
|
|
524
|
+
console.log(`Overall: ${health.healthy ? '✅ Healthy' : '❌ Unhealthy'}`);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Print preflight check results to console
|
|
529
|
+
*/
|
|
530
|
+
export function printPreflightReport(result: PreflightResult): void {
|
|
531
|
+
logger.section('🚀 Preflight Check');
|
|
532
|
+
|
|
533
|
+
if (result.blockers.length > 0) {
|
|
534
|
+
console.log('❌ Blockers:');
|
|
535
|
+
for (const blocker of result.blockers) {
|
|
536
|
+
console.log(` • ${blocker}`);
|
|
537
|
+
}
|
|
538
|
+
console.log('');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (result.warnings.length > 0) {
|
|
542
|
+
console.log('⚠️ Warnings:');
|
|
543
|
+
for (const warning of result.warnings) {
|
|
544
|
+
console.log(` • ${warning}`);
|
|
545
|
+
}
|
|
546
|
+
console.log('');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (result.recommendations.length > 0) {
|
|
550
|
+
console.log('💡 Recommendations:');
|
|
551
|
+
for (const rec of result.recommendations) {
|
|
552
|
+
console.log(` • ${rec}`);
|
|
553
|
+
}
|
|
554
|
+
console.log('');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (result.canProceed) {
|
|
558
|
+
logger.success('Preflight check passed!');
|
|
559
|
+
} else {
|
|
560
|
+
logger.error('Preflight check failed. Please fix the blockers above.');
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Auto-repair common issues found during health check
|
|
566
|
+
*/
|
|
567
|
+
export async function autoRepair(options: { cwd?: string } = {}): Promise<{ repaired: string[]; failed: string[] }> {
|
|
568
|
+
const repaired: string[] = [];
|
|
569
|
+
const failed: string[] = [];
|
|
570
|
+
|
|
571
|
+
// Clean stale locks
|
|
572
|
+
try {
|
|
573
|
+
const lockDir = getLockDir(options.cwd || process.cwd());
|
|
574
|
+
if (fs.existsSync(lockDir)) {
|
|
575
|
+
const cleaned = cleanStaleLocks(lockDir);
|
|
576
|
+
if (cleaned > 0) {
|
|
577
|
+
repaired.push(`Cleaned ${cleaned} stale lock(s)`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
} catch (e: any) {
|
|
581
|
+
failed.push(`Lock cleanup failed: ${e.message}`);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Prune orphaned worktrees
|
|
585
|
+
try {
|
|
586
|
+
const result = git.runGitResult(['worktree', 'prune'], { cwd: options.cwd });
|
|
587
|
+
if (result.success) {
|
|
588
|
+
repaired.push('Pruned orphaned worktrees');
|
|
589
|
+
}
|
|
590
|
+
} catch (e: any) {
|
|
591
|
+
failed.push(`Worktree prune failed: ${e.message}`);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return { repaired, failed };
|
|
595
|
+
}
|
|
596
|
+
|