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/monitor/health.mjs
DELETED
|
@@ -1,486 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* health.mjs — System health checks and monitoring
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { spawnSync } from 'child_process';
|
|
6
|
-
import { existsSync } from 'fs';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
import { atomicAppendJSONL, lockedReadModifyWrite } from '../state/atomic.mjs';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Health monitoring class
|
|
12
|
-
*/
|
|
13
|
-
export class HealthMonitor {
|
|
14
|
-
constructor(workspace = process.cwd()) {
|
|
15
|
-
this.workspace = workspace;
|
|
16
|
-
this.healthDir = join(workspace, '.cortex', 'health');
|
|
17
|
-
this.startTime = Date.now();
|
|
18
|
-
this.checks = new Map();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Ensure health directory exists
|
|
23
|
-
*/
|
|
24
|
-
ensureHealthDir() {
|
|
25
|
-
if (!existsSync(this.healthDir)) {
|
|
26
|
-
require('fs').mkdirSync(this.healthDir, { recursive: true });
|
|
27
|
-
}
|
|
28
|
-
return this.healthDir;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Check CLI availability and response time
|
|
33
|
-
*/
|
|
34
|
-
async checkCliHealth(provider, command, args = ['--version']) {
|
|
35
|
-
const startTime = Date.now();
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const result = spawnSync(command, args, {
|
|
39
|
-
encoding: 'utf8',
|
|
40
|
-
stdio: 'pipe',
|
|
41
|
-
timeout: 10000 // 10 second timeout
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const responseTime = Date.now() - startTime;
|
|
45
|
-
|
|
46
|
-
const healthData = {
|
|
47
|
-
provider,
|
|
48
|
-
command,
|
|
49
|
-
timestamp: Date.now(),
|
|
50
|
-
available: result.status === 0,
|
|
51
|
-
responseTime,
|
|
52
|
-
version: result.status === 0 ? result.stdout.trim().split('\n')[0] : null,
|
|
53
|
-
error: result.status !== 0 ? (result.stderr || 'Command failed') : null
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
this.recordHealthCheck(provider, healthData);
|
|
57
|
-
return healthData;
|
|
58
|
-
|
|
59
|
-
} catch (error) {
|
|
60
|
-
const responseTime = Date.now() - startTime;
|
|
61
|
-
|
|
62
|
-
const healthData = {
|
|
63
|
-
provider,
|
|
64
|
-
command,
|
|
65
|
-
timestamp: Date.now(),
|
|
66
|
-
available: false,
|
|
67
|
-
responseTime,
|
|
68
|
-
version: null,
|
|
69
|
-
error: error.code === 'ENOENT' ? 'Command not found' : error.message
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
this.recordHealthCheck(provider, healthData);
|
|
73
|
-
return healthData;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Check network connectivity to provider APIs
|
|
79
|
-
*/
|
|
80
|
-
async checkNetworkHealth(provider, endpoint) {
|
|
81
|
-
return new Promise((resolve) => {
|
|
82
|
-
const startTime = Date.now();
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
const https = require('https');
|
|
86
|
-
const url = new URL(endpoint);
|
|
87
|
-
|
|
88
|
-
const req = https.request({
|
|
89
|
-
hostname: url.hostname,
|
|
90
|
-
port: url.port || 443,
|
|
91
|
-
path: url.pathname,
|
|
92
|
-
method: 'HEAD',
|
|
93
|
-
timeout: 5000
|
|
94
|
-
}, (res) => {
|
|
95
|
-
const responseTime = Date.now() - startTime;
|
|
96
|
-
|
|
97
|
-
const healthData = {
|
|
98
|
-
provider,
|
|
99
|
-
endpoint,
|
|
100
|
-
timestamp: Date.now(),
|
|
101
|
-
available: res.statusCode < 400,
|
|
102
|
-
responseTime,
|
|
103
|
-
statusCode: res.statusCode,
|
|
104
|
-
error: res.statusCode >= 400 ? `HTTP ${res.statusCode}` : null
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
this.recordHealthCheck(`${provider}_network`, healthData);
|
|
108
|
-
resolve(healthData);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
req.on('error', (error) => {
|
|
112
|
-
const responseTime = Date.now() - startTime;
|
|
113
|
-
|
|
114
|
-
const healthData = {
|
|
115
|
-
provider,
|
|
116
|
-
endpoint,
|
|
117
|
-
timestamp: Date.now(),
|
|
118
|
-
available: false,
|
|
119
|
-
responseTime,
|
|
120
|
-
statusCode: null,
|
|
121
|
-
error: error.message
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
this.recordHealthCheck(`${provider}_network`, healthData);
|
|
125
|
-
resolve(healthData);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
req.on('timeout', () => {
|
|
129
|
-
const responseTime = Date.now() - startTime;
|
|
130
|
-
|
|
131
|
-
const healthData = {
|
|
132
|
-
provider,
|
|
133
|
-
endpoint,
|
|
134
|
-
timestamp: Date.now(),
|
|
135
|
-
available: false,
|
|
136
|
-
responseTime,
|
|
137
|
-
statusCode: null,
|
|
138
|
-
error: 'Timeout'
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
this.recordHealthCheck(`${provider}_network`, healthData);
|
|
142
|
-
resolve(healthData);
|
|
143
|
-
req.destroy();
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
req.end();
|
|
147
|
-
} catch (error) {
|
|
148
|
-
const responseTime = Date.now() - startTime;
|
|
149
|
-
|
|
150
|
-
const healthData = {
|
|
151
|
-
provider,
|
|
152
|
-
endpoint,
|
|
153
|
-
timestamp: Date.now(),
|
|
154
|
-
available: false,
|
|
155
|
-
responseTime,
|
|
156
|
-
statusCode: null,
|
|
157
|
-
error: error.message
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
this.recordHealthCheck(`${provider}_network`, healthData);
|
|
161
|
-
resolve(healthData);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Check system resources
|
|
168
|
-
*/
|
|
169
|
-
checkSystemHealth() {
|
|
170
|
-
const memUsage = process.memoryUsage();
|
|
171
|
-
|
|
172
|
-
const healthData = {
|
|
173
|
-
timestamp: Date.now(),
|
|
174
|
-
memory: {
|
|
175
|
-
heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024), // MB
|
|
176
|
-
heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024),
|
|
177
|
-
external: Math.round(memUsage.external / 1024 / 1024),
|
|
178
|
-
rss: Math.round(memUsage.rss / 1024 / 1024)
|
|
179
|
-
},
|
|
180
|
-
uptime: Math.round(process.uptime() * 1000), // ms
|
|
181
|
-
loadAverage: process.platform !== 'win32' ? process.loadavg() : null,
|
|
182
|
-
platform: process.platform,
|
|
183
|
-
arch: process.arch,
|
|
184
|
-
nodeVersion: process.version
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
this.recordHealthCheck('system', healthData);
|
|
188
|
-
return healthData;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Record health check result
|
|
193
|
-
*/
|
|
194
|
-
recordHealthCheck(component, data) {
|
|
195
|
-
this.checks.set(component, data);
|
|
196
|
-
|
|
197
|
-
// Persist to file
|
|
198
|
-
this.ensureHealthDir();
|
|
199
|
-
const logPath = join(this.healthDir, `${component}-health.jsonl`);
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
atomicAppendJSONL(logPath, data);
|
|
203
|
-
} catch (error) {
|
|
204
|
-
console.warn(`Failed to log health check for ${component}:`, error.message);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Get recent health status for a component
|
|
210
|
-
*/
|
|
211
|
-
getRecentHealth(component, timeWindowMs = 5 * 60 * 1000) {
|
|
212
|
-
const logPath = join(this.healthDir, `${component}-health.jsonl`);
|
|
213
|
-
|
|
214
|
-
if (!existsSync(logPath)) {
|
|
215
|
-
return [];
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
const content = require('fs').readFileSync(logPath, 'utf8');
|
|
220
|
-
const cutoff = Date.now() - timeWindowMs;
|
|
221
|
-
|
|
222
|
-
return content
|
|
223
|
-
.trim()
|
|
224
|
-
.split('\n')
|
|
225
|
-
.filter(line => line.trim())
|
|
226
|
-
.map(line => {
|
|
227
|
-
try {
|
|
228
|
-
return JSON.parse(line);
|
|
229
|
-
} catch {
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
})
|
|
233
|
-
.filter(Boolean)
|
|
234
|
-
.filter(entry => entry.timestamp > cutoff)
|
|
235
|
-
.sort((a, b) => a.timestamp - b.timestamp);
|
|
236
|
-
} catch {
|
|
237
|
-
return [];
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Calculate component health score
|
|
243
|
-
*/
|
|
244
|
-
calculateHealthScore(component, timeWindowMs = 15 * 60 * 1000) {
|
|
245
|
-
const recent = this.getRecentHealth(component, timeWindowMs);
|
|
246
|
-
|
|
247
|
-
if (recent.length === 0) {
|
|
248
|
-
return { score: 0, status: 'unknown', message: 'No recent data' };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const availabilityRatio = recent.filter(entry => entry.available).length / recent.length;
|
|
252
|
-
const avgResponseTime = recent
|
|
253
|
-
.filter(entry => entry.available && entry.responseTime)
|
|
254
|
-
.reduce((sum, entry) => sum + entry.responseTime, 0) / Math.max(recent.length, 1);
|
|
255
|
-
|
|
256
|
-
let score = availabilityRatio * 100;
|
|
257
|
-
|
|
258
|
-
// Penalize high response times
|
|
259
|
-
if (avgResponseTime > 5000) {
|
|
260
|
-
score *= 0.7; // High latency
|
|
261
|
-
} else if (avgResponseTime > 2000) {
|
|
262
|
-
score *= 0.9; // Moderate latency
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const status = score >= 90 ? 'healthy' :
|
|
266
|
-
score >= 70 ? 'degraded' :
|
|
267
|
-
score >= 50 ? 'unhealthy' : 'critical';
|
|
268
|
-
|
|
269
|
-
return {
|
|
270
|
-
score: Math.round(score),
|
|
271
|
-
status,
|
|
272
|
-
availabilityRatio,
|
|
273
|
-
avgResponseTime: Math.round(avgResponseTime),
|
|
274
|
-
recentChecks: recent.length,
|
|
275
|
-
message: this.getHealthMessage(status, availabilityRatio, avgResponseTime)
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Get health status message
|
|
281
|
-
*/
|
|
282
|
-
getHealthMessage(status, availability, responseTime) {
|
|
283
|
-
switch (status) {
|
|
284
|
-
case 'healthy':
|
|
285
|
-
return `Healthy - ${(availability * 100).toFixed(1)}% availability`;
|
|
286
|
-
|
|
287
|
-
case 'degraded':
|
|
288
|
-
if (availability < 0.8) {
|
|
289
|
-
return `Degraded - ${(availability * 100).toFixed(1)}% availability`;
|
|
290
|
-
} else {
|
|
291
|
-
return `Degraded - High latency (${responseTime}ms)`;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
case 'unhealthy':
|
|
295
|
-
return `Unhealthy - ${(availability * 100).toFixed(1)}% availability, ${responseTime}ms latency`;
|
|
296
|
-
|
|
297
|
-
case 'critical':
|
|
298
|
-
return `Critical - ${(availability * 100).toFixed(1)}% availability`;
|
|
299
|
-
|
|
300
|
-
default:
|
|
301
|
-
return 'Unknown status';
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Comprehensive health check
|
|
307
|
-
*/
|
|
308
|
-
async runComprehensiveCheck() {
|
|
309
|
-
const results = {
|
|
310
|
-
timestamp: Date.now(),
|
|
311
|
-
workspace: this.workspace,
|
|
312
|
-
components: {},
|
|
313
|
-
overall: { score: 0, status: 'unknown' }
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
// Check CLI health
|
|
317
|
-
results.components.claude = await this.checkCliHealth('claude', 'claude');
|
|
318
|
-
results.components.codex = await this.checkCliHealth('codex', 'codex');
|
|
319
|
-
|
|
320
|
-
// Check network health
|
|
321
|
-
results.components.claude_network = await this.checkNetworkHealth('claude', 'https://console.anthropic.com');
|
|
322
|
-
results.components.openai_network = await this.checkNetworkHealth('openai', 'https://api.openai.com');
|
|
323
|
-
|
|
324
|
-
// Check system health
|
|
325
|
-
results.components.system = this.checkSystemHealth();
|
|
326
|
-
|
|
327
|
-
// Calculate health scores
|
|
328
|
-
for (const [component, data] of Object.entries(results.components)) {
|
|
329
|
-
if (data.timestamp) {
|
|
330
|
-
const score = this.calculateHealthScore(component);
|
|
331
|
-
results.components[component].healthScore = score;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Calculate overall score
|
|
336
|
-
const scores = Object.values(results.components)
|
|
337
|
-
.map(comp => comp.healthScore?.score || 0)
|
|
338
|
-
.filter(score => score > 0);
|
|
339
|
-
|
|
340
|
-
if (scores.length > 0) {
|
|
341
|
-
results.overall.score = Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length);
|
|
342
|
-
results.overall.status = results.overall.score >= 80 ? 'healthy' :
|
|
343
|
-
results.overall.score >= 60 ? 'degraded' :
|
|
344
|
-
results.overall.score >= 40 ? 'unhealthy' : 'critical';
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Update running health statistics
|
|
348
|
-
this.updateHealthStats(results);
|
|
349
|
-
|
|
350
|
-
return results;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Update running health statistics
|
|
355
|
-
*/
|
|
356
|
-
updateHealthStats(results) {
|
|
357
|
-
const statsPath = join(this.healthDir, 'running-health-stats.json');
|
|
358
|
-
|
|
359
|
-
try {
|
|
360
|
-
return lockedReadModifyWrite(statsPath, (stats) => {
|
|
361
|
-
const updated = stats || {
|
|
362
|
-
totalChecks: 0,
|
|
363
|
-
averageScore: 0,
|
|
364
|
-
downtimeEvents: 0,
|
|
365
|
-
lastCheck: null,
|
|
366
|
-
components: {}
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
updated.totalChecks++;
|
|
370
|
-
updated.lastCheck = results.timestamp;
|
|
371
|
-
|
|
372
|
-
// Update overall average
|
|
373
|
-
const newScore = results.overall.score;
|
|
374
|
-
updated.averageScore = ((updated.averageScore * (updated.totalChecks - 1)) + newScore) / updated.totalChecks;
|
|
375
|
-
|
|
376
|
-
// Track component statistics
|
|
377
|
-
for (const [component, data] of Object.entries(results.components)) {
|
|
378
|
-
if (!updated.components[component]) {
|
|
379
|
-
updated.components[component] = {
|
|
380
|
-
checks: 0,
|
|
381
|
-
averageScore: 0,
|
|
382
|
-
downtimeEvents: 0,
|
|
383
|
-
lastDowntime: null
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const compStats = updated.components[component];
|
|
388
|
-
compStats.checks++;
|
|
389
|
-
|
|
390
|
-
if (data.healthScore) {
|
|
391
|
-
compStats.averageScore = ((compStats.averageScore * (compStats.checks - 1)) + data.healthScore.score) / compStats.checks;
|
|
392
|
-
|
|
393
|
-
// Track downtime events
|
|
394
|
-
if (data.healthScore.status === 'critical' || data.healthScore.status === 'unhealthy') {
|
|
395
|
-
if (compStats.lastDowntime === null || (results.timestamp - compStats.lastDowntime) > 60 * 60 * 1000) {
|
|
396
|
-
compStats.downtimeEvents++;
|
|
397
|
-
compStats.lastDowntime = results.timestamp;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
return updated;
|
|
404
|
-
});
|
|
405
|
-
} catch (error) {
|
|
406
|
-
console.warn('Failed to update health stats:', error.message);
|
|
407
|
-
return null;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Get health trends
|
|
413
|
-
*/
|
|
414
|
-
getHealthTrends(component, timeWindowMs = 24 * 60 * 60 * 1000) {
|
|
415
|
-
const recent = this.getRecentHealth(component, timeWindowMs);
|
|
416
|
-
|
|
417
|
-
if (recent.length < 2) {
|
|
418
|
-
return { trend: 'insufficient_data', slope: 0 };
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Calculate availability trend over time
|
|
422
|
-
const dataPoints = recent.map((entry, index) => ({
|
|
423
|
-
x: index,
|
|
424
|
-
y: entry.available ? 1 : 0
|
|
425
|
-
}));
|
|
426
|
-
|
|
427
|
-
// Simple linear regression to determine trend
|
|
428
|
-
const n = dataPoints.length;
|
|
429
|
-
const sumX = dataPoints.reduce((sum, p) => sum + p.x, 0);
|
|
430
|
-
const sumY = dataPoints.reduce((sum, p) => sum + p.y, 0);
|
|
431
|
-
const sumXY = dataPoints.reduce((sum, p) => sum + (p.x * p.y), 0);
|
|
432
|
-
const sumXX = dataPoints.reduce((sum, p) => sum + (p.x * p.x), 0);
|
|
433
|
-
|
|
434
|
-
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
|
|
435
|
-
|
|
436
|
-
const trend = slope > 0.01 ? 'improving' :
|
|
437
|
-
slope < -0.01 ? 'declining' : 'stable';
|
|
438
|
-
|
|
439
|
-
return {
|
|
440
|
-
trend,
|
|
441
|
-
slope: slope.toFixed(4),
|
|
442
|
-
dataPoints: dataPoints.length,
|
|
443
|
-
availability: (sumY / n).toFixed(3)
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Global health monitor instance
|
|
450
|
-
*/
|
|
451
|
-
let globalHealthMonitor = null;
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Get or create global health monitor
|
|
455
|
-
*/
|
|
456
|
-
export function getHealthMonitor(workspace = process.cwd()) {
|
|
457
|
-
if (!globalHealthMonitor) {
|
|
458
|
-
globalHealthMonitor = new HealthMonitor(workspace);
|
|
459
|
-
}
|
|
460
|
-
return globalHealthMonitor;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Run health check
|
|
465
|
-
*/
|
|
466
|
-
export async function runHealthCheck(workspace = process.cwd()) {
|
|
467
|
-
const monitor = getHealthMonitor(workspace);
|
|
468
|
-
return await monitor.runComprehensiveCheck();
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Get component health score
|
|
473
|
-
*/
|
|
474
|
-
export function getComponentHealth(component, workspace = process.cwd()) {
|
|
475
|
-
const monitor = getHealthMonitor(workspace);
|
|
476
|
-
return monitor.calculateHealthScore(component);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Check if a provider is healthy
|
|
481
|
-
*/
|
|
482
|
-
export function isProviderHealthy(provider, workspace = process.cwd()) {
|
|
483
|
-
const monitor = getHealthMonitor(workspace);
|
|
484
|
-
const health = monitor.calculateHealthScore(provider);
|
|
485
|
-
return health.status === 'healthy' || health.status === 'degraded';
|
|
486
|
-
}
|