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,541 +0,0 @@
1
- /**
2
- * Sentinel Dependency Graph
3
- *
4
- * Maps all file dependencies in the Orion codebase.
5
- * Answers: "If I change X, what breaks?"
6
- *
7
- * @purpose Track and analyze file dependencies across codebase
8
- * @module sentinel
9
- * @layer infrastructure
10
- * @exports-to sentinel-core, widget-generator
11
- * @side-effects File system reads
12
- */
13
-
14
- const fs = require('fs');
15
- const path = require('path');
16
- const EventEmitter = require('events');
17
-
18
- class DependencyGraph extends EventEmitter {
19
- constructor(config = {}) {
20
- super();
21
-
22
- this.config = {
23
- rootPath: config.rootPath || process.cwd(),
24
- ignorePatterns: config.ignorePatterns || [
25
- 'node_modules', '.git', 'dist', '.vite', '.orion/snapshots'
26
- ],
27
- extensions: config.extensions || ['.js', '.ts', '.jsx', '.tsx', '.php', '.rb'],
28
- ...config
29
- };
30
-
31
- // The graph: file -> { imports: [], importedBy: [] }
32
- this.graph = new Map();
33
-
34
- // Module resolution cache
35
- this.resolveCache = new Map();
36
-
37
- // Statistics
38
- this.stats = {
39
- filesAnalyzed: 0,
40
- totalImports: 0,
41
- unresolvedImports: 0,
42
- circularDependencies: []
43
- };
44
- }
45
-
46
- // Convenience getter so callers can use graph.circularDeps || []
47
- get circularDeps() {
48
- return this.stats.circularDependencies;
49
- }
50
-
51
- /**
52
- * Build the full dependency graph
53
- */
54
- async build() {
55
- // debug logging silenced for clean output
56
- const startTime = Date.now();
57
-
58
- // Clear existing graph
59
- this.graph.clear();
60
- this.resolveCache.clear();
61
- this.stats = {
62
- filesAnalyzed: 0,
63
- totalImports: 0,
64
- unresolvedImports: 0,
65
- circularDependencies: []
66
- };
67
-
68
- // Discover all files
69
- const files = await this._discoverFiles(this.config.rootPath);
70
-
71
- // First pass: extract all imports
72
- for (const file of files) {
73
- await this._analyzeFile(file);
74
- }
75
-
76
- // Second pass: build reverse dependencies (importedBy)
77
- this._buildReverseDependencies();
78
-
79
- // Third pass: detect circular dependencies
80
- this._detectCircularDependencies();
81
-
82
- const duration = Date.now() - startTime;
83
-
84
- return this.getStats();
85
- }
86
-
87
- /**
88
- * Get what a file depends on (imports)
89
- */
90
- getDependencies(filePath) {
91
- const normalized = this._normalizePath(filePath);
92
- const node = this.graph.get(normalized);
93
- return node ? node.imports : [];
94
- }
95
-
96
- /**
97
- * Get what depends on a file (dependents)
98
- */
99
- getDependents(filePath) {
100
- const normalized = this._normalizePath(filePath);
101
- const node = this.graph.get(normalized);
102
- return node ? node.importedBy : [];
103
- }
104
-
105
- /**
106
- * Get full dependency chain (recursive)
107
- */
108
- getFullDependencyChain(filePath, visited = new Set()) {
109
- const normalized = this._normalizePath(filePath);
110
- if (visited.has(normalized)) return []; // Prevent infinite loop
111
-
112
- visited.add(normalized);
113
- const deps = this.getDependencies(normalized);
114
- const chain = [...deps];
115
-
116
- for (const dep of deps) {
117
- if (dep.resolved) {
118
- chain.push(...this.getFullDependencyChain(dep.resolved, visited));
119
- }
120
- }
121
-
122
- return chain;
123
- }
124
-
125
- /**
126
- * IMPACT ANALYSIS: What could break if I change this file?
127
- */
128
- getImpactAnalysis(filePath) {
129
- const normalized = this._normalizePath(filePath);
130
- const result = {
131
- file: normalized,
132
- relativePath: path.relative(this.config.rootPath, normalized),
133
- directDependents: [],
134
- indirectDependents: [],
135
- totalImpact: 0,
136
- riskLevel: 'low',
137
- affectedModules: new Set()
138
- };
139
-
140
- // Get direct dependents
141
- result.directDependents = this.getDependents(normalized).map(d => ({
142
- file: d,
143
- relativePath: path.relative(this.config.rootPath, d)
144
- }));
145
-
146
- // Get indirect dependents (recursive)
147
- const visited = new Set([normalized]);
148
- const queue = [...result.directDependents.map(d => d.file)];
149
-
150
- while (queue.length > 0) {
151
- const current = queue.shift();
152
- if (visited.has(current)) continue;
153
- visited.add(current);
154
-
155
- const dependents = this.getDependents(current);
156
- for (const dep of dependents) {
157
- if (!visited.has(dep)) {
158
- result.indirectDependents.push({
159
- file: dep,
160
- relativePath: path.relative(this.config.rootPath, dep),
161
- via: path.relative(this.config.rootPath, current)
162
- });
163
- queue.push(dep);
164
- }
165
- }
166
- }
167
-
168
- // Calculate total impact
169
- result.totalImpact = result.directDependents.length + result.indirectDependents.length;
170
-
171
- // Determine risk level
172
- if (result.totalImpact === 0) {
173
- result.riskLevel = 'none';
174
- } else if (result.totalImpact <= 3) {
175
- result.riskLevel = 'low';
176
- } else if (result.totalImpact <= 10) {
177
- result.riskLevel = 'medium';
178
- } else if (result.totalImpact <= 25) {
179
- result.riskLevel = 'high';
180
- } else {
181
- result.riskLevel = 'critical';
182
- }
183
-
184
- // Extract affected modules
185
- for (const dep of [...result.directDependents, ...result.indirectDependents]) {
186
- const parts = dep.relativePath.split(path.sep);
187
- if (parts.length > 0) {
188
- result.affectedModules.add(parts[0]);
189
- }
190
- }
191
- result.affectedModules = Array.from(result.affectedModules);
192
-
193
- return result;
194
- }
195
-
196
- /**
197
- * Find most critical files (most dependents)
198
- */
199
- getMostCriticalFiles(limit = 20) {
200
- const files = [];
201
-
202
- for (const [filePath, node] of this.graph) {
203
- files.push({
204
- file: filePath,
205
- relativePath: path.relative(this.config.rootPath, filePath),
206
- dependentCount: node.importedBy.length,
207
- dependencyCount: node.imports.length
208
- });
209
- }
210
-
211
- return files
212
- .sort((a, b) => b.dependentCount - a.dependentCount)
213
- .slice(0, limit);
214
- }
215
-
216
- /**
217
- * Find orphan files (no imports, no dependents)
218
- */
219
- getOrphanFiles() {
220
- const orphans = [];
221
-
222
- for (const [filePath, node] of this.graph) {
223
- if (node.imports.length === 0 && node.importedBy.length === 0) {
224
- orphans.push({
225
- file: filePath,
226
- relativePath: path.relative(this.config.rootPath, filePath)
227
- });
228
- }
229
- }
230
-
231
- return orphans;
232
- }
233
-
234
- /**
235
- * Get module dependency summary
236
- */
237
- getModuleSummary() {
238
- const modules = new Map();
239
-
240
- for (const [filePath, node] of this.graph) {
241
- const relativePath = path.relative(this.config.rootPath, filePath);
242
- const parts = relativePath.split(path.sep);
243
- const moduleName = parts[0];
244
-
245
- if (!modules.has(moduleName)) {
246
- modules.set(moduleName, {
247
- name: moduleName,
248
- files: [],
249
- internalDeps: 0,
250
- externalDeps: 0,
251
- dependedOnBy: new Set()
252
- });
253
- }
254
-
255
- const mod = modules.get(moduleName);
256
- mod.files.push(relativePath);
257
-
258
- // Count internal vs external dependencies
259
- for (const imp of node.imports) {
260
- if (imp.resolved) {
261
- const impRelative = path.relative(this.config.rootPath, imp.resolved);
262
- const impModule = impRelative.split(path.sep)[0];
263
- if (impModule === moduleName) {
264
- mod.internalDeps++;
265
- } else {
266
- mod.externalDeps++;
267
- }
268
- }
269
- }
270
-
271
- // Track which modules depend on this one
272
- for (const depPath of node.importedBy) {
273
- const depRelative = path.relative(this.config.rootPath, depPath);
274
- const depModule = depRelative.split(path.sep)[0];
275
- if (depModule !== moduleName) {
276
- mod.dependedOnBy.add(depModule);
277
- }
278
- }
279
- }
280
-
281
- // Convert to array and calculate metrics
282
- const result = [];
283
- for (const [name, mod] of modules) {
284
- result.push({
285
- name,
286
- fileCount: mod.files.length,
287
- internalDeps: mod.internalDeps,
288
- externalDeps: mod.externalDeps,
289
- dependedOnByCount: mod.dependedOnBy.size,
290
- dependedOnBy: Array.from(mod.dependedOnBy)
291
- });
292
- }
293
-
294
- return result.sort((a, b) => b.fileCount - a.fileCount);
295
- }
296
-
297
- /**
298
- * Get statistics
299
- */
300
- getStats() {
301
- return {
302
- ...this.stats,
303
- totalFiles: this.graph.size,
304
- circularCount: this.stats.circularDependencies.length
305
- };
306
- }
307
-
308
- /**
309
- * Export graph as JSON
310
- */
311
- toJSON() {
312
- const data = {
313
- generated: new Date().toISOString(),
314
- rootPath: this.config.rootPath,
315
- stats: this.getStats(),
316
- files: {}
317
- };
318
-
319
- for (const [filePath, node] of this.graph) {
320
- const relativePath = path.relative(this.config.rootPath, filePath);
321
- data.files[relativePath] = {
322
- imports: node.imports.map(i => ({
323
- module: i.module,
324
- resolved: i.resolved ? path.relative(this.config.rootPath, i.resolved) : null,
325
- specifiers: i.specifiers
326
- })),
327
- importedBy: node.importedBy.map(p => path.relative(this.config.rootPath, p))
328
- };
329
- }
330
-
331
- return data;
332
- }
333
-
334
- // Private methods
335
-
336
- async _discoverFiles(dir, files = []) {
337
- try {
338
- const entries = fs.readdirSync(dir, { withFileTypes: true });
339
-
340
- for (const entry of entries) {
341
- const fullPath = path.join(dir, entry.name);
342
-
343
- // Check ignore patterns
344
- if (this._shouldIgnore(entry.name)) {
345
- continue;
346
- }
347
-
348
- if (entry.isDirectory()) {
349
- await this._discoverFiles(fullPath, files);
350
- } else if (entry.isFile()) {
351
- const ext = path.extname(entry.name).toLowerCase();
352
- if (this.config.extensions.includes(ext)) {
353
- files.push(fullPath);
354
- }
355
- }
356
- }
357
- } catch (error) {
358
- // Skip inaccessible directories
359
- }
360
-
361
- return files;
362
- }
363
-
364
- _shouldIgnore(name) {
365
- return this.config.ignorePatterns.some(pattern =>
366
- name === pattern || name.startsWith(pattern)
367
- );
368
- }
369
-
370
- async _analyzeFile(filePath) {
371
- try {
372
- const content = fs.readFileSync(filePath, 'utf8');
373
- const imports = this._extractImports(content, filePath);
374
-
375
- this.graph.set(filePath, {
376
- imports,
377
- importedBy: [] // Will be filled in second pass
378
- });
379
-
380
- this.stats.filesAnalyzed++;
381
- this.stats.totalImports += imports.length;
382
- this.stats.unresolvedImports += imports.filter(i => !i.resolved).length;
383
-
384
- } catch (error) {
385
- // Skip files that can't be read
386
- }
387
- }
388
-
389
- _extractImports(content, filePath) {
390
- const imports = [];
391
- const fileDir = path.dirname(filePath);
392
-
393
- // CommonJS: require('module') or require("module")
394
- const requireRegex = /(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
395
- let match;
396
-
397
- while ((match = requireRegex.exec(content)) !== null) {
398
- const specifiers = match[1]
399
- ? match[1].split(',').map(s => s.trim().split(':')[0].trim()).filter(Boolean)
400
- : [match[2]];
401
- const modulePath = match[3];
402
-
403
- imports.push({
404
- module: modulePath,
405
- specifiers,
406
- resolved: this._resolveModule(modulePath, fileDir),
407
- type: 'require'
408
- });
409
- }
410
-
411
- // Also catch: require('module') without assignment
412
- const bareRequireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
413
- while ((match = bareRequireRegex.exec(content)) !== null) {
414
- const modulePath = match[1];
415
- // Skip if already captured
416
- if (!imports.some(i => i.module === modulePath)) {
417
- imports.push({
418
- module: modulePath,
419
- specifiers: [],
420
- resolved: this._resolveModule(modulePath, fileDir),
421
- type: 'require'
422
- });
423
- }
424
- }
425
-
426
- // ES6: import ... from 'module'
427
- const importRegex = /import\s+(?:(?:\{([^}]+)\}|(\w+)|\*\s+as\s+(\w+))(?:\s*,\s*)?)+\s+from\s+['"]([^'"]+)['"]/g;
428
- while ((match = importRegex.exec(content)) !== null) {
429
- const specifiers = [];
430
- if (match[1]) specifiers.push(...match[1].split(',').map(s => s.trim().split(' as ')[0].trim()).filter(Boolean));
431
- if (match[2]) specifiers.push(match[2]);
432
- if (match[3]) specifiers.push(match[3]);
433
- const modulePath = match[4];
434
-
435
- imports.push({
436
- module: modulePath,
437
- specifiers,
438
- resolved: this._resolveModule(modulePath, fileDir),
439
- type: 'import'
440
- });
441
- }
442
-
443
- return imports;
444
- }
445
-
446
- _resolveModule(modulePath, fromDir) {
447
- // Check cache
448
- const cacheKey = `${fromDir}:${modulePath}`;
449
- if (this.resolveCache.has(cacheKey)) {
450
- return this.resolveCache.get(cacheKey);
451
- }
452
-
453
- let resolved = null;
454
-
455
- // Skip external modules (node_modules)
456
- if (!modulePath.startsWith('.') && !modulePath.startsWith('/')) {
457
- this.resolveCache.set(cacheKey, null);
458
- return null;
459
- }
460
-
461
- // Try to resolve relative path
462
- const basePath = path.resolve(fromDir, modulePath);
463
- const extensions = ['', '.js', '.ts', '.jsx', '.tsx', '.php', '.rb', '/index.js', '/index.ts'];
464
-
465
- for (const ext of extensions) {
466
- const tryPath = basePath + ext;
467
- if (fs.existsSync(tryPath) && fs.statSync(tryPath).isFile()) {
468
- resolved = tryPath;
469
- break;
470
- }
471
- }
472
-
473
- this.resolveCache.set(cacheKey, resolved);
474
- return resolved;
475
- }
476
-
477
- _buildReverseDependencies() {
478
- for (const [filePath, node] of this.graph) {
479
- for (const imp of node.imports) {
480
- if (imp.resolved && this.graph.has(imp.resolved)) {
481
- const targetNode = this.graph.get(imp.resolved);
482
- if (!targetNode.importedBy.includes(filePath)) {
483
- targetNode.importedBy.push(filePath);
484
- }
485
- }
486
- }
487
- }
488
- }
489
-
490
- _detectCircularDependencies() {
491
- const visited = new Set();
492
- const recursionStack = new Set();
493
-
494
- const dfs = (filePath, chain = []) => {
495
- if (recursionStack.has(filePath)) {
496
- // Found circular dependency
497
- const cycleStart = chain.indexOf(filePath);
498
- const cycle = chain.slice(cycleStart).map(p =>
499
- path.relative(this.config.rootPath, p)
500
- );
501
- cycle.push(path.relative(this.config.rootPath, filePath));
502
-
503
- // Only add if not already recorded
504
- const cycleKey = cycle.sort().join(' -> ');
505
- if (!this.stats.circularDependencies.some(c => c.sort().join(' -> ') === cycleKey)) {
506
- this.stats.circularDependencies.push(cycle);
507
- }
508
- return;
509
- }
510
-
511
- if (visited.has(filePath)) return;
512
-
513
- visited.add(filePath);
514
- recursionStack.add(filePath);
515
-
516
- const node = this.graph.get(filePath);
517
- if (node) {
518
- for (const imp of node.imports) {
519
- if (imp.resolved) {
520
- dfs(imp.resolved, [...chain, filePath]);
521
- }
522
- }
523
- }
524
-
525
- recursionStack.delete(filePath);
526
- };
527
-
528
- for (const filePath of this.graph.keys()) {
529
- dfs(filePath);
530
- }
531
- }
532
-
533
- _normalizePath(filePath) {
534
- if (path.isAbsolute(filePath)) {
535
- return filePath;
536
- }
537
- return path.resolve(this.config.rootPath, filePath);
538
- }
539
- }
540
-
541
- module.exports = DependencyGraph;