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