thuban 0.3.2 → 0.3.3

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.
@@ -1,608 +1,616 @@
1
- /**
2
- * Sentinel Core
3
- *
4
- * @purpose Event-driven SentinelCore component
5
- * @module sentinel
6
- * @layer application
7
- * @imports-from fs, path, events, sentinel/file-watcher.js, sentinel/code-scanner.js, sentinel/drift-detector.js, sentinel/health-checker.js, sentinel/alert-manager.js
8
- * @exports-to sentinel
9
- * @side-effects Process termination, Console output, Event emission
10
- * @critical-for sentinel
11
- */
12
-
13
- /**
14
- * Sentinel Core - Codebase Monitoring Orchestrator
15
- *
16
- * Continuous monitoring system that catches problems before they ship.
17
- * Coordinates file watching, code scanning, drift detection, and alerting.
18
- *
19
- * Part of Orion OS - will be spun out as Thuban SaaS
20
- *
21
- * Schedule:
22
- * - On commit: Security scan, contract validation, dependency update
23
- * - Every 6 hours: Full AST scan vs widget comparison
24
- * - Nightly: Complete rescan, CVE database check, architectural drift
25
- * - Weekly: Deep quality audit, trend report, technical debt assessment
26
- */
27
-
28
- const fs = require('fs');
29
- const path = require('path');
30
- const EventEmitter = require('events');
31
-
32
- // Sentinel components (will be required when available)
33
- let FileWatcher, CodeScanner, DriftDetector, HealthChecker, AlertManager;
34
-
35
- class SentinelCore extends EventEmitter {
36
- constructor(options = {}) {
37
- super();
38
-
39
- this.config = {
40
- rootPath: options.rootPath || process.cwd(),
41
- watchPatterns: options.watchPatterns || ['**/*.js', '**/*.ts', '**/*.json'],
42
- ignorePatterns: options.ignorePatterns || ['node_modules/**', '.git/**', 'dist/**', '.orion/snapshots/**'],
43
- scanIntervalHours: options.scanIntervalHours || 6,
44
- dailyReportHour: options.dailyReportHour || 8, // 8 AM
45
- weeklyReportDay: options.weeklyReportDay || 1, // Monday
46
- enableRealtime: options.enableRealtime !== false,
47
- enableScheduled: options.enableScheduled !== false,
48
- alertThreshold: options.alertThreshold || 'warning', // 'info', 'warning', 'error', 'critical'
49
- ...options
50
- };
51
-
52
- this.state = {
53
- isRunning: false,
54
- lastFullScan: null,
55
- lastHealthCheck: null,
56
- lastDriftCheck: null,
57
- filesWatched: 0,
58
- issuesFound: [],
59
- metrics: {
60
- totalScans: 0,
61
- issuesDetected: 0,
62
- issuesResolved: 0,
63
- averageScanTime: 0
64
- }
65
- };
66
-
67
- this.components = {
68
- fileWatcher: null,
69
- codeScanner: null,
70
- driftDetector: null,
71
- healthChecker: null,
72
- alertManager: null
73
- };
74
-
75
- this.scheduledJobs = [];
76
- }
77
-
78
- /**
79
- * Initialize all Sentinel components
80
- */
81
- async initialize() {
82
- console.log('[SENTINEL] Initializing Sentinel Core...');
83
-
84
- try {
85
- // Load components
86
- FileWatcher = require('./file-watcher.js');
87
- CodeScanner = require('./code-scanner.js');
88
- DriftDetector = require('./drift-detector.js');
89
- HealthChecker = require('./health-checker.js');
90
- AlertManager = require('./alert-manager.js');
91
-
92
- // Initialize components
93
- this.components.fileWatcher = new FileWatcher(this.config);
94
- this.components.codeScanner = new CodeScanner(this.config);
95
- this.components.driftDetector = new DriftDetector(this.config);
96
- this.components.healthChecker = new HealthChecker(this.config);
97
- this.components.alertManager = new AlertManager(this.config);
98
-
99
- // Wire up event handlers
100
- this._setupEventHandlers();
101
-
102
- console.log('[SENTINEL] All components initialized');
103
- return { success: true };
104
- } catch (error) {
105
- console.error('[SENTINEL] Initialization failed:', error.message);
106
- return { success: false, error: error.message };
107
- }
108
- }
109
-
110
- /**
111
- * Start Sentinel monitoring
112
- */
113
- async start() {
114
- if (this.state.isRunning) {
115
- console.log('[SENTINEL] Already running');
116
- return { success: true, message: 'Already running' };
117
- }
118
-
119
- console.log('[SENTINEL] Starting Sentinel monitoring...');
120
-
121
- try {
122
- // Start file watcher for real-time monitoring
123
- if (this.config.enableRealtime && this.components.fileWatcher) {
124
- await this.components.fileWatcher.start();
125
- this.state.filesWatched = this.components.fileWatcher.getWatchedCount();
126
- }
127
-
128
- // Set up scheduled jobs
129
- if (this.config.enableScheduled) {
130
- this._setupScheduledJobs();
131
- }
132
-
133
- // Run initial health check
134
- await this.runHealthCheck();
135
-
136
- // Run initial scan
137
- const initialScan = await this.runFullScan();
138
-
139
- this.state.isRunning = true;
140
- this.emit('started', { timestamp: new Date().toISOString() });
141
-
142
- console.log('[SENTINEL] Monitoring active');
143
- console.log(`[SENTINEL] Watching ${this.state.filesWatched} files`);
144
- console.log(`[SENTINEL] Initial scan found ${initialScan.issues?.length || 0} issues`);
145
-
146
- return {
147
- success: true,
148
- filesWatched: this.state.filesWatched,
149
- initialIssues: initialScan.issues?.length || 0
150
- };
151
- } catch (error) {
152
- console.error('[SENTINEL] Start failed:', error.message);
153
- return { success: false, error: error.message };
154
- }
155
- }
156
-
157
- /**
158
- * Stop Sentinel monitoring
159
- */
160
- async stop() {
161
- if (!this.state.isRunning) {
162
- return { success: true, message: 'Not running' };
163
- }
164
-
165
- console.log('[SENTINEL] Stopping Sentinel monitoring...');
166
-
167
- // Stop file watcher
168
- if (this.components.fileWatcher) {
169
- await this.components.fileWatcher.stop();
170
- }
171
-
172
- // Clear scheduled jobs
173
- this.scheduledJobs.forEach(job => clearInterval(job));
174
- this.scheduledJobs = [];
175
-
176
- this.state.isRunning = false;
177
- this.emit('stopped', { timestamp: new Date().toISOString() });
178
-
179
- console.log('[SENTINEL] Monitoring stopped');
180
- return { success: true };
181
- }
182
-
183
- /**
184
- * Run a full codebase scan
185
- */
186
- async runFullScan(options = {}) {
187
- const startTime = Date.now();
188
- console.log('[SENTINEL] Starting full codebase scan...');
189
-
190
- const scanResult = {
191
- timestamp: new Date().toISOString(),
192
- duration: 0,
193
- filesScanned: 0,
194
- issues: [],
195
- summary: {}
196
- };
197
-
198
- try {
199
- if (!this.components.codeScanner) {
200
- throw new Error('Code scanner not initialized');
201
- }
202
-
203
- // Run code scanner
204
- const codeResults = await this.components.codeScanner.scanAll();
205
- scanResult.filesScanned = codeResults.filesScanned || 0;
206
- scanResult.issues.push(...(codeResults.issues || []));
207
-
208
- // Run drift detection
209
- if (this.components.driftDetector) {
210
- const driftResults = await this.components.driftDetector.detectAll();
211
- scanResult.issues.push(...(driftResults.issues || []));
212
- this.state.lastDriftCheck = new Date().toISOString();
213
- }
214
-
215
- // Calculate duration and update metrics
216
- scanResult.duration = Date.now() - startTime;
217
- this.state.lastFullScan = scanResult.timestamp;
218
- this.state.metrics.totalScans++;
219
- this.state.metrics.issuesDetected += scanResult.issues.length;
220
-
221
- // Update running average
222
- this.state.metrics.averageScanTime =
223
- (this.state.metrics.averageScanTime * (this.state.metrics.totalScans - 1) + scanResult.duration)
224
- / this.state.metrics.totalScans;
225
-
226
- // Generate summary
227
- scanResult.summary = this._generateScanSummary(scanResult.issues);
228
-
229
- // Store issues
230
- this.state.issuesFound = scanResult.issues;
231
-
232
- // Send alerts for critical issues
233
- await this._processIssues(scanResult.issues);
234
-
235
- console.log(`[SENTINEL] Scan complete: ${scanResult.filesScanned} files, ${scanResult.issues.length} issues, ${scanResult.duration}ms`);
236
- this.emit('scanComplete', scanResult);
237
-
238
- return scanResult;
239
- } catch (error) {
240
- console.error('[SENTINEL] Full scan failed:', error.message);
241
- scanResult.error = error.message;
242
- return scanResult;
243
- }
244
- }
245
-
246
- /**
247
- * Run a quick scan on specific files
248
- */
249
- async runQuickScan(filePaths) {
250
- if (!this.components.codeScanner) {
251
- return { success: false, error: 'Code scanner not initialized' };
252
- }
253
-
254
- console.log(`[SENTINEL] Quick scan on ${filePaths.length} files...`);
255
- const results = await this.components.codeScanner.scanFiles(filePaths);
256
-
257
- // Process any issues found
258
- if (results.issues && results.issues.length > 0) {
259
- await this._processIssues(results.issues);
260
- }
261
-
262
- return results;
263
- }
264
-
265
- /**
266
- * Run health check on all Orion systems
267
- */
268
- async runHealthCheck() {
269
- console.log('[SENTINEL] Running health check...');
270
-
271
- if (!this.components.healthChecker) {
272
- return { success: false, error: 'Health checker not initialized' };
273
- }
274
-
275
- const healthResult = await this.components.healthChecker.checkAll();
276
- this.state.lastHealthCheck = new Date().toISOString();
277
-
278
- // Alert on unhealthy systems
279
- if (healthResult.unhealthySystems && healthResult.unhealthySystems.length > 0) {
280
- await this.components.alertManager?.sendAlert({
281
- level: 'warning',
282
- title: 'System Health Issues Detected',
283
- message: `${healthResult.unhealthySystems.length} systems are unhealthy`,
284
- details: healthResult.unhealthySystems
285
- });
286
- }
287
-
288
- this.emit('healthCheck', healthResult);
289
- return healthResult;
290
- }
291
-
292
- /**
293
- * Generate daily health digest
294
- */
295
- async generateDailyDigest() {
296
- console.log('[SENTINEL] Generating daily digest...');
297
-
298
- const digest = {
299
- date: new Date().toISOString().split('T')[0],
300
- timestamp: new Date().toISOString(),
301
- summary: {
302
- issuesFound: this.state.issuesFound.length,
303
- criticalIssues: this.state.issuesFound.filter(i => i.severity === 'critical').length,
304
- warningIssues: this.state.issuesFound.filter(i => i.severity === 'warning').length,
305
- filesWatched: this.state.filesWatched,
306
- lastScan: this.state.lastFullScan,
307
- lastHealthCheck: this.state.lastHealthCheck
308
- },
309
- topIssues: this.state.issuesFound
310
- .sort((a, b) => this._severityScore(b.severity) - this._severityScore(a.severity))
311
- .slice(0, 10),
312
- metrics: this.state.metrics,
313
- recommendations: this._generateRecommendations()
314
- };
315
-
316
- // Send digest via alert manager
317
- if (this.components.alertManager) {
318
- await this.components.alertManager.sendDigest(digest);
319
- }
320
-
321
- this.emit('dailyDigest', digest);
322
- return digest;
323
- }
324
-
325
- /**
326
- * Generate weekly report
327
- */
328
- async generateWeeklyReport() {
329
- console.log('[SENTINEL] Generating weekly report...');
330
-
331
- const report = {
332
- weekOf: new Date().toISOString().split('T')[0],
333
- timestamp: new Date().toISOString(),
334
- summary: this.state.metrics,
335
- issuesTrend: this._calculateTrend(),
336
- codeQuality: await this._assessCodeQuality(),
337
- technicalDebt: await this._assessTechnicalDebt(),
338
- recommendations: this._generateRecommendations(),
339
- fullIssueList: this.state.issuesFound
340
- };
341
-
342
- // Send report via alert manager
343
- if (this.components.alertManager) {
344
- await this.components.alertManager.sendWeeklyReport(report);
345
- }
346
-
347
- this.emit('weeklyReport', report);
348
- return report;
349
- }
350
-
351
- /**
352
- * Get current Sentinel status
353
- */
354
- getStatus() {
355
- return {
356
- isRunning: this.state.isRunning,
357
- config: this.config,
358
- state: {
359
- ...this.state,
360
- componentsLoaded: {
361
- fileWatcher: !!this.components.fileWatcher,
362
- codeScanner: !!this.components.codeScanner,
363
- driftDetector: !!this.components.driftDetector,
364
- healthChecker: !!this.components.healthChecker,
365
- alertManager: !!this.components.alertManager
366
- }
367
- },
368
- uptime: this.state.isRunning ? Date.now() - new Date(this.state.lastFullScan).getTime() : 0
369
- };
370
- }
371
-
372
- /**
373
- * Get all current issues
374
- */
375
- getIssues(filter = {}) {
376
- let issues = [...this.state.issuesFound];
377
-
378
- if (filter.severity) {
379
- issues = issues.filter(i => i.severity === filter.severity);
380
- }
381
- if (filter.category) {
382
- issues = issues.filter(i => i.category === filter.category);
383
- }
384
- if (filter.file) {
385
- issues = issues.filter(i => i.file && i.file.includes(filter.file));
386
- }
387
-
388
- return issues;
389
- }
390
-
391
- // Private methods
392
-
393
- _setupEventHandlers() {
394
- // File watcher events
395
- if (this.components.fileWatcher) {
396
- this.components.fileWatcher.on('change', async (event) => {
397
- console.log(`[SENTINEL] File changed: ${event.filePath}`);
398
- await this.runQuickScan([event.filePath]);
399
- });
400
-
401
- this.components.fileWatcher.on('error', (error) => {
402
- console.error('[SENTINEL] File watcher error:', error);
403
- });
404
- }
405
-
406
- // Code scanner events
407
- if (this.components.codeScanner) {
408
- this.components.codeScanner.on('issueFound', async (issue) => {
409
- await this._processIssues([issue]);
410
- });
411
- }
412
- }
413
-
414
- _setupScheduledJobs() {
415
- // Full scan every N hours
416
- const scanInterval = this.config.scanIntervalHours * 60 * 60 * 1000;
417
- this.scheduledJobs.push(
418
- setInterval(() => this.runFullScan(), scanInterval)
419
- );
420
-
421
- // Health check every hour
422
- this.scheduledJobs.push(
423
- setInterval(() => this.runHealthCheck(), 60 * 60 * 1000)
424
- );
425
-
426
- // Daily digest (check every hour if it's time)
427
- this.scheduledJobs.push(
428
- setInterval(() => {
429
- const now = new Date();
430
- if (now.getHours() === this.config.dailyReportHour && now.getMinutes() < 5) {
431
- this.generateDailyDigest();
432
- }
433
- }, 60 * 60 * 1000)
434
- );
435
-
436
- // Weekly report (check daily if it's the right day)
437
- this.scheduledJobs.push(
438
- setInterval(() => {
439
- const now = new Date();
440
- if (now.getDay() === this.config.weeklyReportDay &&
441
- now.getHours() === this.config.dailyReportHour &&
442
- now.getMinutes() < 5) {
443
- this.generateWeeklyReport();
444
- }
445
- }, 60 * 60 * 1000)
446
- );
447
-
448
- console.log(`[SENTINEL] Scheduled jobs: scan every ${this.config.scanIntervalHours}h, daily digest at ${this.config.dailyReportHour}:00`);
449
- }
450
-
451
- async _processIssues(issues) {
452
- for (const issue of issues) {
453
- // Check if issue meets alert threshold
454
- if (this._meetsThreshold(issue.severity)) {
455
- if (this.components.alertManager) {
456
- await this.components.alertManager.sendAlert({
457
- level: issue.severity,
458
- title: `[${issue.category}] ${issue.title || issue.message}`,
459
- message: issue.message,
460
- file: issue.file,
461
- line: issue.line,
462
- details: issue
463
- });
464
- }
465
- }
466
- }
467
- }
468
-
469
- _meetsThreshold(severity) {
470
- const levels = ['info', 'warning', 'error', 'critical'];
471
- const severityIndex = levels.indexOf(severity);
472
- const thresholdIndex = levels.indexOf(this.config.alertThreshold);
473
- return severityIndex >= thresholdIndex;
474
- }
475
-
476
- _severityScore(severity) {
477
- const scores = { info: 1, warning: 2, error: 3, critical: 4 };
478
- return scores[severity] || 0;
479
- }
480
-
481
- _generateScanSummary(issues) {
482
- return {
483
- total: issues.length,
484
- bySeverity: {
485
- critical: issues.filter(i => i.severity === 'critical').length,
486
- error: issues.filter(i => i.severity === 'error').length,
487
- warning: issues.filter(i => i.severity === 'warning').length,
488
- info: issues.filter(i => i.severity === 'info').length
489
- },
490
- byCategory: issues.reduce((acc, i) => {
491
- acc[i.category] = (acc[i.category] || 0) + 1;
492
- return acc;
493
- }, {})
494
- };
495
- }
496
-
497
- _generateRecommendations() {
498
- const recommendations = [];
499
- const issues = this.state.issuesFound;
500
-
501
- // Check for security issues
502
- const securityIssues = issues.filter(i => i.category === 'security');
503
- if (securityIssues.length > 0) {
504
- recommendations.push({
505
- priority: 'high',
506
- category: 'security',
507
- message: `Address ${securityIssues.length} security issues before deploying`,
508
- issues: securityIssues.slice(0, 5)
509
- });
510
- }
511
-
512
- // Check for quality issues
513
- const qualityIssues = issues.filter(i => i.category === 'quality');
514
- if (qualityIssues.length > 10) {
515
- recommendations.push({
516
- priority: 'medium',
517
- category: 'quality',
518
- message: `Consider refactoring - ${qualityIssues.length} quality issues detected`,
519
- issues: qualityIssues.slice(0, 5)
520
- });
521
- }
522
-
523
- // Check for drift
524
- const driftIssues = issues.filter(i => i.category === 'drift');
525
- if (driftIssues.length > 0) {
526
- recommendations.push({
527
- priority: 'medium',
528
- category: 'drift',
529
- message: `${driftIssues.length} files have drifted from their declared contracts`,
530
- issues: driftIssues.slice(0, 5)
531
- });
532
- }
533
-
534
- return recommendations;
535
- }
536
-
537
- _calculateTrend() {
538
- // Placeholder - would track historical data
539
- return {
540
- direction: 'stable',
541
- change: 0,
542
- note: 'Trend analysis requires historical data'
543
- };
544
- }
545
-
546
- async _assessCodeQuality() {
547
- // Placeholder - would calculate code quality score
548
- return {
549
- score: 75,
550
- grade: 'B',
551
- factors: {
552
- complexity: 70,
553
- maintainability: 80,
554
- documentation: 65,
555
- testCoverage: 40
556
- }
557
- };
558
- }
559
-
560
- async _assessTechnicalDebt() {
561
- // Placeholder - would calculate technical debt
562
- return {
563
- estimatedHours: 40,
564
- categories: {
565
- refactoring: 20,
566
- documentation: 10,
567
- testing: 10
568
- },
569
- priorityItems: []
570
- };
571
- }
572
- }
573
-
574
- // Factory function for creating Sentinel instance
575
- function createSentinel(options = {}) {
576
- return new SentinelCore(options);
577
- }
578
-
579
- // Main execution if run directly
580
- async function main() {
581
- const sentinel = createSentinel({
582
- rootPath: path.resolve(__dirname, '..'),
583
- enableRealtime: true,
584
- enableScheduled: false // Disable scheduled for testing
585
- });
586
-
587
- const initResult = await sentinel.initialize();
588
- if (!initResult.success) {
589
- console.error('Failed to initialize Sentinel:', initResult.error);
590
- process.exit(1);
591
- }
592
-
593
- const startResult = await sentinel.start();
594
- console.log('Sentinel status:', sentinel.getStatus());
595
-
596
- // Keep running
597
- process.on('SIGINT', async () => {
598
- console.log('\nShutting down Sentinel...');
599
- await sentinel.stop();
600
- process.exit(0);
601
- });
602
- }
603
-
604
- if (require.main === module) {
605
- main().catch(console.error);
606
- }
607
-
608
- module.exports = { SentinelCore, createSentinel };
1
+ /**
2
+ * Sentinel Core
3
+ *
4
+ * @purpose Event-driven SentinelCore component
5
+ * @module sentinel
6
+ * @layer application
7
+ * @imports-from fs, path, events, sentinel/file-watcher.js, sentinel/code-scanner.js, sentinel/drift-detector.js, sentinel/health-checker.js, sentinel/alert-manager.js
8
+ * @exports-to sentinel
9
+ * @side-effects Process termination, Console output, Event emission
10
+ * @critical-for sentinel
11
+ */
12
+
13
+ /**
14
+ * Sentinel Core - Codebase Monitoring Orchestrator
15
+ *
16
+ * Continuous monitoring system that catches problems before they ship.
17
+ * Coordinates file watching, code scanning, drift detection, and alerting.
18
+ *
19
+ * Part of Orion OS - will be spun out as Thuban SaaS
20
+ *
21
+ * Schedule:
22
+ * - On commit: Security scan, contract validation, dependency update
23
+ * - Every 6 hours: Full AST scan vs widget comparison
24
+ * - Nightly: Complete rescan, CVE database check, architectural drift
25
+ * - Weekly: Deep quality audit, trend report, technical debt assessment
26
+ */
27
+
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+ const EventEmitter = require('events');
31
+
32
+ // Sentinel components (will be required when available)
33
+ let FileWatcher, CodeScanner, DriftDetector, HealthChecker, AlertManager, SecretScanner;
34
+
35
+ class SentinelCore extends EventEmitter {
36
+ constructor(options = {}) {
37
+ super();
38
+
39
+ this.config = {
40
+ rootPath: options.rootPath || process.cwd(),
41
+ watchPatterns: options.watchPatterns || ['**/*.js', '**/*.ts', '**/*.json'],
42
+ ignorePatterns: options.ignorePatterns || ['node_modules/**', '.git/**', 'dist/**', '.orion/snapshots/**'],
43
+ scanIntervalHours: options.scanIntervalHours || 6,
44
+ dailyReportHour: options.dailyReportHour || 8, // 8 AM
45
+ weeklyReportDay: options.weeklyReportDay || 1, // Monday
46
+ enableRealtime: options.enableRealtime !== false,
47
+ enableScheduled: options.enableScheduled !== false,
48
+ alertThreshold: options.alertThreshold || 'warning', // 'info', 'warning', 'error', 'critical'
49
+ ...options
50
+ };
51
+
52
+ this.state = {
53
+ isRunning: false,
54
+ lastFullScan: null,
55
+ lastHealthCheck: null,
56
+ lastDriftCheck: null,
57
+ filesWatched: 0,
58
+ issuesFound: [],
59
+ metrics: {
60
+ totalScans: 0,
61
+ issuesDetected: 0,
62
+ issuesResolved: 0,
63
+ averageScanTime: 0
64
+ }
65
+ };
66
+
67
+ this.components = {
68
+ fileWatcher: null,
69
+ codeScanner: null,
70
+ driftDetector: null,
71
+ healthChecker: null,
72
+ alertManager: null
73
+ };
74
+
75
+ this.scheduledJobs = [];
76
+ }
77
+
78
+ /**
79
+ * Initialize all Sentinel components
80
+ */
81
+ async initialize() {
82
+ console.log('[SENTINEL] Initializing Sentinel Core...');
83
+
84
+ try {
85
+ // Load components
86
+ FileWatcher = require('./file-watcher.js');
87
+ CodeScanner = require('./code-scanner.js');
88
+ DriftDetector = require('./drift-detector.js');
89
+ HealthChecker = require('./health-checker.js');
90
+ AlertManager = require('./alert-manager.js');
91
+ SecretScanner = require('./secret-scanner.js');
92
+
93
+ // Initialize components
94
+ this.components.fileWatcher = new FileWatcher(this.config);
95
+ this.components.codeScanner = new CodeScanner(this.config);
96
+ this.components.driftDetector = new DriftDetector(this.config);
97
+ this.components.healthChecker = new HealthChecker(this.config);
98
+ this.components.alertManager = new AlertManager(this.config);
99
+ this.components.secretScanner = new SecretScanner(this.config);
100
+
101
+ // Wire up event handlers
102
+ this._setupEventHandlers();
103
+
104
+ console.log('[SENTINEL] All components initialized');
105
+ return { success: true };
106
+ } catch (error) {
107
+ console.error('[SENTINEL] Initialization failed:', error.message);
108
+ return { success: false, error: error.message };
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Start Sentinel monitoring
114
+ */
115
+ async start() {
116
+ if (this.state.isRunning) {
117
+ console.log('[SENTINEL] Already running');
118
+ return { success: true, message: 'Already running' };
119
+ }
120
+
121
+ console.log('[SENTINEL] Starting Sentinel monitoring...');
122
+
123
+ try {
124
+ // Start file watcher for real-time monitoring
125
+ if (this.config.enableRealtime && this.components.fileWatcher) {
126
+ await this.components.fileWatcher.start();
127
+ this.state.filesWatched = this.components.fileWatcher.getWatchedCount();
128
+ }
129
+
130
+ // Set up scheduled jobs
131
+ if (this.config.enableScheduled) {
132
+ this._setupScheduledJobs();
133
+ }
134
+
135
+ // Run initial health check
136
+ await this.runHealthCheck();
137
+
138
+ // Run initial scan
139
+ const initialScan = await this.runFullScan();
140
+
141
+ this.state.isRunning = true;
142
+ this.emit('started', { timestamp: new Date().toISOString() });
143
+
144
+ console.log('[SENTINEL] Monitoring active');
145
+ console.log(`[SENTINEL] Watching ${this.state.filesWatched} files`);
146
+ console.log(`[SENTINEL] Initial scan found ${initialScan.issues?.length || 0} issues`);
147
+
148
+ return {
149
+ success: true,
150
+ filesWatched: this.state.filesWatched,
151
+ initialIssues: initialScan.issues?.length || 0
152
+ };
153
+ } catch (error) {
154
+ console.error('[SENTINEL] Start failed:', error.message);
155
+ return { success: false, error: error.message };
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Stop Sentinel monitoring
161
+ */
162
+ async stop() {
163
+ if (!this.state.isRunning) {
164
+ return { success: true, message: 'Not running' };
165
+ }
166
+
167
+ console.log('[SENTINEL] Stopping Sentinel monitoring...');
168
+
169
+ // Stop file watcher
170
+ if (this.components.fileWatcher) {
171
+ await this.components.fileWatcher.stop();
172
+ }
173
+
174
+ // Clear scheduled jobs
175
+ this.scheduledJobs.forEach(job => clearInterval(job));
176
+ this.scheduledJobs = [];
177
+
178
+ this.state.isRunning = false;
179
+ this.emit('stopped', { timestamp: new Date().toISOString() });
180
+
181
+ console.log('[SENTINEL] Monitoring stopped');
182
+ return { success: true };
183
+ }
184
+
185
+ /**
186
+ * Run a full codebase scan
187
+ */
188
+ async runFullScan(options = {}) {
189
+ const startTime = Date.now();
190
+ console.log('[SENTINEL] Starting full codebase scan...');
191
+
192
+ const scanResult = {
193
+ timestamp: new Date().toISOString(),
194
+ duration: 0,
195
+ filesScanned: 0,
196
+ issues: [],
197
+ summary: {}
198
+ };
199
+
200
+ try {
201
+ if (!this.components.codeScanner) {
202
+ throw new Error('Code scanner not initialized');
203
+ }
204
+
205
+ // Run code scanner
206
+ const codeResults = await this.components.codeScanner.scanAll();
207
+ scanResult.filesScanned = codeResults.filesScanned || 0;
208
+ scanResult.issues.push(...(codeResults.issues || []));
209
+
210
+ if (this.components.secretScanner) {
211
+ const secretResults = await this.components.secretScanner.scanAll();
212
+ scanResult.filesScanned += secretResults.filesScanned || 0;
213
+ scanResult.issues.push(...(secretResults.issues || []));
214
+ }
215
+
216
+ // Run drift detection
217
+ if (this.components.driftDetector) {
218
+ const driftResults = await this.components.driftDetector.detectAll();
219
+ scanResult.issues.push(...(driftResults.issues || []));
220
+ this.state.lastDriftCheck = new Date().toISOString();
221
+ }
222
+
223
+ // Calculate duration and update metrics
224
+ scanResult.duration = Date.now() - startTime;
225
+ this.state.lastFullScan = scanResult.timestamp;
226
+ this.state.metrics.totalScans++;
227
+ this.state.metrics.issuesDetected += scanResult.issues.length;
228
+
229
+ // Update running average
230
+ this.state.metrics.averageScanTime =
231
+ (this.state.metrics.averageScanTime * (this.state.metrics.totalScans - 1) + scanResult.duration)
232
+ / this.state.metrics.totalScans;
233
+
234
+ // Generate summary
235
+ scanResult.summary = this._generateScanSummary(scanResult.issues);
236
+
237
+ // Store issues
238
+ this.state.issuesFound = scanResult.issues;
239
+
240
+ // Send alerts for critical issues
241
+ await this._processIssues(scanResult.issues);
242
+
243
+ console.log(`[SENTINEL] Scan complete: ${scanResult.filesScanned} files, ${scanResult.issues.length} issues, ${scanResult.duration}ms`);
244
+ this.emit('scanComplete', scanResult);
245
+
246
+ return scanResult;
247
+ } catch (error) {
248
+ console.error('[SENTINEL] Full scan failed:', error.message);
249
+ scanResult.error = error.message;
250
+ return scanResult;
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Run a quick scan on specific files
256
+ */
257
+ async runQuickScan(filePaths) {
258
+ if (!this.components.codeScanner) {
259
+ return { success: false, error: 'Code scanner not initialized' };
260
+ }
261
+
262
+ console.log(`[SENTINEL] Quick scan on ${filePaths.length} files...`);
263
+ const results = await this.components.codeScanner.scanFiles(filePaths);
264
+
265
+ // Process any issues found
266
+ if (results.issues && results.issues.length > 0) {
267
+ await this._processIssues(results.issues);
268
+ }
269
+
270
+ return results;
271
+ }
272
+
273
+ /**
274
+ * Run health check on all Orion systems
275
+ */
276
+ async runHealthCheck() {
277
+ console.log('[SENTINEL] Running health check...');
278
+
279
+ if (!this.components.healthChecker) {
280
+ return { success: false, error: 'Health checker not initialized' };
281
+ }
282
+
283
+ const healthResult = await this.components.healthChecker.checkAll();
284
+ this.state.lastHealthCheck = new Date().toISOString();
285
+
286
+ // Alert on unhealthy systems
287
+ if (healthResult.unhealthySystems && healthResult.unhealthySystems.length > 0) {
288
+ await this.components.alertManager?.sendAlert({
289
+ level: 'warning',
290
+ title: 'System Health Issues Detected',
291
+ message: `${healthResult.unhealthySystems.length} systems are unhealthy`,
292
+ details: healthResult.unhealthySystems
293
+ });
294
+ }
295
+
296
+ this.emit('healthCheck', healthResult);
297
+ return healthResult;
298
+ }
299
+
300
+ /**
301
+ * Generate daily health digest
302
+ */
303
+ async generateDailyDigest() {
304
+ console.log('[SENTINEL] Generating daily digest...');
305
+
306
+ const digest = {
307
+ date: new Date().toISOString().split('T')[0],
308
+ timestamp: new Date().toISOString(),
309
+ summary: {
310
+ issuesFound: this.state.issuesFound.length,
311
+ criticalIssues: this.state.issuesFound.filter(i => i.severity === 'critical').length,
312
+ warningIssues: this.state.issuesFound.filter(i => i.severity === 'warning').length,
313
+ filesWatched: this.state.filesWatched,
314
+ lastScan: this.state.lastFullScan,
315
+ lastHealthCheck: this.state.lastHealthCheck
316
+ },
317
+ topIssues: this.state.issuesFound
318
+ .sort((a, b) => this._severityScore(b.severity) - this._severityScore(a.severity))
319
+ .slice(0, 10),
320
+ metrics: this.state.metrics,
321
+ recommendations: this._generateRecommendations()
322
+ };
323
+
324
+ // Send digest via alert manager
325
+ if (this.components.alertManager) {
326
+ await this.components.alertManager.sendDigest(digest);
327
+ }
328
+
329
+ this.emit('dailyDigest', digest);
330
+ return digest;
331
+ }
332
+
333
+ /**
334
+ * Generate weekly report
335
+ */
336
+ async generateWeeklyReport() {
337
+ console.log('[SENTINEL] Generating weekly report...');
338
+
339
+ const report = {
340
+ weekOf: new Date().toISOString().split('T')[0],
341
+ timestamp: new Date().toISOString(),
342
+ summary: this.state.metrics,
343
+ issuesTrend: this._calculateTrend(),
344
+ codeQuality: await this._assessCodeQuality(),
345
+ technicalDebt: await this._assessTechnicalDebt(),
346
+ recommendations: this._generateRecommendations(),
347
+ fullIssueList: this.state.issuesFound
348
+ };
349
+
350
+ // Send report via alert manager
351
+ if (this.components.alertManager) {
352
+ await this.components.alertManager.sendWeeklyReport(report);
353
+ }
354
+
355
+ this.emit('weeklyReport', report);
356
+ return report;
357
+ }
358
+
359
+ /**
360
+ * Get current Sentinel status
361
+ */
362
+ getStatus() {
363
+ return {
364
+ isRunning: this.state.isRunning,
365
+ config: this.config,
366
+ state: {
367
+ ...this.state,
368
+ componentsLoaded: {
369
+ fileWatcher: !!this.components.fileWatcher,
370
+ codeScanner: !!this.components.codeScanner,
371
+ driftDetector: !!this.components.driftDetector,
372
+ healthChecker: !!this.components.healthChecker,
373
+ alertManager: !!this.components.alertManager
374
+ }
375
+ },
376
+ uptime: this.state.isRunning ? Date.now() - new Date(this.state.lastFullScan).getTime() : 0
377
+ };
378
+ }
379
+
380
+ /**
381
+ * Get all current issues
382
+ */
383
+ getIssues(filter = {}) {
384
+ let issues = [...this.state.issuesFound];
385
+
386
+ if (filter.severity) {
387
+ issues = issues.filter(i => i.severity === filter.severity);
388
+ }
389
+ if (filter.category) {
390
+ issues = issues.filter(i => i.category === filter.category);
391
+ }
392
+ if (filter.file) {
393
+ issues = issues.filter(i => i.file && i.file.includes(filter.file));
394
+ }
395
+
396
+ return issues;
397
+ }
398
+
399
+ // Private methods
400
+
401
+ _setupEventHandlers() {
402
+ // File watcher events
403
+ if (this.components.fileWatcher) {
404
+ this.components.fileWatcher.on('change', async (event) => {
405
+ console.log(`[SENTINEL] File changed: ${event.filePath}`);
406
+ await this.runQuickScan([event.filePath]);
407
+ });
408
+
409
+ this.components.fileWatcher.on('error', (error) => {
410
+ console.error('[SENTINEL] File watcher error:', error);
411
+ });
412
+ }
413
+
414
+ // Code scanner events
415
+ if (this.components.codeScanner) {
416
+ this.components.codeScanner.on('issueFound', async (issue) => {
417
+ await this._processIssues([issue]);
418
+ });
419
+ }
420
+ }
421
+
422
+ _setupScheduledJobs() {
423
+ // Full scan every N hours
424
+ const scanInterval = this.config.scanIntervalHours * 60 * 60 * 1000;
425
+ this.scheduledJobs.push(
426
+ setInterval(() => this.runFullScan(), scanInterval)
427
+ );
428
+
429
+ // Health check every hour
430
+ this.scheduledJobs.push(
431
+ setInterval(() => this.runHealthCheck(), 60 * 60 * 1000)
432
+ );
433
+
434
+ // Daily digest (check every hour if it's time)
435
+ this.scheduledJobs.push(
436
+ setInterval(() => {
437
+ const now = new Date();
438
+ if (now.getHours() === this.config.dailyReportHour && now.getMinutes() < 5) {
439
+ this.generateDailyDigest();
440
+ }
441
+ }, 60 * 60 * 1000)
442
+ );
443
+
444
+ // Weekly report (check daily if it's the right day)
445
+ this.scheduledJobs.push(
446
+ setInterval(() => {
447
+ const now = new Date();
448
+ if (now.getDay() === this.config.weeklyReportDay &&
449
+ now.getHours() === this.config.dailyReportHour &&
450
+ now.getMinutes() < 5) {
451
+ this.generateWeeklyReport();
452
+ }
453
+ }, 60 * 60 * 1000)
454
+ );
455
+
456
+ console.log(`[SENTINEL] Scheduled jobs: scan every ${this.config.scanIntervalHours}h, daily digest at ${this.config.dailyReportHour}:00`);
457
+ }
458
+
459
+ async _processIssues(issues) {
460
+ for (const issue of issues) {
461
+ // Check if issue meets alert threshold
462
+ if (this._meetsThreshold(issue.severity)) {
463
+ if (this.components.alertManager) {
464
+ await this.components.alertManager.sendAlert({
465
+ level: issue.severity,
466
+ title: `[${issue.category}] ${issue.title || issue.message}`,
467
+ message: issue.message,
468
+ file: issue.file,
469
+ line: issue.line,
470
+ details: issue
471
+ });
472
+ }
473
+ }
474
+ }
475
+ }
476
+
477
+ _meetsThreshold(severity) {
478
+ const levels = ['info', 'warning', 'error', 'critical'];
479
+ const severityIndex = levels.indexOf(severity);
480
+ const thresholdIndex = levels.indexOf(this.config.alertThreshold);
481
+ return severityIndex >= thresholdIndex;
482
+ }
483
+
484
+ _severityScore(severity) {
485
+ const scores = { info: 1, warning: 2, error: 3, critical: 4 };
486
+ return scores[severity] || 0;
487
+ }
488
+
489
+ _generateScanSummary(issues) {
490
+ return {
491
+ total: issues.length,
492
+ bySeverity: {
493
+ critical: issues.filter(i => i.severity === 'critical').length,
494
+ error: issues.filter(i => i.severity === 'error').length,
495
+ warning: issues.filter(i => i.severity === 'warning').length,
496
+ info: issues.filter(i => i.severity === 'info').length
497
+ },
498
+ byCategory: issues.reduce((acc, i) => {
499
+ acc[i.category] = (acc[i.category] || 0) + 1;
500
+ return acc;
501
+ }, {})
502
+ };
503
+ }
504
+
505
+ _generateRecommendations() {
506
+ const recommendations = [];
507
+ const issues = this.state.issuesFound;
508
+
509
+ // Check for security issues
510
+ const securityIssues = issues.filter(i => i.category === 'security');
511
+ if (securityIssues.length > 0) {
512
+ recommendations.push({
513
+ priority: 'high',
514
+ category: 'security',
515
+ message: `Address ${securityIssues.length} security issues before deploying`,
516
+ issues: securityIssues.slice(0, 5)
517
+ });
518
+ }
519
+
520
+ // Check for quality issues
521
+ const qualityIssues = issues.filter(i => i.category === 'quality');
522
+ if (qualityIssues.length > 10) {
523
+ recommendations.push({
524
+ priority: 'medium',
525
+ category: 'quality',
526
+ message: `Consider refactoring - ${qualityIssues.length} quality issues detected`,
527
+ issues: qualityIssues.slice(0, 5)
528
+ });
529
+ }
530
+
531
+ // Check for drift
532
+ const driftIssues = issues.filter(i => i.category === 'drift');
533
+ if (driftIssues.length > 0) {
534
+ recommendations.push({
535
+ priority: 'medium',
536
+ category: 'drift',
537
+ message: `${driftIssues.length} files have drifted from their declared contracts`,
538
+ issues: driftIssues.slice(0, 5)
539
+ });
540
+ }
541
+
542
+ return recommendations;
543
+ }
544
+
545
+ _calculateTrend() {
546
+ // Placeholder - would track historical data
547
+ return {
548
+ direction: 'stable',
549
+ change: 0,
550
+ note: 'Trend analysis requires historical data'
551
+ };
552
+ }
553
+
554
+ async _assessCodeQuality() {
555
+ // Placeholder - would calculate code quality score
556
+ return {
557
+ score: 75,
558
+ grade: 'B',
559
+ factors: {
560
+ complexity: 70,
561
+ maintainability: 80,
562
+ documentation: 65,
563
+ testCoverage: 40
564
+ }
565
+ };
566
+ }
567
+
568
+ async _assessTechnicalDebt() {
569
+ // Placeholder - would calculate technical debt
570
+ return {
571
+ estimatedHours: 40,
572
+ categories: {
573
+ refactoring: 20,
574
+ documentation: 10,
575
+ testing: 10
576
+ },
577
+ priorityItems: []
578
+ };
579
+ }
580
+ }
581
+
582
+ // Factory function for creating Sentinel instance
583
+ function createSentinel(options = {}) {
584
+ return new SentinelCore(options);
585
+ }
586
+
587
+ // Main execution if run directly
588
+ async function main() {
589
+ const sentinel = createSentinel({
590
+ rootPath: path.resolve(__dirname, '..'),
591
+ enableRealtime: true,
592
+ enableScheduled: false // Disable scheduled for testing
593
+ });
594
+
595
+ const initResult = await sentinel.initialize();
596
+ if (!initResult.success) {
597
+ console.error('Failed to initialize Sentinel:', initResult.error);
598
+ process.exit(1);
599
+ }
600
+
601
+ const startResult = await sentinel.start();
602
+ console.log('Sentinel status:', sentinel.getStatus());
603
+
604
+ // Keep running
605
+ process.on('SIGINT', async () => {
606
+ console.log('\nShutting down Sentinel...');
607
+ await sentinel.stop();
608
+ process.exit(0);
609
+ });
610
+ }
611
+
612
+ if (require.main === module) {
613
+ main().catch(console.error);
614
+ }
615
+
616
+ module.exports = { SentinelCore, createSentinel };