thuban 0.3.3 → 0.3.4

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.
Files changed (66) hide show
  1. package/dist/LICENSE +21 -0
  2. package/dist/README.md +185 -0
  3. package/dist/cli.js +2 -0
  4. package/dist/packages/scanner/ai-confidence-scorer.js +1 -0
  5. package/dist/packages/scanner/alert-manager.js +1 -0
  6. package/dist/packages/scanner/baseline-manager.js +1 -0
  7. package/dist/packages/scanner/code-scanner.js +1 -0
  8. package/dist/packages/scanner/codebase-passport.js +1 -0
  9. package/dist/packages/scanner/copy-paste-detector.js +1 -0
  10. package/dist/packages/scanner/dependency-graph.js +1 -0
  11. package/dist/packages/scanner/drift-detector.js +1 -0
  12. package/dist/packages/scanner/executive-report.js +1 -0
  13. package/dist/packages/scanner/file-collector.js +1 -0
  14. package/dist/packages/scanner/file-watcher.js +1 -0
  15. package/dist/packages/scanner/ghost-code-detector.js +1 -0
  16. package/dist/packages/scanner/hallucination-detector.js +1 -0
  17. package/dist/packages/scanner/health-checker.js +1 -0
  18. package/dist/packages/scanner/html-report.js +1 -0
  19. package/dist/packages/scanner/index.js +1 -0
  20. package/dist/packages/scanner/license-manager.js +1 -0
  21. package/dist/packages/scanner/master-health-checker.js +1 -0
  22. package/dist/packages/scanner/monitor-notifier.js +1 -0
  23. package/dist/packages/scanner/monitor-service.js +1 -0
  24. package/dist/packages/scanner/monitor-store.js +1 -0
  25. package/dist/packages/scanner/pre-commit-gate.js +1 -0
  26. package/dist/packages/scanner/scan-diff.js +1 -0
  27. package/dist/packages/scanner/scan-runner.js +1 -0
  28. package/dist/packages/scanner/secret-scanner.js +1 -0
  29. package/dist/packages/scanner/sentinel-core.js +1 -0
  30. package/dist/packages/scanner/sentinel-knowledge.js +1 -0
  31. package/dist/packages/scanner/tech-debt-analyzer.js +1 -0
  32. package/dist/packages/scanner/tech-debt-cost.js +1 -0
  33. package/dist/packages/scanner/widget-generator.js +1 -0
  34. package/package.json +12 -7
  35. package/cli.js +0 -2627
  36. package/packages/scanner/ai-confidence-scorer.js +0 -260
  37. package/packages/scanner/alert-manager.js +0 -398
  38. package/packages/scanner/baseline-manager.js +0 -109
  39. package/packages/scanner/code-scanner.js +0 -758
  40. package/packages/scanner/codebase-passport.js +0 -299
  41. package/packages/scanner/copy-paste-detector.js +0 -276
  42. package/packages/scanner/dependency-graph.js +0 -541
  43. package/packages/scanner/drift-detector.js +0 -423
  44. package/packages/scanner/executive-report.js +0 -774
  45. package/packages/scanner/file-collector.js +0 -64
  46. package/packages/scanner/file-watcher.js +0 -323
  47. package/packages/scanner/ghost-code-detector.js +0 -301
  48. package/packages/scanner/hallucination-detector.js +0 -822
  49. package/packages/scanner/health-checker.js +0 -586
  50. package/packages/scanner/html-report.js +0 -634
  51. package/packages/scanner/index.js +0 -100
  52. package/packages/scanner/license-manager.js +0 -331
  53. package/packages/scanner/master-health-checker.js +0 -513
  54. package/packages/scanner/monitor-notifier.js +0 -64
  55. package/packages/scanner/monitor-service.js +0 -103
  56. package/packages/scanner/monitor-store.js +0 -117
  57. package/packages/scanner/pre-commit-gate.js +0 -216
  58. package/packages/scanner/scan-diff.js +0 -50
  59. package/packages/scanner/scan-runner.js +0 -172
  60. package/packages/scanner/secret-scanner.js +0 -612
  61. package/packages/scanner/secret-scanner.test.js +0 -103
  62. package/packages/scanner/sentinel-core.js +0 -616
  63. package/packages/scanner/sentinel-knowledge.js +0 -322
  64. package/packages/scanner/tech-debt-analyzer.js +0 -583
  65. package/packages/scanner/tech-debt-cost.js +0 -194
  66. package/packages/scanner/widget-generator.js +0 -415
@@ -1,513 +0,0 @@
1
- /**
2
- * Master Health Checker
3
- *
4
- * @purpose Comprehensive health monitoring for all 47 Orion failure points
5
- * @module sentinel
6
- * @layer infrastructure
7
- * @imports-from fs, path, child_process, events
8
- * @exports-to server/autonomous/health-scheduler.js
9
- * @side-effects File system reads, Process spawning, Network requests
10
- * @critical-for System reliability and self-healing
11
- */
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
- const { execSync, exec } = require('child_process');
16
- const EventEmitter = require('events');
17
-
18
- // Paths
19
- const ROOT = path.join(__dirname, '..');
20
- const SERVER = path.join(ROOT, 'server');
21
- const MANIFEST_PATH = path.join(SERVER, 'data', 'health-check-manifest.json');
22
-
23
- class MasterHealthChecker extends EventEmitter {
24
- constructor(config = {}) {
25
- super();
26
- this.config = {
27
- rootPath: config.rootPath || ROOT,
28
- serverPath: config.serverPath || SERVER,
29
- timeout: config.timeout || 5000,
30
- ...config
31
- };
32
-
33
- this.manifest = this._loadManifest();
34
- this.lastResults = null;
35
- this.issueCount = 0;
36
- }
37
-
38
- _loadManifest() {
39
- try {
40
- if (fs.existsSync(MANIFEST_PATH)) {
41
- return JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
42
- }
43
- } catch (e) {
44
- console.error('[MASTER-HEALTH] Failed to load manifest:', e.message);
45
- }
46
- return null;
47
- }
48
-
49
- /**
50
- * Run ALL 47 health checks
51
- */
52
- async runFullCheck() {
53
- console.log('[MASTER-HEALTH] Starting full system health check (47 checkpoints)...');
54
- const startTime = Date.now();
55
-
56
- const results = {
57
- timestamp: new Date().toISOString(),
58
- duration: 0,
59
- totalChecks: 0,
60
- passed: 0,
61
- failed: 0,
62
- warnings: 0,
63
- systems: {},
64
- criticalIssues: [],
65
- autoHealAttempted: [],
66
- humanEscalationNeeded: []
67
- };
68
-
69
- // Run checks for each system
70
- const systems = [
71
- { id: 'server', fn: () => this._checkServer() },
72
- { id: 'senate', fn: () => this._checkSenate() },
73
- { id: 'citadel', fn: () => this._checkCitadel() },
74
- { id: 'shadowRealm', fn: () => this._checkShadowRealm() },
75
- { id: 'forge', fn: () => this._checkForge() },
76
- { id: 'ocee', fn: () => this._checkOCEE() },
77
- { id: 'memory', fn: () => this._checkMemory() },
78
- { id: 'external', fn: () => this._checkExternal() }
79
- ];
80
-
81
- for (const system of systems) {
82
- try {
83
- const systemResult = await system.fn();
84
- results.systems[system.id] = systemResult;
85
-
86
- results.totalChecks += systemResult.checks.length;
87
- results.passed += systemResult.checks.filter(c => c.status === 'PASS').length;
88
- results.failed += systemResult.checks.filter(c => c.status === 'FAIL').length;
89
- results.warnings += systemResult.checks.filter(c => c.status === 'WARN').length;
90
-
91
- // Collect critical issues
92
- const critical = systemResult.checks.filter(c => c.status === 'FAIL' && c.criticality === 'CRITICAL');
93
- results.criticalIssues.push(...critical);
94
-
95
- // Collect escalation needs
96
- const needsHuman = systemResult.checks.filter(c => c.needsHumanEscalation);
97
- results.humanEscalationNeeded.push(...needsHuman);
98
-
99
- } catch (error) {
100
- results.systems[system.id] = {
101
- status: 'ERROR',
102
- error: error.message,
103
- checks: []
104
- };
105
- }
106
- }
107
-
108
- results.duration = Date.now() - startTime;
109
- results.overallStatus = this._calculateOverallStatus(results);
110
-
111
- this.lastResults = results;
112
- this.emit('checkComplete', results);
113
-
114
- console.log(`[MASTER-HEALTH] Complete: ${results.passed}/${results.totalChecks} passed (${results.duration}ms)`);
115
-
116
- return results;
117
- }
118
-
119
- /**
120
- * Run quick check (critical systems only)
121
- */
122
- async runQuickCheck() {
123
- console.log('[MASTER-HEALTH] Running quick check (critical systems)...');
124
-
125
- const results = {
126
- timestamp: new Date().toISOString(),
127
- type: 'quick',
128
- checks: []
129
- };
130
-
131
- // SRV-001: Server running
132
- results.checks.push(await this._check('SRV-001', 'Server Process', () => {
133
- return this._fileExists(path.join(this.config.serverPath, 'server.js'));
134
- }));
135
-
136
- // SEN-003: Providers responding (simplified)
137
- results.checks.push(await this._check('SEN-003', 'Model Registry', () => {
138
- const registry = path.join(this.config.serverPath, 'data', 'model-registry.json');
139
- if (!fs.existsSync(registry)) return false;
140
- const data = JSON.parse(fs.readFileSync(registry, 'utf8'));
141
- return Object.keys(data.providers || {}).length >= 3;
142
- }));
143
-
144
- // CIT-001: Constitution loaded
145
- results.checks.push(await this._check('CIT-001', 'Constitution', () => {
146
- const constitution = path.join(this.config.rootPath, 'citadel', 'constitution.json');
147
- return this._fileExists(constitution) && this._isValidJson(constitution);
148
- }));
149
-
150
- // CIT-002: File guard active
151
- results.checks.push(await this._check('CIT-002', 'File Guard', () => {
152
- return this._fileExists(path.join(this.config.rootPath, 'citadel', 'file-guard.js'));
153
- }));
154
-
155
- const passed = results.checks.filter(c => c.status === 'PASS').length;
156
- console.log(`[MASTER-HEALTH] Quick check: ${passed}/${results.checks.length} passed`);
157
-
158
- return results;
159
- }
160
-
161
- // ========== SYSTEM CHECKS ==========
162
-
163
- async _checkServer() {
164
- const checks = [];
165
-
166
- // SRV-001: Server process files exist
167
- checks.push(await this._check('SRV-001', 'Server Files', () => {
168
- return this._fileExists(path.join(this.config.serverPath, 'server.js')) &&
169
- this._fileExists(path.join(this.config.serverPath, 'package.json'));
170
- }, 'CRITICAL'));
171
-
172
- // SRV-002: Memory (check node_modules size as proxy)
173
- checks.push(await this._check('SRV-002', 'Node Modules', () => {
174
- return this._fileExists(path.join(this.config.serverPath, 'node_modules'));
175
- }, 'HIGH', 'npm install'));
176
-
177
- // SRV-003: Package.json valid
178
- checks.push(await this._check('SRV-003', 'Package Valid', () => {
179
- return this._isValidJson(path.join(this.config.serverPath, 'package.json'));
180
- }, 'HIGH'));
181
-
182
- // SRV-004: Data directory exists
183
- checks.push(await this._check('SRV-004', 'Data Directory', () => {
184
- return this._dirExists(path.join(this.config.serverPath, 'data'));
185
- }, 'MEDIUM'));
186
-
187
- // SRV-005: Environment file or vars
188
- checks.push(await this._check('SRV-005', 'Environment', () => {
189
- return this._fileExists(path.join(this.config.rootPath, '.env')) ||
190
- process.env.ANTHROPIC_API_KEY !== undefined;
191
- }, 'HIGH'));
192
-
193
- return { status: this._systemStatus(checks), checks };
194
- }
195
-
196
- async _checkSenate() {
197
- const checks = [];
198
-
199
- // SEN-001: Model registry
200
- checks.push(await this._check('SEN-001', 'Model Registry', () => {
201
- const file = path.join(this.config.serverPath, 'data', 'model-registry.json');
202
- return this._isValidJson(file);
203
- }, 'CRITICAL', 'Restore from snapshot'));
204
-
205
- // SEN-002: Senate weights
206
- checks.push(await this._check('SEN-002', 'Senate Weights', () => {
207
- const file = path.join(this.config.serverPath, 'data', 'senate-weights-v2.json');
208
- return this._isValidJson(file);
209
- }, 'HIGH', 'Reset to defaults'));
210
-
211
- // SEN-003: Providers configured
212
- checks.push(await this._check('SEN-003', 'Providers Configured', () => {
213
- const file = path.join(this.config.serverPath, 'data', 'model-registry.json');
214
- if (!this._isValidJson(file)) return false;
215
- const data = JSON.parse(fs.readFileSync(file, 'utf8'));
216
- return Object.keys(data.providers || {}).length >= 5;
217
- }, 'CRITICAL'));
218
-
219
- // SEN-004: Senate core files
220
- checks.push(await this._check('SEN-004', 'Senate Core', () => {
221
- return this._fileExists(path.join(this.config.serverPath, 'senate-v2.js')) ||
222
- this._fileExists(path.join(this.config.serverPath, 'senate.js'));
223
- }, 'CRITICAL'));
224
-
225
- // SEN-005: Response cache not corrupted
226
- checks.push(await this._check('SEN-005', 'Response Cache', () => {
227
- const file = path.join(this.config.serverPath, 'data', 'response-cache.json');
228
- return !this._fileExists(file) || this._isValidJson(file);
229
- }, 'LOW'));
230
-
231
- return { status: this._systemStatus(checks), checks };
232
- }
233
-
234
- async _checkCitadel() {
235
- const checks = [];
236
-
237
- // CIT-001: Constitution
238
- checks.push(await this._check('CIT-001', 'Constitution', () => {
239
- const file = path.join(this.config.rootPath, 'citadel', 'constitution.json');
240
- return this._isValidJson(file);
241
- }, 'CRITICAL', 'HALT - Safety rules missing'));
242
-
243
- // CIT-002: File guard
244
- checks.push(await this._check('CIT-002', 'File Guard', () => {
245
- return this._fileExists(path.join(this.config.rootPath, 'citadel', 'file-guard.js'));
246
- }, 'CRITICAL'));
247
-
248
- // CIT-003: Enforcer
249
- checks.push(await this._check('CIT-003', 'Enforcer', () => {
250
- return this._fileExists(path.join(this.config.rootPath, 'citadel', 'enforcer.js'));
251
- }, 'CRITICAL'));
252
-
253
- // CIT-004: Watchers
254
- checks.push(await this._check('CIT-004', 'Watchers', () => {
255
- return this._fileExists(path.join(this.config.rootPath, 'citadel', 'watchers.js'));
256
- }, 'HIGH'));
257
-
258
- // CIT-005: Audit directory
259
- checks.push(await this._check('CIT-005', 'Audit Directory', () => {
260
- return this._dirExists(path.join(this.config.rootPath, 'citadel', 'audit'));
261
- }, 'MEDIUM', 'Create directory'));
262
-
263
- return { status: this._systemStatus(checks), checks };
264
- }
265
-
266
- async _checkShadowRealm() {
267
- const checks = [];
268
-
269
- // SHD-001: Shadow realm file
270
- checks.push(await this._check('SHD-001', 'Shadow Realm V2', () => {
271
- return this._fileExists(path.join(this.config.rootPath, 'build-engine', 'orion-core', 'shadow-realm-v2.js'));
272
- }, 'CRITICAL'));
273
-
274
- // SHD-002: Node available
275
- checks.push(await this._check('SHD-002', 'Node Binary', () => {
276
- try {
277
- execSync('node --version', { stdio: 'pipe' });
278
- return true;
279
- } catch { return false; }
280
- }, 'CRITICAL'));
281
-
282
- // SHD-003: Temp directory writable
283
- checks.push(await this._check('SHD-003', 'Temp Writable', () => {
284
- const os = require('os');
285
- const testFile = path.join(os.tmpdir(), 'orion-health-test-' + Date.now());
286
- try {
287
- fs.writeFileSync(testFile, 'test');
288
- fs.unlinkSync(testFile);
289
- return true;
290
- } catch { return false; }
291
- }, 'HIGH'));
292
-
293
- // SHD-004: Healing loop
294
- checks.push(await this._check('SHD-004', 'Healing Loop', () => {
295
- return this._fileExists(path.join(this.config.rootPath, 'build-engine', 'orion-core', 'healing-loop.js'));
296
- }, 'HIGH'));
297
-
298
- return { status: this._systemStatus(checks), checks };
299
- }
300
-
301
- async _checkForge() {
302
- const checks = [];
303
- const forgePath = path.join(this.config.rootPath, 'forge');
304
-
305
- // FRG-001: Forge core
306
- checks.push(await this._check('FRG-001', 'Forge Core', () => {
307
- return this._fileExists(path.join(forgePath, 'forge-core.js'));
308
- }, 'HIGH'));
309
-
310
- // FRG-002: Task decomposer
311
- checks.push(await this._check('FRG-002', 'Task Decomposer', () => {
312
- return this._fileExists(path.join(forgePath, 'task-decomposer.js'));
313
- }, 'MEDIUM'));
314
-
315
- // FRG-003: Intent matcher
316
- checks.push(await this._check('FRG-003', 'Intent Matcher', () => {
317
- return this._fileExists(path.join(forgePath, 'intent-matcher.js'));
318
- }, 'MEDIUM'));
319
-
320
- // FRG-004: Diagnostician
321
- checks.push(await this._check('FRG-004', 'Diagnostician', () => {
322
- return this._fileExists(path.join(forgePath, 'diagnostician-v2.js')) ||
323
- this._fileExists(path.join(forgePath, 'diagnostician.js'));
324
- }, 'MEDIUM'));
325
-
326
- // FRG-005: Foreman
327
- checks.push(await this._check('FRG-005', 'Foreman', () => {
328
- return this._fileExists(path.join(this.config.rootPath, 'build-engine', 'orion-core', 'foreman.js'));
329
- }, 'HIGH'));
330
-
331
- return { status: this._systemStatus(checks), checks };
332
- }
333
-
334
- async _checkOCEE() {
335
- const checks = [];
336
- const oceePath = path.join(this.config.serverPath, 'ocee');
337
-
338
- // OCE-001: OCEE core
339
- checks.push(await this._check('OCE-001', 'OCEE Core', () => {
340
- return this._fileExists(path.join(oceePath, 'ocee-core.js'));
341
- }, 'HIGH'));
342
-
343
- // OCE-002: Persistent task queue
344
- checks.push(await this._check('OCE-002', 'Task Queue', () => {
345
- return this._fileExists(path.join(oceePath, 'persistent-task-queue.js'));
346
- }, 'HIGH'));
347
-
348
- // OCE-003: Checkpoint manager
349
- checks.push(await this._check('OCE-003', 'Checkpoint Manager', () => {
350
- return this._fileExists(path.join(oceePath, 'checkpoint-manager.js'));
351
- }, 'HIGH'));
352
-
353
- // OCE-004: Build sessions valid
354
- checks.push(await this._check('OCE-004', 'Build Sessions', () => {
355
- const file = path.join(this.config.serverPath, 'data', 'build-sessions.json');
356
- return !this._fileExists(file) || this._isValidJson(file);
357
- }, 'MEDIUM'));
358
-
359
- // OCE-005: Executor status
360
- checks.push(await this._check('OCE-005', 'Executor Status', () => {
361
- const file = path.join(this.config.serverPath, 'data', 'executor-status.json');
362
- return this._isValidJson(file);
363
- }, 'MEDIUM'));
364
-
365
- return { status: this._systemStatus(checks), checks };
366
- }
367
-
368
- async _checkMemory() {
369
- const checks = [];
370
-
371
- // MEM-001: orion-knowledge.json
372
- checks.push(await this._check('MEM-001', 'Orion Knowledge', () => {
373
- return this._isValidJson(path.join(this.config.rootPath, 'orion-knowledge.json'));
374
- }, 'MEDIUM', 'Reinitialize'));
375
-
376
- // MEM-002: Memory bank files
377
- checks.push(await this._check('MEM-002', 'Memory Bank', () => {
378
- const mbPath = path.join(this.config.rootPath, 'memory-bank');
379
- return this._fileExists(path.join(mbPath, 'progress.md')) &&
380
- this._fileExists(path.join(mbPath, 'activeContext.md'));
381
- }, 'MEDIUM'));
382
-
383
- // MEM-003: Snapshot manifest
384
- checks.push(await this._check('MEM-003', 'Snapshot Manifest', () => {
385
- const file = path.join(this.config.serverPath, '.orion', 'snapshot-manifest.json');
386
- return !this._fileExists(file) || this._isValidJson(file);
387
- }, 'MEDIUM'));
388
-
389
- // MEM-004: Build logs directory
390
- checks.push(await this._check('MEM-004', 'Build Logs', () => {
391
- return this._dirExists(path.join(this.config.serverPath, 'data', 'build-logs'));
392
- }, 'MEDIUM'));
393
-
394
- // MEM-005: Failure memory service
395
- checks.push(await this._check('MEM-005', 'Failure Memory', () => {
396
- return this._fileExists(path.join(this.config.serverPath, 'services', 'failure-memory.js'));
397
- }, 'MEDIUM'));
398
-
399
- return { status: this._systemStatus(checks), checks };
400
- }
401
-
402
- async _checkExternal() {
403
- const checks = [];
404
-
405
- // EXT-001: Zoho tokens (if configured)
406
- checks.push(await this._check('EXT-001', 'Zoho Tokens', () => {
407
- const file = path.join(this.config.serverPath, 'data', 'zoho-tokens.json');
408
- return !this._fileExists(file) || this._isValidJson(file);
409
- }, 'LOW'));
410
-
411
- // EXT-002: Cartography maps
412
- checks.push(await this._check('EXT-002', 'Cartography', () => {
413
- return this._dirExists(path.join(this.config.rootPath, 'cartography'));
414
- }, 'LOW'));
415
-
416
- // EXT-003: Notification config
417
- checks.push(await this._check('EXT-003', 'Notification Config', () => {
418
- const file = path.join(this.config.serverPath, 'data', 'notification-config.json');
419
- return !this._fileExists(file) || this._isValidJson(file);
420
- }, 'LOW'));
421
-
422
- // EXT-004: Health history
423
- checks.push(await this._check('EXT-004', 'Health History', () => {
424
- const file = path.join(this.config.serverPath, 'data', 'health-history.json');
425
- return !this._fileExists(file) || this._isValidJson(file);
426
- }, 'LOW'));
427
-
428
- return { status: this._systemStatus(checks), checks };
429
- }
430
-
431
- // ========== HELPERS ==========
432
-
433
- async _check(id, name, checkFn, criticality = 'MEDIUM', autoHeal = null) {
434
- const result = {
435
- id,
436
- name,
437
- criticality,
438
- status: 'UNKNOWN',
439
- autoHeal,
440
- needsHumanEscalation: false
441
- };
442
-
443
- try {
444
- const passed = await checkFn();
445
- result.status = passed ? 'PASS' : 'FAIL';
446
-
447
- if (!passed && criticality === 'CRITICAL') {
448
- result.needsHumanEscalation = true;
449
- }
450
- } catch (error) {
451
- result.status = 'ERROR';
452
- result.error = error.message;
453
- result.needsHumanEscalation = true;
454
- }
455
-
456
- return result;
457
- }
458
-
459
- _fileExists(filePath) {
460
- try {
461
- return fs.existsSync(filePath);
462
- } catch { return false; }
463
- }
464
-
465
- _dirExists(dirPath) {
466
- try {
467
- return fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory();
468
- } catch { return false; }
469
- }
470
-
471
- _isValidJson(filePath) {
472
- try {
473
- if (!fs.existsSync(filePath)) return false;
474
- JSON.parse(fs.readFileSync(filePath, 'utf8'));
475
- return true;
476
- } catch { return false; }
477
- }
478
-
479
- _systemStatus(checks) {
480
- const failed = checks.filter(c => c.status === 'FAIL' || c.status === 'ERROR');
481
- const critical = failed.filter(c => c.criticality === 'CRITICAL');
482
-
483
- if (critical.length > 0) return 'CRITICAL';
484
- if (failed.length > 0) return 'DEGRADED';
485
- return 'HEALTHY';
486
- }
487
-
488
- _calculateOverallStatus(results) {
489
- if (results.criticalIssues.length > 0) return 'CRITICAL';
490
- if (results.failed > 0) return 'DEGRADED';
491
- if (results.warnings > 0) return 'WARNING';
492
- return 'HEALTHY';
493
- }
494
-
495
- /**
496
- * Get summary for display
497
- */
498
- getSummary() {
499
- if (!this.lastResults) return { status: 'NO_DATA', message: 'No health check run yet' };
500
-
501
- return {
502
- status: this.lastResults.overallStatus,
503
- passed: this.lastResults.passed,
504
- failed: this.lastResults.failed,
505
- total: this.lastResults.totalChecks,
506
- criticalIssues: this.lastResults.criticalIssues.length,
507
- humanActionNeeded: this.lastResults.humanEscalationNeeded.length,
508
- lastCheck: this.lastResults.timestamp
509
- };
510
- }
511
- }
512
-
513
- module.exports = MasterHealthChecker;
@@ -1,64 +0,0 @@
1
- const path = require('path');
2
-
3
- class MonitorNotifier {
4
- constructor({ alertManager, store, repoConfig }) {
5
- this.alertManager = alertManager;
6
- this.store = store;
7
- this.repoConfig = repoConfig;
8
- }
9
-
10
- async notify(diff, run) {
11
- const channels = this.repoConfig.notification?.channels || ['inbox'];
12
- const notifications = [];
13
-
14
- if (!diff.hasNewIssues) {
15
- return notifications;
16
- }
17
-
18
- const summary = `Found ${diff.added.length} new issue${diff.added.length === 1 ? '' : 's'} in ${this.repoConfig.repoPath}`;
19
- const payload = {
20
- timestamp: new Date().toISOString(),
21
- repoId: this.repoConfig.repoId,
22
- repoPath: this.repoConfig.repoPath,
23
- channels,
24
- summary,
25
- newIssues: diff.added.slice(0, 10).map(issue => ({
26
- file: issue.file,
27
- line: issue.line,
28
- severity: issue.severity,
29
- category: issue.category,
30
- message: issue.message,
31
- })),
32
- runTimestamp: run.timestamp,
33
- };
34
-
35
- if (channels.includes('inbox')) {
36
- this.store.appendNotification(payload);
37
- notifications.push({ channel: 'inbox', delivered: true });
38
- }
39
-
40
- if (channels.includes('console') && this.alertManager) {
41
- await this.alertManager.sendAlert({
42
- level: 'warning',
43
- category: 'scheduled-scan',
44
- title: 'Thuban monitor detected new issues',
45
- message: summary,
46
- file: path.join(this.repoConfig.repoPath, '.'),
47
- details: payload,
48
- });
49
- notifications.push({ channel: 'console', delivered: true });
50
- }
51
-
52
- if (channels.includes('webhook')) {
53
- notifications.push({ channel: 'webhook', delivered: false, stub: true });
54
- }
55
-
56
- if (channels.includes('email')) {
57
- notifications.push({ channel: 'email', delivered: false, stub: true });
58
- }
59
-
60
- return notifications;
61
- }
62
- }
63
-
64
- module.exports = MonitorNotifier;
@@ -1,103 +0,0 @@
1
- const path = require('path');
2
-
3
- const AlertManager = require('./alert-manager.js');
4
- const MonitorStore = require('./monitor-store.js');
5
- const MonitorNotifier = require('./monitor-notifier.js');
6
- const { runScanPipeline } = require('./scan-runner.js');
7
- const { diffRuns } = require('./scan-diff.js');
8
- const { collectFiles } = require('./file-collector.js');
9
-
10
- function resolveIntervalMs(frequency, intervalMs) {
11
- if (frequency === 'weekly') return 7 * 24 * 60 * 60 * 1000;
12
- if (frequency === 'custom') return Math.max(60 * 1000, Number(intervalMs) || 24 * 60 * 60 * 1000);
13
- return 24 * 60 * 60 * 1000;
14
- }
15
-
16
- class MonitorService {
17
- constructor(options = {}) {
18
- this.store = options.store || new MonitorStore();
19
- this.timers = new Map();
20
- }
21
-
22
- configureRepo({ repoPath, frequency = 'daily', intervalMs, notification }) {
23
- const repoId = this.store.getRepoId(repoPath);
24
- const effectiveIntervalMs = resolveIntervalMs(frequency, intervalMs);
25
- const repoConfig = this.store.upsertRepo({
26
- repoId,
27
- repoPath,
28
- frequency,
29
- intervalMs: effectiveIntervalMs,
30
- notification,
31
- nextRunAt: new Date(Date.now() + effectiveIntervalMs).toISOString(),
32
- });
33
- return repoConfig;
34
- }
35
-
36
- async runRepoScan(repoConfig) {
37
- const rootPath = path.resolve(repoConfig.repoPath);
38
- const files = this._collectFiles(rootPath);
39
- const startedAt = Date.now();
40
- const pipeline = await runScanPipeline({ rootPath, files });
41
- const run = {
42
- repoId: repoConfig.repoId,
43
- repoPath: rootPath,
44
- timestamp: new Date().toISOString(),
45
- durationMs: Date.now() - startedAt,
46
- frequency: repoConfig.frequency,
47
- metrics: pipeline.metrics,
48
- summary: pipeline.summary,
49
- issues: pipeline.issues,
50
- };
51
-
52
- const previousRun = this.store.getLatestRun(repoConfig.repoId);
53
- const diff = diffRuns(previousRun, run);
54
- run.diff = diff;
55
-
56
- const runPath = this.store.saveRun(repoConfig.repoId, run);
57
- const updatedRepo = this.store.upsertRepo({
58
- ...repoConfig,
59
- lastRunAt: run.timestamp,
60
- nextRunAt: new Date(Date.now() + repoConfig.intervalMs).toISOString(),
61
- });
62
-
63
- const notifier = new MonitorNotifier({
64
- alertManager: new AlertManager({ rootPath, consoleOutput: true, fileOutput: true }),
65
- store: this.store,
66
- repoConfig: updatedRepo,
67
- });
68
- run.notifications = await notifier.notify(diff, run);
69
-
70
- return { run, runPath, previousRun, diff };
71
- }
72
-
73
- start(repoConfig, { once = false } = {}) {
74
- const execute = async () => {
75
- try {
76
- await this.runRepoScan(repoConfig);
77
- } catch (error) {
78
- console.error(`[THUBAN MONITOR] Scheduled scan failed for ${repoConfig.repoPath}: ${error.message}`);
79
- }
80
- };
81
-
82
- execute();
83
- if (once) return;
84
-
85
- const timer = setInterval(execute, repoConfig.intervalMs);
86
- this.timers.set(repoConfig.repoId, timer);
87
- return timer;
88
- }
89
-
90
- stopAll() {
91
- for (const timer of this.timers.values()) clearInterval(timer);
92
- this.timers.clear();
93
- }
94
-
95
- _collectFiles(rootPath) {
96
- return collectFiles(rootPath);
97
- }
98
- }
99
-
100
- module.exports = {
101
- MonitorService,
102
- resolveIntervalMs,
103
- };