thuban 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/README.md +2 -0
- package/dist/cli.js +1 -1
- package/dist/packages/crucible/.golden/golden-master.json +414 -0
- package/dist/packages/crucible/badge-generator.js +1 -0
- package/dist/packages/crucible/banner.js +1 -0
- package/dist/packages/crucible/ci-integration.js +1 -0
- package/dist/packages/crucible/golden-master.js +1 -0
- package/dist/packages/crucible/index.js +1 -0
- package/dist/packages/crucible/kenny-mode.js +1 -0
- package/dist/packages/crucible/leaderboard.js +1 -0
- package/dist/packages/crucible/mutation-engine.js +1 -0
- package/dist/packages/crucible/pattern-learner.js +1 -0
- package/dist/packages/crucible/report-generator.js +1 -0
- package/dist/packages/crucible/rule-loader.js +1 -0
- package/dist/packages/crucible/seeding-engine.js +1 -0
- package/dist/packages/crucible/seeds/csharp/seed-001.cs +28 -0
- package/dist/packages/crucible/seeds/csharp/seed-002.cs +24 -0
- package/dist/packages/crucible/seeds/csharp/seed-003.cs +30 -0
- package/dist/packages/crucible/seeds/csharp/seed-004.cs +31 -0
- package/dist/packages/crucible/seeds/csharp/seed-005.cs +28 -0
- package/dist/packages/crucible/seeds/csharp/seed-006.cs +25 -0
- package/dist/packages/crucible/seeds/csharp/seed-007.cs +34 -0
- package/dist/packages/crucible/seeds/csharp/seed-008.cs +23 -0
- package/dist/packages/crucible/seeds/csharp/seed-009.cs +27 -0
- package/dist/packages/crucible/seeds/csharp/seed-010.cs +29 -0
- package/dist/packages/crucible/seeds/csharp/seed-011.cs +32 -0
- package/dist/packages/crucible/seeds/csharp/seed-012.cs +27 -0
- package/dist/packages/crucible/seeds/csharp/seed-013.cs +27 -0
- package/dist/packages/crucible/seeds/csharp/seed-014.cs +26 -0
- package/dist/packages/crucible/seeds/csharp/seed-015.cs +33 -0
- package/dist/packages/crucible/seeds/csharp/seed-016.cs +25 -0
- package/dist/packages/crucible/seeds/csharp/seed-017.cs +22 -0
- package/dist/packages/crucible/seeds/csharp/seed-018.cs +26 -0
- package/dist/packages/crucible/seeds/csharp/seed-019.cs +25 -0
- package/dist/packages/crucible/seeds/csharp/seed-020.cs +22 -0
- package/dist/packages/crucible/seeds/csharp/seed-021.cs +28 -0
- package/dist/packages/crucible/seeds/csharp/seed-022.cs +25 -0
- package/dist/packages/crucible/seeds/csharp/seed-023.cs +33 -0
- package/dist/packages/crucible/seeds/csharp/seed-024.cs +31 -0
- package/dist/packages/crucible/seeds/csharp/seed-025.cs +31 -0
- package/dist/packages/crucible/seeds/csharp/seed-026.cs +25 -0
- package/dist/packages/crucible/seeds/csharp/seed-027.cs +29 -0
- package/dist/packages/crucible/seeds/csharp/seed-028.cs +25 -0
- package/dist/packages/crucible/seeds/csharp/seed-029.cs +27 -0
- package/dist/packages/crucible/seeds/csharp/seed-030.cs +27 -0
- package/dist/packages/crucible/seeds/csharp/seed-031.cs +28 -0
- package/dist/packages/crucible/seeds/csharp/seed-032.cs +30 -0
- package/dist/packages/crucible/seeds/csharp/seed-033.cs +27 -0
- package/dist/packages/crucible/seeds/csharp/seed-034.cs +25 -0
- package/dist/packages/crucible/seeds/csharp/seed-035.cs +32 -0
- package/dist/packages/crucible/seeds/csharp/seed-036.cs +28 -0
- package/dist/packages/crucible/seeds/csharp/seed-037.cs +29 -0
- package/dist/packages/crucible/seeds/csharp/seed-038.cs +28 -0
- package/dist/packages/crucible/seeds/csharp/seed-039.cs +29 -0
- package/dist/packages/crucible/seeds/csharp/seed-040.cs +28 -0
- package/dist/packages/crucible/seeds/csharp/seed-041.cs +33 -0
- package/dist/packages/crucible/seeds/csharp/seed-042.cs +27 -0
- package/dist/packages/crucible/seeds/csharp/seed-043.cs +27 -0
- package/dist/packages/crucible/seeds/csharp/seed-044.cs +25 -0
- package/dist/packages/crucible/seeds/csharp/seed-045.cs +30 -0
- package/dist/packages/crucible/seeds/csharp/seed-046.cs +29 -0
- package/dist/packages/crucible/seeds/csharp/seed-047.cs +26 -0
- package/dist/packages/crucible/seeds/csharp/seed-048.cs +31 -0
- package/dist/packages/crucible/seeds/csharp/seed-049.cs +27 -0
- package/dist/packages/crucible/seeds/csharp/seed-050.cs +30 -0
- package/dist/packages/crucible/seeds/csharp/seed-051.cs +34 -0
- package/dist/packages/crucible/seeds/csharp/seed-052.cs +36 -0
- package/dist/packages/crucible/seeds/go/seed-001.go +29 -0
- package/dist/packages/crucible/seeds/go/seed-002.go +27 -0
- package/dist/packages/crucible/seeds/go/seed-003.go +31 -0
- package/dist/packages/crucible/seeds/go/seed-004.go +27 -0
- package/dist/packages/crucible/seeds/go/seed-005.go +27 -0
- package/dist/packages/crucible/seeds/go/seed-006.go +29 -0
- package/dist/packages/crucible/seeds/go/seed-007.go +24 -0
- package/dist/packages/crucible/seeds/go/seed-008.go +25 -0
- package/dist/packages/crucible/seeds/go/seed-009.go +30 -0
- package/dist/packages/crucible/seeds/go/seed-010.go +35 -0
- package/dist/packages/crucible/seeds/go/seed-011.go +24 -0
- package/dist/packages/crucible/seeds/go/seed-012.go +24 -0
- package/dist/packages/crucible/seeds/go/seed-013.go +31 -0
- package/dist/packages/crucible/seeds/go/seed-014.go +35 -0
- package/dist/packages/crucible/seeds/go/seed-015.go +25 -0
- package/dist/packages/crucible/seeds/go/seed-016.go +19 -0
- package/dist/packages/crucible/seeds/go/seed-017.go +23 -0
- package/dist/packages/crucible/seeds/go/seed-018.go +23 -0
- package/dist/packages/crucible/seeds/go/seed-019.go +22 -0
- package/dist/packages/crucible/seeds/go/seed-020.go +31 -0
- package/dist/packages/crucible/seeds/go/seed-021.go +22 -0
- package/dist/packages/crucible/seeds/go/seed-022.go +29 -0
- package/dist/packages/crucible/seeds/go/seed-023.go +26 -0
- package/dist/packages/crucible/seeds/go/seed-024.go +27 -0
- package/dist/packages/crucible/seeds/go/seed-025.go +23 -0
- package/dist/packages/crucible/seeds/go/seed-026.go +29 -0
- package/dist/packages/crucible/seeds/go/seed-027.go +26 -0
- package/dist/packages/crucible/seeds/go/seed-028.go +23 -0
- package/dist/packages/crucible/seeds/go/seed-029.go +28 -0
- package/dist/packages/crucible/seeds/go/seed-030.go +27 -0
- package/dist/packages/crucible/seeds/go/seed-031.go +28 -0
- package/dist/packages/crucible/seeds/go/seed-032.go +27 -0
- package/dist/packages/crucible/seeds/go/seed-033.go +29 -0
- package/dist/packages/crucible/seeds/go/seed-034.go +22 -0
- package/dist/packages/crucible/seeds/go/seed-035.go +32 -0
- package/dist/packages/crucible/seeds/go/seed-036.go +27 -0
- package/dist/packages/crucible/seeds/go/seed-037.go +27 -0
- package/dist/packages/crucible/seeds/go/seed-038.go +27 -0
- package/dist/packages/crucible/seeds/go/seed-039.go +26 -0
- package/dist/packages/crucible/seeds/go/seed-040.go +37 -0
- package/dist/packages/crucible/seeds/go/seed-041.go +23 -0
- package/dist/packages/crucible/seeds/go/seed-042.go +32 -0
- package/dist/packages/crucible/seeds/go/seed-043.go +35 -0
- package/dist/packages/crucible/seeds/go/seed-044.go +25 -0
- package/dist/packages/crucible/seeds/go/seed-045.go +26 -0
- package/dist/packages/crucible/seeds/go/seed-046.go +34 -0
- package/dist/packages/crucible/seeds/go/seed-047.go +26 -0
- package/dist/packages/crucible/seeds/go/seed-048.go +29 -0
- package/dist/packages/crucible/seeds/go/seed-049.go +24 -0
- package/dist/packages/crucible/seeds/go/seed-050.go +27 -0
- package/dist/packages/crucible/seeds/go/seed-051.go +38 -0
- package/dist/packages/crucible/seeds/go/seed-052.go +27 -0
- package/dist/packages/crucible/seeds/java/seed-001.java +23 -0
- package/dist/packages/crucible/seeds/java/seed-002.java +22 -0
- package/dist/packages/crucible/seeds/java/seed-003.java +28 -0
- package/dist/packages/crucible/seeds/java/seed-004.java +26 -0
- package/dist/packages/crucible/seeds/java/seed-005.java +33 -0
- package/dist/packages/crucible/seeds/java/seed-006.java +22 -0
- package/dist/packages/crucible/seeds/java/seed-007.java +21 -0
- package/dist/packages/crucible/seeds/java/seed-008.java +29 -0
- package/dist/packages/crucible/seeds/java/seed-009.java +24 -0
- package/dist/packages/crucible/seeds/java/seed-010.java +30 -0
- package/dist/packages/crucible/seeds/java/seed-011.java +25 -0
- package/dist/packages/crucible/seeds/java/seed-012.java +20 -0
- package/dist/packages/crucible/seeds/java/seed-013.java +26 -0
- package/dist/packages/crucible/seeds/java/seed-014.java +23 -0
- package/dist/packages/crucible/seeds/java/seed-015.java +25 -0
- package/dist/packages/crucible/seeds/java/seed-016.java +22 -0
- package/dist/packages/crucible/seeds/java/seed-017.java +24 -0
- package/dist/packages/crucible/seeds/java/seed-018.java +24 -0
- package/dist/packages/crucible/seeds/java/seed-019.java +38 -0
- package/dist/packages/crucible/seeds/java/seed-020.java +24 -0
- package/dist/packages/crucible/seeds/java/seed-021.java +22 -0
- package/dist/packages/crucible/seeds/java/seed-022.java +22 -0
- package/dist/packages/crucible/seeds/java/seed-023.java +22 -0
- package/dist/packages/crucible/seeds/java/seed-024.java +25 -0
- package/dist/packages/crucible/seeds/java/seed-025.java +27 -0
- package/dist/packages/crucible/seeds/java/seed-026.java +25 -0
- package/dist/packages/crucible/seeds/java/seed-027.java +22 -0
- package/dist/packages/crucible/seeds/java/seed-028.java +21 -0
- package/dist/packages/crucible/seeds/java/seed-029.java +28 -0
- package/dist/packages/crucible/seeds/java/seed-030.java +27 -0
- package/dist/packages/crucible/seeds/java/seed-031.java +23 -0
- package/dist/packages/crucible/seeds/java/seed-032.java +22 -0
- package/dist/packages/crucible/seeds/java/seed-033.java +24 -0
- package/dist/packages/crucible/seeds/java/seed-034.java +24 -0
- package/dist/packages/crucible/seeds/java/seed-035.java +29 -0
- package/dist/packages/crucible/seeds/java/seed-036.java +25 -0
- package/dist/packages/crucible/seeds/java/seed-037.java +30 -0
- package/dist/packages/crucible/seeds/java/seed-038.java +29 -0
- package/dist/packages/crucible/seeds/java/seed-039.java +26 -0
- package/dist/packages/crucible/seeds/java/seed-040.java +31 -0
- package/dist/packages/crucible/seeds/java/seed-041.java +20 -0
- package/dist/packages/crucible/seeds/java/seed-042.java +26 -0
- package/dist/packages/crucible/seeds/java/seed-043.java +31 -0
- package/dist/packages/crucible/seeds/java/seed-044.java +22 -0
- package/dist/packages/crucible/seeds/java/seed-045.java +25 -0
- package/dist/packages/crucible/seeds/java/seed-046.java +22 -0
- package/dist/packages/crucible/seeds/java/seed-047.java +26 -0
- package/dist/packages/crucible/seeds/java/seed-048.java +24 -0
- package/dist/packages/crucible/seeds/java/seed-049.java +28 -0
- package/dist/packages/crucible/seeds/java/seed-050.java +29 -0
- package/dist/packages/crucible/seeds/java/seed-051.java +25 -0
- package/dist/packages/crucible/seeds/java/seed-052.java +32 -0
- package/dist/packages/crucible/seeds/js/seed-001.js +27 -0
- package/dist/packages/crucible/seeds/js/seed-002.js +28 -0
- package/dist/packages/crucible/seeds/js/seed-003.js +30 -0
- package/dist/packages/crucible/seeds/js/seed-004.js +28 -0
- package/dist/packages/crucible/seeds/js/seed-005.js +31 -0
- package/dist/packages/crucible/seeds/js/seed-006.js +28 -0
- package/dist/packages/crucible/seeds/js/seed-007.js +26 -0
- package/dist/packages/crucible/seeds/js/seed-008.js +30 -0
- package/dist/packages/crucible/seeds/js/seed-009.js +30 -0
- package/dist/packages/crucible/seeds/js/seed-010.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-011.js +34 -0
- package/dist/packages/crucible/seeds/js/seed-012.js +32 -0
- package/dist/packages/crucible/seeds/js/seed-013.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-014.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-015.js +31 -0
- package/dist/packages/crucible/seeds/js/seed-016.js +31 -0
- package/dist/packages/crucible/seeds/js/seed-017.js +30 -0
- package/dist/packages/crucible/seeds/js/seed-018.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-019.js +32 -0
- package/dist/packages/crucible/seeds/js/seed-020.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-021.js +31 -0
- package/dist/packages/crucible/seeds/js/seed-022.js +31 -0
- package/dist/packages/crucible/seeds/js/seed-023.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-024.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-025.js +34 -0
- package/dist/packages/crucible/seeds/js/seed-026.js +31 -0
- package/dist/packages/crucible/seeds/js/seed-027.js +31 -0
- package/dist/packages/crucible/seeds/js/seed-028.js +33 -0
- package/dist/packages/crucible/seeds/js/seed-029.js +33 -0
- package/dist/packages/crucible/seeds/js/seed-030.js +30 -0
- package/dist/packages/crucible/seeds/js/seed-031.js +31 -0
- package/dist/packages/crucible/seeds/js/seed-032.js +34 -0
- package/dist/packages/crucible/seeds/js/seed-033.js +33 -0
- package/dist/packages/crucible/seeds/js/seed-034.js +33 -0
- package/dist/packages/crucible/seeds/js/seed-035.js +30 -0
- package/dist/packages/crucible/seeds/js/seed-036.js +30 -0
- package/dist/packages/crucible/seeds/js/seed-037.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-038.js +31 -0
- package/dist/packages/crucible/seeds/js/seed-039.js +34 -0
- package/dist/packages/crucible/seeds/js/seed-040.js +35 -0
- package/dist/packages/crucible/seeds/js/seed-041.js +32 -0
- package/dist/packages/crucible/seeds/js/seed-042.js +32 -0
- package/dist/packages/crucible/seeds/js/seed-043.js +27 -0
- package/dist/packages/crucible/seeds/js/seed-044.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-045.js +33 -0
- package/dist/packages/crucible/seeds/js/seed-046.js +33 -0
- package/dist/packages/crucible/seeds/js/seed-047.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-048.js +29 -0
- package/dist/packages/crucible/seeds/js/seed-049.js +34 -0
- package/dist/packages/crucible/seeds/js/seed-050.js +34 -0
- package/dist/packages/crucible/seeds/js/seed-051.js +30 -0
- package/dist/packages/crucible/seeds/js/seed-052.js +30 -0
- package/dist/packages/crucible/seeds/js/seed-053.js +26 -0
- package/dist/packages/crucible/seeds/js/seed-054.js +35 -0
- package/dist/packages/crucible/seeds/js/seed-055.js +33 -0
- package/dist/packages/crucible/seeds/kotlin/seed-001.kt +24 -0
- package/dist/packages/crucible/seeds/kotlin/seed-002.kt +27 -0
- package/dist/packages/crucible/seeds/kotlin/seed-003.kt +29 -0
- package/dist/packages/crucible/seeds/kotlin/seed-004.kt +24 -0
- package/dist/packages/crucible/seeds/kotlin/seed-005.kt +29 -0
- package/dist/packages/crucible/seeds/kotlin/seed-006.kt +21 -0
- package/dist/packages/crucible/seeds/kotlin/seed-007.kt +23 -0
- package/dist/packages/crucible/seeds/kotlin/seed-008.kt +25 -0
- package/dist/packages/crucible/seeds/kotlin/seed-009.kt +21 -0
- package/dist/packages/crucible/seeds/kotlin/seed-010.kt +28 -0
- package/dist/packages/crucible/seeds/kotlin/seed-011.kt +22 -0
- package/dist/packages/crucible/seeds/kotlin/seed-012.kt +25 -0
- package/dist/packages/crucible/seeds/kotlin/seed-013.kt +30 -0
- package/dist/packages/crucible/seeds/kotlin/seed-014.kt +27 -0
- package/dist/packages/crucible/seeds/kotlin/seed-015.kt +24 -0
- package/dist/packages/crucible/seeds/kotlin/seed-016.kt +28 -0
- package/dist/packages/crucible/seeds/kotlin/seed-017.kt +33 -0
- package/dist/packages/crucible/seeds/kotlin/seed-018.kt +31 -0
- package/dist/packages/crucible/seeds/kotlin/seed-019.kt +28 -0
- package/dist/packages/crucible/seeds/kotlin/seed-020.kt +31 -0
- package/dist/packages/crucible/seeds/kotlin/seed-021.kt +34 -0
- package/dist/packages/crucible/seeds/kotlin/seed-022.kt +29 -0
- package/dist/packages/crucible/seeds/kotlin/seed-023.kt +20 -0
- package/dist/packages/crucible/seeds/kotlin/seed-024.kt +23 -0
- package/dist/packages/crucible/seeds/kotlin/seed-025.kt +23 -0
- package/dist/packages/crucible/seeds/kotlin/seed-026.kt +26 -0
- package/dist/packages/crucible/seeds/kotlin/seed-027.kt +29 -0
- package/dist/packages/crucible/seeds/kotlin/seed-028.kt +24 -0
- package/dist/packages/crucible/seeds/kotlin/seed-029.kt +27 -0
- package/dist/packages/crucible/seeds/kotlin/seed-030.kt +26 -0
- package/dist/packages/crucible/seeds/kotlin/seed-031.kt +28 -0
- package/dist/packages/crucible/seeds/kotlin/seed-032.kt +29 -0
- package/dist/packages/crucible/seeds/kotlin/seed-033.kt +30 -0
- package/dist/packages/crucible/seeds/kotlin/seed-034.kt +26 -0
- package/dist/packages/crucible/seeds/kotlin/seed-035.kt +33 -0
- package/dist/packages/crucible/seeds/kotlin/seed-036.kt +25 -0
- package/dist/packages/crucible/seeds/kotlin/seed-037.kt +26 -0
- package/dist/packages/crucible/seeds/kotlin/seed-038.kt +25 -0
- package/dist/packages/crucible/seeds/kotlin/seed-039.kt +29 -0
- package/dist/packages/crucible/seeds/kotlin/seed-040.kt +28 -0
- package/dist/packages/crucible/seeds/kotlin/seed-041.kt +31 -0
- package/dist/packages/crucible/seeds/kotlin/seed-042.kt +27 -0
- package/dist/packages/crucible/seeds/kotlin/seed-043.kt +26 -0
- package/dist/packages/crucible/seeds/kotlin/seed-044.kt +27 -0
- package/dist/packages/crucible/seeds/kotlin/seed-045.kt +27 -0
- package/dist/packages/crucible/seeds/kotlin/seed-046.kt +33 -0
- package/dist/packages/crucible/seeds/kotlin/seed-047.kt +32 -0
- package/dist/packages/crucible/seeds/kotlin/seed-048.kt +20 -0
- package/dist/packages/crucible/seeds/kotlin/seed-049.kt +23 -0
- package/dist/packages/crucible/seeds/kotlin/seed-050.kt +19 -0
- package/dist/packages/crucible/seeds/php/seed-001.php +23 -0
- package/dist/packages/crucible/seeds/php/seed-002.php +18 -0
- package/dist/packages/crucible/seeds/php/seed-003.php +25 -0
- package/dist/packages/crucible/seeds/php/seed-004.php +21 -0
- package/dist/packages/crucible/seeds/php/seed-005.php +21 -0
- package/dist/packages/crucible/seeds/php/seed-006.php +21 -0
- package/dist/packages/crucible/seeds/php/seed-007.php +18 -0
- package/dist/packages/crucible/seeds/php/seed-008.php +19 -0
- package/dist/packages/crucible/seeds/php/seed-009.php +24 -0
- package/dist/packages/crucible/seeds/php/seed-010.php +24 -0
- package/dist/packages/crucible/seeds/php/seed-011.php +18 -0
- package/dist/packages/crucible/seeds/php/seed-012.php +24 -0
- package/dist/packages/crucible/seeds/php/seed-013.php +23 -0
- package/dist/packages/crucible/seeds/php/seed-014.php +20 -0
- package/dist/packages/crucible/seeds/php/seed-015.php +18 -0
- package/dist/packages/crucible/seeds/php/seed-016.php +18 -0
- package/dist/packages/crucible/seeds/php/seed-017.php +18 -0
- package/dist/packages/crucible/seeds/php/seed-018.php +17 -0
- package/dist/packages/crucible/seeds/php/seed-019.php +24 -0
- package/dist/packages/crucible/seeds/php/seed-020.php +19 -0
- package/dist/packages/crucible/seeds/php/seed-021.php +22 -0
- package/dist/packages/crucible/seeds/php/seed-022.php +15 -0
- package/dist/packages/crucible/seeds/php/seed-023.php +24 -0
- package/dist/packages/crucible/seeds/php/seed-024.php +22 -0
- package/dist/packages/crucible/seeds/php/seed-025.php +20 -0
- package/dist/packages/crucible/seeds/php/seed-026.php +22 -0
- package/dist/packages/crucible/seeds/php/seed-027.php +15 -0
- package/dist/packages/crucible/seeds/php/seed-028.php +15 -0
- package/dist/packages/crucible/seeds/php/seed-029.php +21 -0
- package/dist/packages/crucible/seeds/php/seed-030.php +21 -0
- package/dist/packages/crucible/seeds/php/seed-031.php +23 -0
- package/dist/packages/crucible/seeds/php/seed-032.php +18 -0
- package/dist/packages/crucible/seeds/php/seed-033.php +18 -0
- package/dist/packages/crucible/seeds/php/seed-034.php +24 -0
- package/dist/packages/crucible/seeds/php/seed-035.php +22 -0
- package/dist/packages/crucible/seeds/php/seed-036.php +17 -0
- package/dist/packages/crucible/seeds/php/seed-037.php +19 -0
- package/dist/packages/crucible/seeds/php/seed-038.php +20 -0
- package/dist/packages/crucible/seeds/php/seed-039.php +15 -0
- package/dist/packages/crucible/seeds/php/seed-040.php +21 -0
- package/dist/packages/crucible/seeds/php/seed-041.php +20 -0
- package/dist/packages/crucible/seeds/php/seed-042.php +21 -0
- package/dist/packages/crucible/seeds/php/seed-043.php +21 -0
- package/dist/packages/crucible/seeds/php/seed-044.php +25 -0
- package/dist/packages/crucible/seeds/php/seed-045.php +25 -0
- package/dist/packages/crucible/seeds/php/seed-046.php +20 -0
- package/dist/packages/crucible/seeds/php/seed-047.php +21 -0
- package/dist/packages/crucible/seeds/php/seed-048.php +19 -0
- package/dist/packages/crucible/seeds/php/seed-049.php +18 -0
- package/dist/packages/crucible/seeds/php/seed-050.php +20 -0
- package/dist/packages/crucible/seeds/python/seed-001.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-002.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-003.py +31 -0
- package/dist/packages/crucible/seeds/python/seed-004.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-005.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-006.py +34 -0
- package/dist/packages/crucible/seeds/python/seed-007.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-008.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-009.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-010.py +42 -0
- package/dist/packages/crucible/seeds/python/seed-011.py +30 -0
- package/dist/packages/crucible/seeds/python/seed-012.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-013.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-014.py +40 -0
- package/dist/packages/crucible/seeds/python/seed-015.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-016.py +34 -0
- package/dist/packages/crucible/seeds/python/seed-017.py +34 -0
- package/dist/packages/crucible/seeds/python/seed-018.py +30 -0
- package/dist/packages/crucible/seeds/python/seed-019.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-020.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-021 2.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-021 3.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-021.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-022 2.py +34 -0
- package/dist/packages/crucible/seeds/python/seed-022 3.py +34 -0
- package/dist/packages/crucible/seeds/python/seed-022.py +34 -0
- package/dist/packages/crucible/seeds/python/seed-023 2.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-023 3.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-023.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-024 2.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-024 3.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-024.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-025 2.py +40 -0
- package/dist/packages/crucible/seeds/python/seed-025 3.py +40 -0
- package/dist/packages/crucible/seeds/python/seed-025.py +40 -0
- package/dist/packages/crucible/seeds/python/seed-026 2.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-026 3.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-026.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-027 2.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-027 3.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-027.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-028 2.py +42 -0
- package/dist/packages/crucible/seeds/python/seed-028 3.py +42 -0
- package/dist/packages/crucible/seeds/python/seed-028.py +42 -0
- package/dist/packages/crucible/seeds/python/seed-029.py +42 -0
- package/dist/packages/crucible/seeds/python/seed-030 2.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-030 3.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-030.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-031 2.py +34 -0
- package/dist/packages/crucible/seeds/python/seed-031 3.py +34 -0
- package/dist/packages/crucible/seeds/python/seed-031.py +34 -0
- package/dist/packages/crucible/seeds/python/seed-032.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-033.py +32 -0
- package/dist/packages/crucible/seeds/python/seed-034.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-035.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-036 2.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-036 3.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-036.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-037 2.py +41 -0
- package/dist/packages/crucible/seeds/python/seed-037 3.py +41 -0
- package/dist/packages/crucible/seeds/python/seed-037.py +41 -0
- package/dist/packages/crucible/seeds/python/seed-038 2.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-038 3.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-038.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-039 2.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-039 3.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-039.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-040 2.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-040 3.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-040.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-041 2.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-041 3.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-041.py +37 -0
- package/dist/packages/crucible/seeds/python/seed-042 2.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-042 3.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-042.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-043 2.py +32 -0
- package/dist/packages/crucible/seeds/python/seed-043 3.py +32 -0
- package/dist/packages/crucible/seeds/python/seed-043.py +32 -0
- package/dist/packages/crucible/seeds/python/seed-044 2.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-044 3.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-044.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-045 2.py +36 -0
- package/dist/packages/crucible/seeds/python/seed-045 3.py +36 -0
- package/dist/packages/crucible/seeds/python/seed-045.py +36 -0
- package/dist/packages/crucible/seeds/python/seed-046 2.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-046 3.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-046.py +33 -0
- package/dist/packages/crucible/seeds/python/seed-047 2.py +44 -0
- package/dist/packages/crucible/seeds/python/seed-047 3.py +44 -0
- package/dist/packages/crucible/seeds/python/seed-047.py +44 -0
- package/dist/packages/crucible/seeds/python/seed-048 2.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-048 3.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-048.py +35 -0
- package/dist/packages/crucible/seeds/python/seed-049 2.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-049 3.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-049.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-050 2.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-050 3.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-050.py +39 -0
- package/dist/packages/crucible/seeds/python/seed-051 2.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-051 3.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-051.py +38 -0
- package/dist/packages/crucible/seeds/python/seed-052 2.py +41 -0
- package/dist/packages/crucible/seeds/python/seed-052 3.py +41 -0
- package/dist/packages/crucible/seeds/python/seed-052.py +41 -0
- package/dist/packages/crucible/seeds/ruby/seed-001 2.rb +15 -0
- package/dist/packages/crucible/seeds/ruby/seed-001 3.rb +15 -0
- package/dist/packages/crucible/seeds/ruby/seed-001.rb +15 -0
- package/dist/packages/crucible/seeds/ruby/seed-002 2.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-002 3.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-002.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-003 2.rb +25 -0
- package/dist/packages/crucible/seeds/ruby/seed-003 3.rb +25 -0
- package/dist/packages/crucible/seeds/ruby/seed-003.rb +25 -0
- package/dist/packages/crucible/seeds/ruby/seed-004 2.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-004 3.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-004.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-005 2.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-005 3.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-005.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-006 2.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-006 3.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-006.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-007 2.rb +16 -0
- package/dist/packages/crucible/seeds/ruby/seed-007 3.rb +16 -0
- package/dist/packages/crucible/seeds/ruby/seed-007.rb +16 -0
- package/dist/packages/crucible/seeds/ruby/seed-008 2.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-008 3.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-008.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-009 2.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-009 3.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-009.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-010 2.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-010 3.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-010.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-011 2.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-011 3.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-011.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-012 2.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-012 3.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-012.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-013 2.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-013 3.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-013.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-014 2.rb +16 -0
- package/dist/packages/crucible/seeds/ruby/seed-014 3.rb +16 -0
- package/dist/packages/crucible/seeds/ruby/seed-014.rb +16 -0
- package/dist/packages/crucible/seeds/ruby/seed-015 2.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-015 3.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-015.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-016 2.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-016 3.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-016.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-017 2.rb +25 -0
- package/dist/packages/crucible/seeds/ruby/seed-017 3.rb +25 -0
- package/dist/packages/crucible/seeds/ruby/seed-017.rb +25 -0
- package/dist/packages/crucible/seeds/ruby/seed-018 2.rb +23 -0
- package/dist/packages/crucible/seeds/ruby/seed-018 3.rb +23 -0
- package/dist/packages/crucible/seeds/ruby/seed-018.rb +23 -0
- package/dist/packages/crucible/seeds/ruby/seed-019 2.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-019 3.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-019.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-020 2.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-020 3.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-020.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-021 2.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-021 3.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-021.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-022 2.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-022 3.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-022.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-023 2.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-023 3.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-023.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-024 2.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-024 3.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-024.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-025 2.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-025 3.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-025.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-026 2.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-026 3.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-026.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-027 2.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-027 3.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-027.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-028 2.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-028 3.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-028.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-029 2.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-029 3.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-029.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-030 2.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-030 3.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-030.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-031 2.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-031 3.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-031.rb +17 -0
- package/dist/packages/crucible/seeds/ruby/seed-032 2.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-032 3.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-032.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-033 2.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-033 3.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-033.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-034 2.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-034 3.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-034.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-035 2.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-035 3.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-035.rb +19 -0
- package/dist/packages/crucible/seeds/ruby/seed-036 2.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-036 3.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-036.rb +18 -0
- package/dist/packages/crucible/seeds/ruby/seed-037 2.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-037 3.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-037.rb +21 -0
- package/dist/packages/crucible/seeds/ruby/seed-038 2.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-038 3.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-038.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-039 2.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-039 3.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-039.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-040 2.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-040 3.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-040.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-041 2.rb +23 -0
- package/dist/packages/crucible/seeds/ruby/seed-041 3.rb +23 -0
- package/dist/packages/crucible/seeds/ruby/seed-041.rb +23 -0
- package/dist/packages/crucible/seeds/ruby/seed-042 2.rb +25 -0
- package/dist/packages/crucible/seeds/ruby/seed-042 3.rb +25 -0
- package/dist/packages/crucible/seeds/ruby/seed-042.rb +25 -0
- package/dist/packages/crucible/seeds/ruby/seed-043 2.rb +23 -0
- package/dist/packages/crucible/seeds/ruby/seed-043 3.rb +23 -0
- package/dist/packages/crucible/seeds/ruby/seed-043.rb +23 -0
- package/dist/packages/crucible/seeds/ruby/seed-044 2.rb +16 -0
- package/dist/packages/crucible/seeds/ruby/seed-044 3.rb +16 -0
- package/dist/packages/crucible/seeds/ruby/seed-044.rb +16 -0
- package/dist/packages/crucible/seeds/ruby/seed-045 2.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-045 3.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-045.rb +22 -0
- package/dist/packages/crucible/seeds/ruby/seed-046 2.rb +27 -0
- package/dist/packages/crucible/seeds/ruby/seed-046 3.rb +27 -0
- package/dist/packages/crucible/seeds/ruby/seed-046.rb +27 -0
- package/dist/packages/crucible/seeds/ruby/seed-047 2.rb +26 -0
- package/dist/packages/crucible/seeds/ruby/seed-047 3.rb +26 -0
- package/dist/packages/crucible/seeds/ruby/seed-047.rb +26 -0
- package/dist/packages/crucible/seeds/ruby/seed-048 2.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-048 3.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-048.rb +24 -0
- package/dist/packages/crucible/seeds/ruby/seed-049 2.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-049 3.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-049.rb +20 -0
- package/dist/packages/crucible/seeds/ruby/seed-050 2.rb +27 -0
- package/dist/packages/crucible/seeds/ruby/seed-050 3.rb +27 -0
- package/dist/packages/crucible/seeds/ruby/seed-050.rb +27 -0
- package/dist/packages/crucible/seeds/rust/seed-001 2.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-001 3.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-001.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-002 2.rs +20 -0
- package/dist/packages/crucible/seeds/rust/seed-002 3.rs +20 -0
- package/dist/packages/crucible/seeds/rust/seed-002.rs +20 -0
- package/dist/packages/crucible/seeds/rust/seed-003 2.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-003 3.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-003.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-004 2.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-004 3.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-004.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-005 2.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-005 3.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-005.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-006 2.rs +24 -0
- package/dist/packages/crucible/seeds/rust/seed-006 3.rs +24 -0
- package/dist/packages/crucible/seeds/rust/seed-006.rs +24 -0
- package/dist/packages/crucible/seeds/rust/seed-007 2.rs +20 -0
- package/dist/packages/crucible/seeds/rust/seed-007 3.rs +20 -0
- package/dist/packages/crucible/seeds/rust/seed-007.rs +20 -0
- package/dist/packages/crucible/seeds/rust/seed-008 2.rs +19 -0
- package/dist/packages/crucible/seeds/rust/seed-008 3.rs +19 -0
- package/dist/packages/crucible/seeds/rust/seed-008.rs +19 -0
- package/dist/packages/crucible/seeds/rust/seed-009 2.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-009 3.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-009.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-010 2.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-010 3.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-010.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-011 2.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-011 3.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-011.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-012 2.rs +31 -0
- package/dist/packages/crucible/seeds/rust/seed-012 3.rs +31 -0
- package/dist/packages/crucible/seeds/rust/seed-012.rs +31 -0
- package/dist/packages/crucible/seeds/rust/seed-013 2.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-013 3.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-013.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-014 2.rs +30 -0
- package/dist/packages/crucible/seeds/rust/seed-014 3.rs +30 -0
- package/dist/packages/crucible/seeds/rust/seed-014.rs +30 -0
- package/dist/packages/crucible/seeds/rust/seed-015 2.rs +33 -0
- package/dist/packages/crucible/seeds/rust/seed-015 3.rs +33 -0
- package/dist/packages/crucible/seeds/rust/seed-015.rs +33 -0
- package/dist/packages/crucible/seeds/rust/seed-016 2.rs +22 -0
- package/dist/packages/crucible/seeds/rust/seed-016 3.rs +22 -0
- package/dist/packages/crucible/seeds/rust/seed-016.rs +22 -0
- package/dist/packages/crucible/seeds/rust/seed-017 2.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-017 3.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-017.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-018 2.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-018 3.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-018.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-019 2.rs +36 -0
- package/dist/packages/crucible/seeds/rust/seed-019 3.rs +36 -0
- package/dist/packages/crucible/seeds/rust/seed-019.rs +36 -0
- package/dist/packages/crucible/seeds/rust/seed-020 2.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-020 3.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-020.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-021 2.rs +26 -0
- package/dist/packages/crucible/seeds/rust/seed-021 3.rs +26 -0
- package/dist/packages/crucible/seeds/rust/seed-021.rs +26 -0
- package/dist/packages/crucible/seeds/rust/seed-022 2.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-022 3.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-022.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-023 2.rs +22 -0
- package/dist/packages/crucible/seeds/rust/seed-023 3.rs +22 -0
- package/dist/packages/crucible/seeds/rust/seed-023.rs +22 -0
- package/dist/packages/crucible/seeds/rust/seed-024 2.rs +24 -0
- package/dist/packages/crucible/seeds/rust/seed-024 3.rs +24 -0
- package/dist/packages/crucible/seeds/rust/seed-024.rs +24 -0
- package/dist/packages/crucible/seeds/rust/seed-025 2.rs +29 -0
- package/dist/packages/crucible/seeds/rust/seed-025 3.rs +29 -0
- package/dist/packages/crucible/seeds/rust/seed-025.rs +29 -0
- package/dist/packages/crucible/seeds/rust/seed-026 2.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-026 3.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-026.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-027 2.rs +24 -0
- package/dist/packages/crucible/seeds/rust/seed-027 3.rs +24 -0
- package/dist/packages/crucible/seeds/rust/seed-027.rs +24 -0
- package/dist/packages/crucible/seeds/rust/seed-028 2.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-028 3.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-028.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-029 2.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-029 3.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-029.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-030 2.rs +30 -0
- package/dist/packages/crucible/seeds/rust/seed-030 3.rs +30 -0
- package/dist/packages/crucible/seeds/rust/seed-030.rs +30 -0
- package/dist/packages/crucible/seeds/rust/seed-031 2.rs +22 -0
- package/dist/packages/crucible/seeds/rust/seed-031 3.rs +22 -0
- package/dist/packages/crucible/seeds/rust/seed-031.rs +22 -0
- package/dist/packages/crucible/seeds/rust/seed-032 2.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-032 3.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-032.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-033 2.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-033 3.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-033.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-034 2.rs +20 -0
- package/dist/packages/crucible/seeds/rust/seed-034 3.rs +20 -0
- package/dist/packages/crucible/seeds/rust/seed-034.rs +20 -0
- package/dist/packages/crucible/seeds/rust/seed-035 2.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-035 3.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-035.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-036 2.rs +26 -0
- package/dist/packages/crucible/seeds/rust/seed-036 3.rs +26 -0
- package/dist/packages/crucible/seeds/rust/seed-036.rs +26 -0
- package/dist/packages/crucible/seeds/rust/seed-037 2.rs +31 -0
- package/dist/packages/crucible/seeds/rust/seed-037 3.rs +31 -0
- package/dist/packages/crucible/seeds/rust/seed-037.rs +31 -0
- package/dist/packages/crucible/seeds/rust/seed-038 2.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-038 3.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-038.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-039 2.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-039 3.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-039.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-040 2.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-040 3.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-040.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-041 2.rs +32 -0
- package/dist/packages/crucible/seeds/rust/seed-041 3.rs +32 -0
- package/dist/packages/crucible/seeds/rust/seed-041.rs +32 -0
- package/dist/packages/crucible/seeds/rust/seed-042 2.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-042 3.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-042.rs +27 -0
- package/dist/packages/crucible/seeds/rust/seed-043 2.rs +29 -0
- package/dist/packages/crucible/seeds/rust/seed-043 3.rs +29 -0
- package/dist/packages/crucible/seeds/rust/seed-043.rs +29 -0
- package/dist/packages/crucible/seeds/rust/seed-044 2.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-044 3.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-044.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-045 2.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-045 3.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-045.rs +28 -0
- package/dist/packages/crucible/seeds/rust/seed-046 2.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-046 3.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-046.rs +25 -0
- package/dist/packages/crucible/seeds/rust/seed-047 2.rs +34 -0
- package/dist/packages/crucible/seeds/rust/seed-047 3.rs +34 -0
- package/dist/packages/crucible/seeds/rust/seed-047.rs +34 -0
- package/dist/packages/crucible/seeds/rust/seed-048 2.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-048 3.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-048.rs +21 -0
- package/dist/packages/crucible/seeds/rust/seed-049 2.rs +26 -0
- package/dist/packages/crucible/seeds/rust/seed-049 3.rs +26 -0
- package/dist/packages/crucible/seeds/rust/seed-049.rs +26 -0
- package/dist/packages/crucible/seeds/rust/seed-050 2.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-050 3.rs +23 -0
- package/dist/packages/crucible/seeds/rust/seed-050.rs +23 -0
- package/dist/packages/crucible/seeds/ts/seed-001 2.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-001 3.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-001.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-002 2.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-002 3.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-002.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-003 2.ts +28 -0
- package/dist/packages/crucible/seeds/ts/seed-003 3.ts +28 -0
- package/dist/packages/crucible/seeds/ts/seed-003.ts +28 -0
- package/dist/packages/crucible/seeds/ts/seed-004 2.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-004 3.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-004.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-005 2.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-005 3.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-005.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-006 2.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-006 3.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-006.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-007 2.ts +28 -0
- package/dist/packages/crucible/seeds/ts/seed-007 3.ts +28 -0
- package/dist/packages/crucible/seeds/ts/seed-007.ts +28 -0
- package/dist/packages/crucible/seeds/ts/seed-008 2.ts +40 -0
- package/dist/packages/crucible/seeds/ts/seed-008 3.ts +40 -0
- package/dist/packages/crucible/seeds/ts/seed-008.ts +40 -0
- package/dist/packages/crucible/seeds/ts/seed-009 2.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-009 3.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-009.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-010 2.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-010 3.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-010.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-011 2.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-011 3.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-011.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-012 2.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-012 3.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-012.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-013 2.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-013 3.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-013.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-014 2.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-014 3.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-014.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-015 2.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-015 3.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-015.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-016 2.ts +37 -0
- package/dist/packages/crucible/seeds/ts/seed-016 3.ts +37 -0
- package/dist/packages/crucible/seeds/ts/seed-016.ts +37 -0
- package/dist/packages/crucible/seeds/ts/seed-017 2.ts +44 -0
- package/dist/packages/crucible/seeds/ts/seed-017 3.ts +44 -0
- package/dist/packages/crucible/seeds/ts/seed-017.ts +44 -0
- package/dist/packages/crucible/seeds/ts/seed-018 2.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-018 3.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-018.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-019 2.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-019 3.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-019.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-020 2.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-020 3.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-020.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-021 2.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-021 3.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-021.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-022 2.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-022 3.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-022.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-023 2.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-023 3.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-023.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-024 2.ts +35 -0
- package/dist/packages/crucible/seeds/ts/seed-024 3.ts +35 -0
- package/dist/packages/crucible/seeds/ts/seed-024.ts +35 -0
- package/dist/packages/crucible/seeds/ts/seed-025 2.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-025 3.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-025.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-026 2.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-026 3.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-026.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-027 2.ts +30 -0
- package/dist/packages/crucible/seeds/ts/seed-027 3.ts +30 -0
- package/dist/packages/crucible/seeds/ts/seed-027.ts +30 -0
- package/dist/packages/crucible/seeds/ts/seed-028 2.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-028 3.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-028.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-029 2.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-029 3.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-029.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-030 2.ts +37 -0
- package/dist/packages/crucible/seeds/ts/seed-030 3.ts +37 -0
- package/dist/packages/crucible/seeds/ts/seed-030.ts +37 -0
- package/dist/packages/crucible/seeds/ts/seed-031 2.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-031 3.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-031.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-032 2.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-032 3.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-032.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-033 2.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-033 3.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-033.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-034 2.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-034 3.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-034.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-035 2.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-035 3.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-035.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-036 2.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-036 3.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-036.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-037 2.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-037 3.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-037.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-038 2.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-038 3.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-038.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-039 2.ts +35 -0
- package/dist/packages/crucible/seeds/ts/seed-039 3.ts +35 -0
- package/dist/packages/crucible/seeds/ts/seed-039.ts +35 -0
- package/dist/packages/crucible/seeds/ts/seed-040 2.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-040 3.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-040.ts +31 -0
- package/dist/packages/crucible/seeds/ts/seed-041 2.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-041 3.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-041.ts +29 -0
- package/dist/packages/crucible/seeds/ts/seed-042 2.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-042 3.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-042.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-043 2.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-043 3.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-043.ts +32 -0
- package/dist/packages/crucible/seeds/ts/seed-044 2.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-044 3.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-044.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-045 2.ts +39 -0
- package/dist/packages/crucible/seeds/ts/seed-045 3.ts +39 -0
- package/dist/packages/crucible/seeds/ts/seed-045.ts +39 -0
- package/dist/packages/crucible/seeds/ts/seed-046 2.ts +41 -0
- package/dist/packages/crucible/seeds/ts/seed-046 3.ts +41 -0
- package/dist/packages/crucible/seeds/ts/seed-046.ts +41 -0
- package/dist/packages/crucible/seeds/ts/seed-047 2.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-047 3.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-047.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-048 2.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-048 3.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-048.ts +33 -0
- package/dist/packages/crucible/seeds/ts/seed-049 2.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-049 3.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-049.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-050 2.ts +41 -0
- package/dist/packages/crucible/seeds/ts/seed-050 3.ts +41 -0
- package/dist/packages/crucible/seeds/ts/seed-050.ts +41 -0
- package/dist/packages/crucible/seeds/ts/seed-051 2.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-051 3.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-051.ts +34 -0
- package/dist/packages/crucible/seeds/ts/seed-052 2.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-052 3.ts +36 -0
- package/dist/packages/crucible/seeds/ts/seed-052.ts +36 -0
- package/dist/packages/crucible/self-attack.js +1 -0
- package/dist/packages/scanner/ai-confidence-scorer.js +1 -1
- package/dist/packages/scanner/alert-manager.js +1 -1
- package/dist/packages/scanner/ast-analyzer.js +1 -0
- package/dist/packages/scanner/baseline-manager.js +1 -1
- package/dist/packages/scanner/cfg-analyzer.js +1 -0
- package/dist/packages/scanner/code-scanner.js +1 -1
- package/dist/packages/scanner/dependency-graph.js +1 -1
- package/dist/packages/scanner/drift-detector.js +1 -1
- package/dist/packages/scanner/executive-report.js +1 -1
- package/dist/packages/scanner/export-verifier.js +1 -0
- package/dist/packages/scanner/file-collector.js +1 -1
- package/dist/packages/scanner/file-watcher.js +1 -1
- package/dist/packages/scanner/ghost-code-detector.js +1 -1
- package/dist/packages/scanner/go-ast-analyzer.js +1 -0
- package/dist/packages/scanner/hallucination-detector.js +1 -1
- package/dist/packages/scanner/html-report.js +1 -1
- package/dist/packages/scanner/license-manager.js +1 -1
- package/dist/packages/scanner/master-health-checker.js +1 -1
- package/dist/packages/scanner/monitor-service.js +1 -1
- package/dist/packages/scanner/monitor-store.js +1 -1
- package/dist/packages/scanner/python-ast-analyzer.js +1 -0
- package/dist/packages/scanner/secret-scanner.js +1 -1
- package/dist/packages/scanner/sentinel-core.js +1 -1
- package/dist/packages/scanner/taint-tracker.js +1 -0
- package/dist/packages/scanner/tech-debt-analyzer.js +1 -1
- package/dist/packages/scanner/tech-debt-cost.js +1 -1
- package/dist/packages/scanner/widget-generator.js +1 -1
- package/package.json +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
const fs=require("fs"),path=require("path");class HtmlReportGenerator{constructor(e={}){this.config={rootPath:e.rootPath||process.cwd(),outputDir:e.outputDir||process.cwd(),...e}}generate(e,n={},r={}){const t=(new Date).toISOString(),a=r.projectName||path.basename(this.config.rootPath);return`<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>Thuban Report — ${this._esc(a)}</title>\n<style>\n${this._css()}\n</style>\n</head>\n<body>\n<div class="container">\n ${this._header(a,t,e)}\n ${this._overallScore(e)}\n ${this._categoryCards(e)}\n ${this._motherCodeSection(e)}\n ${this._topIssues(e)}\n ${this._fixableItems(e)}\n ${this._dependencySection(n)}\n ${this._recommendations(e)}\n ${this._footer(e,r)}\n</div>\n<script>\n${this._js()}\n<\/script>\n</body>\n</html>`}async writeReport(e,n={},r={}){const t=this.generate(e,n,r),a=`thuban-report-${(new Date).toISOString().slice(0,10)}.html`,o=path.join(this.config.outputDir,a);return fs.writeFileSync(o,t,"utf-8"),o}_header(e,n,r){return`\n <header class="header">\n <div class="header-left">\n <div class="logo">\n <svg width="32" height="32" viewBox="0 0 32 32" fill="none">\n <polygon points="16,2 30,28 2,28" stroke="#00f6ff" stroke-width="2" fill="none"/>\n <polygon points="16,8 25,25 7,25" stroke="#00f6ff" stroke-width="1" fill="rgba(0,246,255,0.1)"/>\n <circle cx="16" cy="18" r="3" fill="#00f6ff"/>\n </svg>\n <h1>THUBAN</h1>\n </div>\n <span class="subtitle">Code Health Report</span>\n </div>\n <div class="header-right">\n <div class="meta-item"><span class="meta-label">Project</span><span class="meta-value">${this._esc(e)}</span></div>\n <div class="meta-item"><span class="meta-label">Generated</span><span class="meta-value">${new Date(n).toLocaleString()}</span></div>\n <div class="meta-item"><span class="meta-label">Files</span><span class="meta-value">${r.stats.totalFiles}</span></div>\n <div class="meta-item"><span class="meta-label">Scan Time</span><span class="meta-value">${r.stats.scanTime}ms</span></div>\n </div>\n </header>`}_overallScore(e){const n=e.scores.overall,r=n>=90?"A":n>=80?"B":n>=70?"C":n>=60?"D":"F",t=n>=80?"grade-good":n>=60?"grade-warn":"grade-bad",a=2*Math.PI*54,o=a-n/100*a;return`\n <section class="score-section">\n <div class="score-ring-container">\n <svg class="score-ring" viewBox="0 0 120 120">\n <circle cx="60" cy="60" r="54" stroke="#1a1a2e" stroke-width="8" fill="none"/>\n <circle cx="60" cy="60" r="54" stroke="${this._scoreColor(n)}" stroke-width="8" fill="none"\n stroke-dasharray="${a}" stroke-dashoffset="${o}"\n stroke-linecap="round" transform="rotate(-90 60 60)" class="score-progress"/>\n </svg>\n <div class="score-inner">\n <span class="score-number">${n}</span>\n <span class="score-label">/ 100</span>\n </div>\n </div>\n <div class="score-details">\n <div class="grade ${t}">Grade ${r}</div>\n <div class="score-summary">\n <div class="stat"><span class="stat-num">${e.stats.totalIssues}</span><span class="stat-label">Issues</span></div>\n <div class="stat"><span class="stat-num">${e.stats.fixableCount}</span><span class="stat-label">Auto-fixable</span></div>\n <div class="stat"><span class="stat-num">${e.stats.manualCount}</span><span class="stat-label">Manual</span></div>\n <div class="stat"><span class="stat-num">${e.stats.estimatedFixTime}</span><span class="stat-label">Est. Fix Time</span></div>\n </div>\n </div>\n </section>`}_categoryCards(e){return`\n <section class="categories-section">\n <h2>Category Breakdown</h2>\n <div class="category-grid">${[{key:"security",icon:"🛡️",label:"Security"},{key:"architecture",icon:"🏗️",label:"Architecture"},{key:"codeQuality",icon:"📝",label:"Code Quality"},{key:"motherCode",icon:"🧬",label:"Mother Code"},{key:"dependencies",icon:"🔗",label:"Dependencies"}].map(n=>{const r=e.scores[n.key]||0,t=e.categories[n.key]||{},a=t.issues?.length||0;return`\n <div class="category-card">\n <div class="card-header">\n <span class="card-icon">${n.icon}</span>\n <span class="card-label">${n.label}</span>\n </div>\n <div class="card-score" style="color: ${this._scoreColor(r)}">${r}%</div>\n <div class="card-bar">\n <div class="card-bar-fill" style="width: ${r}%; background: ${this._scoreColor(r)}"></div>\n </div>\n <div class="card-issues">${a} issue${1!==a?"s":""}</div>\n </div>`}).join("")}</div>\n </section>`}_motherCodeSection(e){const n=e.categories.motherCode;if(!n)return"";const r=n.coverage||0;return`\n <section class="mother-code-section">\n <h2>🧬 Mother Code Coverage</h2>\n <div class="mc-grid">\n <div class="mc-stat">\n <div class="mc-circle" style="background: conic-gradient(${this._scoreColor(r)} ${3.6*r}deg, #1a1a2e ${3.6*r}deg)">\n <span>${r}%</span>\n </div>\n <span class="mc-label">Coverage</span>\n </div>\n <div class="mc-details">\n <div class="mc-row"><span class="mc-dot dot-green"></span> Annotated: <strong>${n.annotated||0}</strong> files</div>\n <div class="mc-row"><span class="mc-dot dot-yellow"></span> Missing: <strong>${n.missing||0}</strong> files</div>\n <div class="mc-row"><span class="mc-dot dot-red"></span> Drifted: <strong>${n.drifted||0}</strong> files</div>\n </div>\n <div class="mc-action">\n <p>Inject Mother Code DNA into all files:</p>\n <code>thuban inject [path] --fix</code>\n </div>\n </div>\n </section>`}_topIssues(e){const n=[...e.manual||[],...e.fixable||[]].filter(e=>"info"!==e.severity).sort((e,n)=>{const r={critical:0,high:1,error:1,medium:2,warning:2,low:3};return(r[e.severity]||4)-(r[n.severity]||4)}).slice(0,20);return 0===n.length?"":`\n <section class="issues-section">\n <h2>Top Issues</h2>\n <table class="issues-table">\n <thead><tr><th>Severity</th><th>File</th><th>Issue</th><th>Fix</th></tr></thead>\n <tbody>${n.map(e=>`\n <tr>\n <td><span class="sev sev-${e.severity}">${(e.severity||"").toUpperCase()}</span></td>\n <td class="file-cell">${this._esc(e.file||"")}</td>\n <td>${this._esc(e.message||e.type||"")}</td>\n <td>${e.fixable?'<span class="fixable-badge">Auto-fix</span>':'<span class="manual-badge">Manual</span>'}</td>\n </tr>`).join("")}</tbody>\n </table>\n </section>`}_fixableItems(e){if(!e.fixable||0===e.fixable.length)return"";const n={};for(const r of e.fixable){const e=r.fixAction||"unknown";n[e]||(n[e]={count:0,label:e}),n[e].count++}const r={inject_widget:"Inject Mother Code DNA",regenerate_widget:"Update stale annotations",remove_console_log:"Remove debug console.logs",extract_to_env:"Extract hardcoded secrets to .env",flag_for_removal:"Remove orphan files",break_circular:"Break circular dependencies"},t=Object.entries(n).sort((e,n)=>n[1].count-e[1].count).map(([e,n])=>`\n <div class="fix-item">\n <span class="fix-count">${n.count}</span>\n <span class="fix-label">${r[e]||e}</span>\n </div>`).join("");return`\n <section class="fixable-section">\n <h2>Auto-Fixable Tech Debt</h2>\n <p class="fix-intro">Run <code>thuban fix [path] --fix</code> to resolve ${e.stats.fixableCount} items automatically:</p>\n <div class="fix-grid">${t}</div>\n </section>`}_dependencySection(e){if(!e||!e.circular?.length&&!e.orphans?.length&&!e.critical?.length)return"";let n='<section class="deps-section"><h2>Dependency Analysis</h2><div class="deps-grid">';return e.circular?.length>0&&(n+=`\n <div class="dep-card dep-danger">\n <h3>Circular Dependencies</h3>\n <span class="dep-count">${e.circular.length}</span>\n <ul>${e.circular.slice(0,5).map(e=>`<li>${this._esc(Array.isArray(e)?e.join(" → "):String(e))}</li>`).join("")}</ul>\n </div>`),e.orphans?.length>0&&(n+=`\n <div class="dep-card dep-warn">\n <h3>Orphan Files</h3>\n <span class="dep-count">${e.orphans.length}</span>\n <p>Files with no imports and no consumers</p>\n </div>`),e.critical?.length>0&&(n+=`\n <div class="dep-card dep-info">\n <h3>Critical Files</h3>\n <span class="dep-count">${e.critical.length}</span>\n <p>Files with the most dependents — high blast radius</p>\n </div>`),n+="</div></section>",n}_recommendations(e){const n=[];return e.scores.security<80&&n.push({priority:"critical",text:"Fix security issues immediately — hardcoded secrets or injection risks detected"}),(e.categories.architecture?.issues||[]).filter(e=>"circular_dependency"===e.type).length>0&&n.push({priority:"high",text:"Resolve circular dependencies to prevent build fragility"}),(e.categories.motherCode?.coverage||0)<50&&n.push({priority:"medium",text:`Run <code>thuban inject --fix</code> to raise Mother Code coverage from ${e.categories.motherCode?.coverage||0}% to 100%`}),e.stats.fixableCount>0&&n.push({priority:"medium",text:`Run <code>thuban fix --fix</code> to auto-resolve ${e.stats.fixableCount} items`}),(e.categories.motherCode?.drifted||0)>0&&n.push({priority:"low",text:`${e.categories.motherCode.drifted} files have drifted from their Mother Code annotations — regenerate with <code>thuban fix --fix</code>`}),0===n.length?'\n <section class="recs-section">\n <h2>Recommendations</h2>\n <div class="rec-clean">Codebase is in excellent health. No critical actions needed.</div>\n </section>':`\n <section class="recs-section">\n <h2>Recommendations</h2>\n ${n.map(e=>`\n <div class="rec-item rec-${e.priority}">\n <span class="rec-priority">${e.priority.toUpperCase()}</span>\n <span class="rec-text">${e.text}</span>\n </div>`).join("")}\n </section>`}_footer(e,n){return`\n <footer class="footer">\n <div class="footer-brand">\n <span>Generated by <strong>Thuban</strong> Code Health Engine v0.2.0</span>\n <span class="footer-link">thuban.dev</span>\n </div>\n <div class="footer-meta">\n <span>${e.stats.totalFiles} files scanned in ${e.stats.scanTime}ms</span>\n </div>\n </footer>`}_css(){return"\n:root {\n --bg: #0a0a1a;\n --surface: #12122a;\n --surface2: #1a1a3e;\n --border: #2a2a5a;\n --text: #e0e0f0;\n --text-dim: #8888aa;\n --cyan: #00f6ff;\n --green: #00ff88;\n --yellow: #ffd93d;\n --red: #ff4444;\n --orange: #ff9500;\n --purple: #a855f7;\n --font: 'Segoe UI', system-ui, -apple-system, sans-serif;\n --mono: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;\n}\n\n* { margin: 0; padding: 0; box-sizing: border-box; }\n\nbody {\n background: var(--bg);\n color: var(--text);\n font-family: var(--font);\n line-height: 1.6;\n min-height: 100vh;\n}\n\n.container {\n max-width: 1200px;\n margin: 0 auto;\n padding: 2rem;\n}\n\n/* Header */\n.header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem 2rem;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 12px;\n margin-bottom: 2rem;\n}\n.header-left { display: flex; align-items: center; gap: 1.5rem; }\n.logo { display: flex; align-items: center; gap: 0.75rem; }\n.logo h1 { font-size: 1.5rem; letter-spacing: 0.3em; color: var(--cyan); font-weight: 300; }\n.subtitle { color: var(--text-dim); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.1em; }\n.header-right { display: flex; gap: 2rem; }\n.meta-item { display: flex; flex-direction: column; align-items: flex-end; }\n.meta-label { font-size: 0.7rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }\n.meta-value { font-size: 0.9rem; color: var(--text); font-family: var(--mono); }\n\n/* Score Section */\n.score-section {\n display: flex;\n align-items: center;\n gap: 3rem;\n padding: 2rem;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 12px;\n margin-bottom: 2rem;\n}\n.score-ring-container { position: relative; width: 140px; height: 140px; flex-shrink: 0; }\n.score-ring { width: 100%; height: 100%; }\n.score-progress { transition: stroke-dashoffset 1s ease-out; }\n.score-inner {\n position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\n text-align: center;\n}\n.score-number { font-size: 2.5rem; font-weight: 700; color: var(--text); display: block; line-height: 1; }\n.score-label { font-size: 0.8rem; color: var(--text-dim); }\n.score-details { flex: 1; }\n.grade {\n font-size: 1.8rem; font-weight: 700; margin-bottom: 1rem;\n letter-spacing: 0.05em;\n}\n.grade-good { color: var(--green); }\n.grade-warn { color: var(--yellow); }\n.grade-bad { color: var(--red); }\n.score-summary { display: flex; gap: 2rem; }\n.stat { display: flex; flex-direction: column; }\n.stat-num { font-size: 1.4rem; font-weight: 600; font-family: var(--mono); }\n.stat-label { font-size: 0.75rem; color: var(--text-dim); text-transform: uppercase; }\n\n/* Category Cards */\n.categories-section { margin-bottom: 2rem; }\n.categories-section h2 { margin-bottom: 1rem; font-size: 1.1rem; color: var(--cyan); text-transform: uppercase; letter-spacing: 0.1em; }\n.category-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 1rem; }\n.category-card {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 10px;\n padding: 1.25rem;\n text-align: center;\n transition: border-color 0.2s;\n}\n.category-card:hover { border-color: var(--cyan); }\n.card-header { display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-bottom: 0.75rem; }\n.card-icon { font-size: 1.2rem; }\n.card-label { font-size: 0.85rem; color: var(--text-dim); }\n.card-score { font-size: 2rem; font-weight: 700; font-family: var(--mono); margin-bottom: 0.5rem; }\n.card-bar { height: 4px; background: var(--surface2); border-radius: 2px; overflow: hidden; margin-bottom: 0.5rem; }\n.card-bar-fill { height: 100%; border-radius: 2px; transition: width 0.8s ease-out; }\n.card-issues { font-size: 0.75rem; color: var(--text-dim); }\n\n/* Mother Code */\n.mother-code-section {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 2rem;\n margin-bottom: 2rem;\n}\n.mother-code-section h2 { margin-bottom: 1.5rem; font-size: 1.1rem; color: var(--cyan); }\n.mc-grid { display: grid; grid-template-columns: 160px 1fr 1fr; gap: 2rem; align-items: center; }\n.mc-circle {\n width: 120px; height: 120px; border-radius: 50%;\n display: flex; align-items: center; justify-content: center;\n margin: 0 auto;\n}\n.mc-circle span { background: var(--bg); width: 90px; height: 90px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; font-weight: 700; font-family: var(--mono); }\n.mc-label { text-align: center; margin-top: 0.5rem; color: var(--text-dim); font-size: 0.85rem; }\n.mc-row { padding: 0.4rem 0; display: flex; align-items: center; gap: 0.5rem; }\n.mc-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }\n.dot-green { background: var(--green); }\n.dot-yellow { background: var(--yellow); }\n.dot-red { background: var(--red); }\n.mc-action code {\n display: inline-block; background: var(--bg); padding: 0.4rem 0.8rem;\n border-radius: 6px; font-family: var(--mono); color: var(--cyan); margin-top: 0.5rem;\n border: 1px solid var(--border);\n}\n\n/* Issues Table */\n.issues-section { margin-bottom: 2rem; }\n.issues-section h2 { margin-bottom: 1rem; font-size: 1.1rem; color: var(--cyan); text-transform: uppercase; letter-spacing: 0.1em; }\n.issues-table {\n width: 100%; border-collapse: collapse;\n background: var(--surface); border: 1px solid var(--border); border-radius: 10px;\n overflow: hidden;\n}\n.issues-table th {\n text-align: left; padding: 0.75rem 1rem; background: var(--surface2);\n font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim);\n border-bottom: 1px solid var(--border);\n}\n.issues-table td { padding: 0.6rem 1rem; border-bottom: 1px solid rgba(42,42,90,0.5); font-size: 0.85rem; }\n.issues-table tr:last-child td { border-bottom: none; }\n.issues-table tr:hover { background: rgba(0,246,255,0.03); }\n.file-cell { font-family: var(--mono); font-size: 0.8rem; color: var(--cyan); max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.sev {\n display: inline-block; padding: 0.15rem 0.5rem; border-radius: 4px;\n font-size: 0.7rem; font-weight: 600; letter-spacing: 0.05em;\n}\n.sev-critical { background: rgba(255,68,68,0.2); color: var(--red); }\n.sev-high, .sev-error { background: rgba(255,149,0,0.2); color: var(--orange); }\n.sev-medium, .sev-warning { background: rgba(255,217,61,0.2); color: var(--yellow); }\n.sev-low, .sev-info { background: rgba(136,136,170,0.15); color: var(--text-dim); }\n.fixable-badge { background: rgba(0,255,136,0.15); color: var(--green); padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.7rem; }\n.manual-badge { background: rgba(255,217,61,0.15); color: var(--yellow); padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.7rem; }\n\n/* Fixable Section */\n.fixable-section {\n background: var(--surface); border: 1px solid var(--border); border-radius: 12px;\n padding: 2rem; margin-bottom: 2rem;\n}\n.fixable-section h2 { margin-bottom: 0.5rem; font-size: 1.1rem; color: var(--green); }\n.fix-intro { color: var(--text-dim); font-size: 0.9rem; margin-bottom: 1.5rem; }\n.fix-intro code { background: var(--bg); padding: 0.2rem 0.5rem; border-radius: 4px; font-family: var(--mono); color: var(--cyan); border: 1px solid var(--border); }\n.fix-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }\n.fix-item {\n background: var(--bg); border: 1px solid var(--border); border-radius: 8px;\n padding: 1rem; display: flex; align-items: center; gap: 1rem;\n}\n.fix-count { font-size: 1.8rem; font-weight: 700; color: var(--green); font-family: var(--mono); min-width: 40px; }\n.fix-label { font-size: 0.85rem; color: var(--text); }\n\n/* Dependencies */\n.deps-section { margin-bottom: 2rem; }\n.deps-section h2 { margin-bottom: 1rem; font-size: 1.1rem; color: var(--cyan); text-transform: uppercase; letter-spacing: 0.1em; }\n.deps-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }\n.dep-card {\n background: var(--surface); border-radius: 10px; padding: 1.5rem;\n border: 1px solid var(--border);\n}\n.dep-danger { border-left: 3px solid var(--red); }\n.dep-warn { border-left: 3px solid var(--yellow); }\n.dep-info { border-left: 3px solid var(--cyan); }\n.dep-card h3 { font-size: 0.9rem; margin-bottom: 0.5rem; }\n.dep-count { font-size: 2rem; font-weight: 700; font-family: var(--mono); color: var(--text); display: block; margin-bottom: 0.5rem; }\n.dep-card ul { list-style: none; font-size: 0.8rem; color: var(--text-dim); font-family: var(--mono); }\n.dep-card li { padding: 0.2rem 0; }\n.dep-card p { font-size: 0.8rem; color: var(--text-dim); }\n\n/* Recommendations */\n.recs-section { margin-bottom: 2rem; }\n.recs-section h2 { margin-bottom: 1rem; font-size: 1.1rem; color: var(--cyan); text-transform: uppercase; letter-spacing: 0.1em; }\n.rec-item {\n display: flex; align-items: center; gap: 1rem;\n background: var(--surface); border: 1px solid var(--border); border-radius: 8px;\n padding: 1rem 1.5rem; margin-bottom: 0.5rem;\n}\n.rec-critical { border-left: 3px solid var(--red); }\n.rec-high { border-left: 3px solid var(--orange); }\n.rec-medium { border-left: 3px solid var(--yellow); }\n.rec-low { border-left: 3px solid var(--text-dim); }\n.rec-priority {\n font-size: 0.65rem; font-weight: 700; letter-spacing: 0.1em;\n padding: 0.2rem 0.5rem; border-radius: 4px; min-width: 70px; text-align: center;\n}\n.rec-critical .rec-priority { background: rgba(255,68,68,0.2); color: var(--red); }\n.rec-high .rec-priority { background: rgba(255,149,0,0.2); color: var(--orange); }\n.rec-medium .rec-priority { background: rgba(255,217,61,0.2); color: var(--yellow); }\n.rec-low .rec-priority { background: rgba(136,136,170,0.15); color: var(--text-dim); }\n.rec-text { font-size: 0.9rem; }\n.rec-text code { background: var(--bg); padding: 0.15rem 0.4rem; border-radius: 4px; font-family: var(--mono); color: var(--cyan); font-size: 0.8rem; }\n.rec-clean { background: var(--surface); border: 1px solid var(--green); border-radius: 8px; padding: 1.5rem; text-align: center; color: var(--green); }\n\n/* Footer */\n.footer {\n display: flex; justify-content: space-between; align-items: center;\n padding: 1.5rem 2rem; border-top: 1px solid var(--border);\n color: var(--text-dim); font-size: 0.8rem; margin-top: 2rem;\n}\n.footer-brand { display: flex; align-items: center; gap: 1.5rem; }\n.footer-brand strong { color: var(--cyan); }\n.footer-link { color: var(--cyan); }\n\n/* Responsive */\n@media (max-width: 900px) {\n .category-grid { grid-template-columns: repeat(2, 1fr); }\n .fix-grid { grid-template-columns: repeat(2, 1fr); }\n .mc-grid { grid-template-columns: 1fr; }\n .header { flex-direction: column; gap: 1rem; }\n .score-section { flex-direction: column; text-align: center; }\n .score-summary { justify-content: center; }\n}\n@media (max-width: 600px) {\n .category-grid { grid-template-columns: 1fr; }\n .fix-grid { grid-template-columns: 1fr; }\n .deps-grid { grid-template-columns: 1fr; }\n}\n\n/* Print */\n@media print {\n body { background: #fff; color: #111; }\n .container { max-width: 100%; }\n .header, .category-card, .mother-code-section, .fixable-section, .issues-section, .dep-card, .rec-item {\n background: #fafafa; border-color: #ddd;\n }\n}\n"}_js(){return"\n// Animate score ring on load\ndocument.addEventListener('DOMContentLoaded', () => {\n const progress = document.querySelector('.score-progress');\n if (progress) {\n const final = progress.style.strokeDashoffset || progress.getAttribute('stroke-dashoffset');\n progress.style.strokeDashoffset = progress.getAttribute('stroke-dasharray');\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n progress.style.strokeDashoffset = final;\n });\n });\n }\n});\n"}_scoreColor(e){return e>=80?"#00ff88":e>=60?"#ffd93d":"#ff4444"}_esc(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}}module.exports=HtmlReportGenerator;
|
|
1
|
+
const fs=require("fs"),path=require("path");class HtmlReportGenerator{constructor(e={}){this.config={rootPath:e.rootPath||process.cwd(),outputDir:e.outputDir||process.cwd(),...e}}generate(e,n={},r={}){const t=(new Date).toISOString(),a=r.projectName||path.basename(this.config.rootPath);return`<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>Thuban Report — ${this._esc(a)}</title>\n<style>\n${this._css()}\n</style>\n</head>\n<body>\n<div class="container">\n ${this._header(a,t,e)}\n ${this._overallScore(e)}\n ${this._categoryCards(e)}\n ${this._motherCodeSection(e)}\n ${this._topIssues(e)}\n ${this._fixableItems(e)}\n ${this._dependencySection(n)}\n ${this._recommendations(e)}\n ${this._footer(e,r)}\n</div>\n<script>\n${this._js()}\n<\/script>\n</body>\n</html>`}async writeReport(e,n={},r={}){const t=this.generate(e,n,r),a=`thuban-report-${(new Date).toISOString().slice(0,10)}.html`,o=path.resolve(this.config.rootPath),s=path.resolve(this.config.outputDir);if(s!==o&&!s.startsWith(o+path.sep))throw new Error(`[HtmlReportGenerator] outputDir "${s}" escapes rootPath "${o}" — refusing to write`);const i=path.join(s,a);return fs.writeFileSync(i,t,"utf-8"),i}_header(e,n,r){return`\n <header class="header">\n <div class="header-left">\n <div class="logo">\n <svg width="32" height="32" viewBox="0 0 32 32" fill="none">\n <polygon points="16,2 30,28 2,28" stroke="#00f6ff" stroke-width="2" fill="none"/>\n <polygon points="16,8 25,25 7,25" stroke="#00f6ff" stroke-width="1" fill="rgba(0,246,255,0.1)"/>\n <circle cx="16" cy="18" r="3" fill="#00f6ff"/>\n </svg>\n <h1>THUBAN</h1>\n </div>\n <span class="subtitle">Code Health Report</span>\n </div>\n <div class="header-right">\n <div class="meta-item"><span class="meta-label">Project</span><span class="meta-value">${this._esc(e)}</span></div>\n <div class="meta-item"><span class="meta-label">Generated</span><span class="meta-value">${new Date(n).toLocaleString()}</span></div>\n <div class="meta-item"><span class="meta-label">Files</span><span class="meta-value">${r.stats.totalFiles}</span></div>\n <div class="meta-item"><span class="meta-label">Scan Time</span><span class="meta-value">${r.stats.scanTime}ms</span></div>\n </div>\n </header>`}_overallScore(e){const n=e.scores.overall,r=n>=90?"A":n>=80?"B":n>=70?"C":n>=60?"D":"F",t=n>=80?"grade-good":n>=60?"grade-warn":"grade-bad",a=2*Math.PI*54,o=a-n/100*a;return`\n <section class="score-section">\n <div class="score-ring-container">\n <svg class="score-ring" viewBox="0 0 120 120">\n <circle cx="60" cy="60" r="54" stroke="#1a1a2e" stroke-width="8" fill="none"/>\n <circle cx="60" cy="60" r="54" stroke="${this._scoreColor(n)}" stroke-width="8" fill="none"\n stroke-dasharray="${a}" stroke-dashoffset="${o}"\n stroke-linecap="round" transform="rotate(-90 60 60)" class="score-progress"/>\n </svg>\n <div class="score-inner">\n <span class="score-number">${n}</span>\n <span class="score-label">/ 100</span>\n </div>\n </div>\n <div class="score-details">\n <div class="grade ${t}">Grade ${r}</div>\n <div class="score-summary">\n <div class="stat"><span class="stat-num">${e.stats.totalIssues}</span><span class="stat-label">Issues</span></div>\n <div class="stat"><span class="stat-num">${e.stats.fixableCount}</span><span class="stat-label">Auto-fixable</span></div>\n <div class="stat"><span class="stat-num">${e.stats.manualCount}</span><span class="stat-label">Manual</span></div>\n <div class="stat"><span class="stat-num">${e.stats.estimatedFixTime}</span><span class="stat-label">Est. Fix Time</span></div>\n </div>\n </div>\n </section>`}_categoryCards(e){return`\n <section class="categories-section">\n <h2>Category Breakdown</h2>\n <div class="category-grid">${[{key:"security",icon:"🛡️",label:"Security"},{key:"architecture",icon:"🏗️",label:"Architecture"},{key:"codeQuality",icon:"📝",label:"Code Quality"},{key:"motherCode",icon:"🧬",label:"Mother Code"},{key:"dependencies",icon:"🔗",label:"Dependencies"}].map(n=>{const r=e.scores[n.key]||0,t=e.categories[n.key]||{},a=t.issues?.length||0;return`\n <div class="category-card">\n <div class="card-header">\n <span class="card-icon">${n.icon}</span>\n <span class="card-label">${n.label}</span>\n </div>\n <div class="card-score" style="color: ${this._scoreColor(r)}">${r}%</div>\n <div class="card-bar">\n <div class="card-bar-fill" style="width: ${r}%; background: ${this._scoreColor(r)}"></div>\n </div>\n <div class="card-issues">${a} issue${1!==a?"s":""}</div>\n </div>`}).join("")}</div>\n </section>`}_motherCodeSection(e){const n=e.categories.motherCode;if(!n)return"";const r=n.coverage||0;return`\n <section class="mother-code-section">\n <h2>🧬 Mother Code Coverage</h2>\n <div class="mc-grid">\n <div class="mc-stat">\n <div class="mc-circle" style="background: conic-gradient(${this._scoreColor(r)} ${3.6*r}deg, #1a1a2e ${3.6*r}deg)">\n <span>${r}%</span>\n </div>\n <span class="mc-label">Coverage</span>\n </div>\n <div class="mc-details">\n <div class="mc-row"><span class="mc-dot dot-green"></span> Annotated: <strong>${n.annotated||0}</strong> files</div>\n <div class="mc-row"><span class="mc-dot dot-yellow"></span> Missing: <strong>${n.missing||0}</strong> files</div>\n <div class="mc-row"><span class="mc-dot dot-red"></span> Drifted: <strong>${n.drifted||0}</strong> files</div>\n </div>\n <div class="mc-action">\n <p>Inject Mother Code DNA into all files:</p>\n <code>thuban inject [path] --fix</code>\n </div>\n </div>\n </section>`}_topIssues(e){const n=[...e.manual||[],...e.fixable||[]].filter(e=>"info"!==e.severity).sort((e,n)=>{const r={critical:0,high:1,error:1,medium:2,warning:2,low:3};return(r[e.severity]||4)-(r[n.severity]||4)}).slice(0,20);return 0===n.length?"":`\n <section class="issues-section">\n <h2>Top Issues</h2>\n <table class="issues-table">\n <thead><tr><th>Severity</th><th>File</th><th>Issue</th><th>Fix</th></tr></thead>\n <tbody>${n.map(e=>`\n <tr>\n <td><span class="sev sev-${e.severity}">${(e.severity||"").toUpperCase()}</span></td>\n <td class="file-cell">${this._esc(e.file||"")}</td>\n <td>${this._esc(e.message||e.type||"")}</td>\n <td>${e.fixable?'<span class="fixable-badge">Auto-fix</span>':'<span class="manual-badge">Manual</span>'}</td>\n </tr>`).join("")}</tbody>\n </table>\n </section>`}_fixableItems(e){if(!e.fixable||0===e.fixable.length)return"";const n={};for(const r of e.fixable){const e=r.fixAction||"unknown";n[e]||(n[e]={count:0,label:e}),n[e].count++}const r={inject_widget:"Inject Mother Code DNA",regenerate_widget:"Update stale annotations",remove_console_log:"Remove debug console.logs",extract_to_env:"Extract hardcoded secrets to .env",flag_for_removal:"Remove orphan files",break_circular:"Break circular dependencies"},t=Object.entries(n).sort((e,n)=>n[1].count-e[1].count).map(([e,n])=>`\n <div class="fix-item">\n <span class="fix-count">${n.count}</span>\n <span class="fix-label">${r[e]||this._esc(e)}</span>\n </div>`).join("");return`\n <section class="fixable-section">\n <h2>Auto-Fixable Tech Debt</h2>\n <p class="fix-intro">Run <code>thuban fix [path] --fix</code> to resolve ${e.stats.fixableCount} items automatically:</p>\n <div class="fix-grid">${t}</div>\n </section>`}_dependencySection(e){if(!e||!e.circular?.length&&!e.orphans?.length&&!e.critical?.length)return"";let n='<section class="deps-section"><h2>Dependency Analysis</h2><div class="deps-grid">';return e.circular?.length>0&&(n+=`\n <div class="dep-card dep-danger">\n <h3>Circular Dependencies</h3>\n <span class="dep-count">${e.circular.length}</span>\n <ul>${e.circular.slice(0,5).map(e=>`<li>${this._esc(Array.isArray(e)?e.join(" → "):String(e))}</li>`).join("")}</ul>\n </div>`),e.orphans?.length>0&&(n+=`\n <div class="dep-card dep-warn">\n <h3>Orphan Files</h3>\n <span class="dep-count">${e.orphans.length}</span>\n <p>Files with no imports and no consumers</p>\n </div>`),e.critical?.length>0&&(n+=`\n <div class="dep-card dep-info">\n <h3>Critical Files</h3>\n <span class="dep-count">${e.critical.length}</span>\n <p>Files with the most dependents — high blast radius</p>\n </div>`),n+="</div></section>",n}_recommendations(e){const n=[];return e.scores.security<80&&n.push({priority:"critical",text:"Fix security issues immediately — hardcoded secrets or injection risks detected"}),(e.categories.architecture?.issues||[]).filter(e=>"circular_dependency"===e.type).length>0&&n.push({priority:"high",text:"Resolve circular dependencies to prevent build fragility"}),(e.categories.motherCode?.coverage||0)<50&&n.push({priority:"medium",text:`Run <code>thuban inject --fix</code> to raise Mother Code coverage from ${e.categories.motherCode?.coverage||0}% to 100%`}),e.stats.fixableCount>0&&n.push({priority:"medium",text:`Run <code>thuban fix --fix</code> to auto-resolve ${e.stats.fixableCount} items`}),(e.categories.motherCode?.drifted||0)>0&&n.push({priority:"low",text:`${e.categories.motherCode.drifted} files have drifted from their Mother Code annotations — regenerate with <code>thuban fix --fix</code>`}),0===n.length?'\n <section class="recs-section">\n <h2>Recommendations</h2>\n <div class="rec-clean">Codebase is in excellent health. No critical actions needed.</div>\n </section>':`\n <section class="recs-section">\n <h2>Recommendations</h2>\n ${n.map(e=>`\n <div class="rec-item rec-${e.priority}">\n <span class="rec-priority">${e.priority.toUpperCase()}</span>\n <span class="rec-text">${e.text}</span>\n </div>`).join("")}\n </section>`}_footer(e,n){return`\n <footer class="footer">\n <div class="footer-brand">\n <span>Generated by <strong>Thuban</strong> Code Health Engine v0.2.0</span>\n <span class="footer-link">thuban.dev</span>\n </div>\n <div class="footer-meta">\n <span>${e.stats.totalFiles} files scanned in ${e.stats.scanTime}ms</span>\n </div>\n </footer>`}_css(){return"\n:root {\n --bg: #0a0a1a;\n --surface: #12122a;\n --surface2: #1a1a3e;\n --border: #2a2a5a;\n --text: #e0e0f0;\n --text-dim: #8888aa;\n --cyan: #00f6ff;\n --green: #00ff88;\n --yellow: #ffd93d;\n --red: #ff4444;\n --orange: #ff9500;\n --purple: #a855f7;\n --font: 'Segoe UI', system-ui, -apple-system, sans-serif;\n --mono: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;\n}\n\n* { margin: 0; padding: 0; box-sizing: border-box; }\n\nbody {\n background: var(--bg);\n color: var(--text);\n font-family: var(--font);\n line-height: 1.6;\n min-height: 100vh;\n}\n\n.container {\n max-width: 1200px;\n margin: 0 auto;\n padding: 2rem;\n}\n\n/* Header */\n.header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem 2rem;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 12px;\n margin-bottom: 2rem;\n}\n.header-left { display: flex; align-items: center; gap: 1.5rem; }\n.logo { display: flex; align-items: center; gap: 0.75rem; }\n.logo h1 { font-size: 1.5rem; letter-spacing: 0.3em; color: var(--cyan); font-weight: 300; }\n.subtitle { color: var(--text-dim); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.1em; }\n.header-right { display: flex; gap: 2rem; }\n.meta-item { display: flex; flex-direction: column; align-items: flex-end; }\n.meta-label { font-size: 0.7rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }\n.meta-value { font-size: 0.9rem; color: var(--text); font-family: var(--mono); }\n\n/* Score Section */\n.score-section {\n display: flex;\n align-items: center;\n gap: 3rem;\n padding: 2rem;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 12px;\n margin-bottom: 2rem;\n}\n.score-ring-container { position: relative; width: 140px; height: 140px; flex-shrink: 0; }\n.score-ring { width: 100%; height: 100%; }\n.score-progress { transition: stroke-dashoffset 1s ease-out; }\n.score-inner {\n position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);\n text-align: center;\n}\n.score-number { font-size: 2.5rem; font-weight: 700; color: var(--text); display: block; line-height: 1; }\n.score-label { font-size: 0.8rem; color: var(--text-dim); }\n.score-details { flex: 1; }\n.grade {\n font-size: 1.8rem; font-weight: 700; margin-bottom: 1rem;\n letter-spacing: 0.05em;\n}\n.grade-good { color: var(--green); }\n.grade-warn { color: var(--yellow); }\n.grade-bad { color: var(--red); }\n.score-summary { display: flex; gap: 2rem; }\n.stat { display: flex; flex-direction: column; }\n.stat-num { font-size: 1.4rem; font-weight: 600; font-family: var(--mono); }\n.stat-label { font-size: 0.75rem; color: var(--text-dim); text-transform: uppercase; }\n\n/* Category Cards */\n.categories-section { margin-bottom: 2rem; }\n.categories-section h2 { margin-bottom: 1rem; font-size: 1.1rem; color: var(--cyan); text-transform: uppercase; letter-spacing: 0.1em; }\n.category-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 1rem; }\n.category-card {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 10px;\n padding: 1.25rem;\n text-align: center;\n transition: border-color 0.2s;\n}\n.category-card:hover { border-color: var(--cyan); }\n.card-header { display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-bottom: 0.75rem; }\n.card-icon { font-size: 1.2rem; }\n.card-label { font-size: 0.85rem; color: var(--text-dim); }\n.card-score { font-size: 2rem; font-weight: 700; font-family: var(--mono); margin-bottom: 0.5rem; }\n.card-bar { height: 4px; background: var(--surface2); border-radius: 2px; overflow: hidden; margin-bottom: 0.5rem; }\n.card-bar-fill { height: 100%; border-radius: 2px; transition: width 0.8s ease-out; }\n.card-issues { font-size: 0.75rem; color: var(--text-dim); }\n\n/* Mother Code */\n.mother-code-section {\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 2rem;\n margin-bottom: 2rem;\n}\n.mother-code-section h2 { margin-bottom: 1.5rem; font-size: 1.1rem; color: var(--cyan); }\n.mc-grid { display: grid; grid-template-columns: 160px 1fr 1fr; gap: 2rem; align-items: center; }\n.mc-circle {\n width: 120px; height: 120px; border-radius: 50%;\n display: flex; align-items: center; justify-content: center;\n margin: 0 auto;\n}\n.mc-circle span { background: var(--bg); width: 90px; height: 90px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; font-weight: 700; font-family: var(--mono); }\n.mc-label { text-align: center; margin-top: 0.5rem; color: var(--text-dim); font-size: 0.85rem; }\n.mc-row { padding: 0.4rem 0; display: flex; align-items: center; gap: 0.5rem; }\n.mc-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }\n.dot-green { background: var(--green); }\n.dot-yellow { background: var(--yellow); }\n.dot-red { background: var(--red); }\n.mc-action code {\n display: inline-block; background: var(--bg); padding: 0.4rem 0.8rem;\n border-radius: 6px; font-family: var(--mono); color: var(--cyan); margin-top: 0.5rem;\n border: 1px solid var(--border);\n}\n\n/* Issues Table */\n.issues-section { margin-bottom: 2rem; }\n.issues-section h2 { margin-bottom: 1rem; font-size: 1.1rem; color: var(--cyan); text-transform: uppercase; letter-spacing: 0.1em; }\n.issues-table {\n width: 100%; border-collapse: collapse;\n background: var(--surface); border: 1px solid var(--border); border-radius: 10px;\n overflow: hidden;\n}\n.issues-table th {\n text-align: left; padding: 0.75rem 1rem; background: var(--surface2);\n font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dim);\n border-bottom: 1px solid var(--border);\n}\n.issues-table td { padding: 0.6rem 1rem; border-bottom: 1px solid rgba(42,42,90,0.5); font-size: 0.85rem; }\n.issues-table tr:last-child td { border-bottom: none; }\n.issues-table tr:hover { background: rgba(0,246,255,0.03); }\n.file-cell { font-family: var(--mono); font-size: 0.8rem; color: var(--cyan); max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.sev {\n display: inline-block; padding: 0.15rem 0.5rem; border-radius: 4px;\n font-size: 0.7rem; font-weight: 600; letter-spacing: 0.05em;\n}\n.sev-critical { background: rgba(255,68,68,0.2); color: var(--red); }\n.sev-high, .sev-error { background: rgba(255,149,0,0.2); color: var(--orange); }\n.sev-medium, .sev-warning { background: rgba(255,217,61,0.2); color: var(--yellow); }\n.sev-low, .sev-info { background: rgba(136,136,170,0.15); color: var(--text-dim); }\n.fixable-badge { background: rgba(0,255,136,0.15); color: var(--green); padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.7rem; }\n.manual-badge { background: rgba(255,217,61,0.15); color: var(--yellow); padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.7rem; }\n\n/* Fixable Section */\n.fixable-section {\n background: var(--surface); border: 1px solid var(--border); border-radius: 12px;\n padding: 2rem; margin-bottom: 2rem;\n}\n.fixable-section h2 { margin-bottom: 0.5rem; font-size: 1.1rem; color: var(--green); }\n.fix-intro { color: var(--text-dim); font-size: 0.9rem; margin-bottom: 1.5rem; }\n.fix-intro code { background: var(--bg); padding: 0.2rem 0.5rem; border-radius: 4px; font-family: var(--mono); color: var(--cyan); border: 1px solid var(--border); }\n.fix-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }\n.fix-item {\n background: var(--bg); border: 1px solid var(--border); border-radius: 8px;\n padding: 1rem; display: flex; align-items: center; gap: 1rem;\n}\n.fix-count { font-size: 1.8rem; font-weight: 700; color: var(--green); font-family: var(--mono); min-width: 40px; }\n.fix-label { font-size: 0.85rem; color: var(--text); }\n\n/* Dependencies */\n.deps-section { margin-bottom: 2rem; }\n.deps-section h2 { margin-bottom: 1rem; font-size: 1.1rem; color: var(--cyan); text-transform: uppercase; letter-spacing: 0.1em; }\n.deps-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }\n.dep-card {\n background: var(--surface); border-radius: 10px; padding: 1.5rem;\n border: 1px solid var(--border);\n}\n.dep-danger { border-left: 3px solid var(--red); }\n.dep-warn { border-left: 3px solid var(--yellow); }\n.dep-info { border-left: 3px solid var(--cyan); }\n.dep-card h3 { font-size: 0.9rem; margin-bottom: 0.5rem; }\n.dep-count { font-size: 2rem; font-weight: 700; font-family: var(--mono); color: var(--text); display: block; margin-bottom: 0.5rem; }\n.dep-card ul { list-style: none; font-size: 0.8rem; color: var(--text-dim); font-family: var(--mono); }\n.dep-card li { padding: 0.2rem 0; }\n.dep-card p { font-size: 0.8rem; color: var(--text-dim); }\n\n/* Recommendations */\n.recs-section { margin-bottom: 2rem; }\n.recs-section h2 { margin-bottom: 1rem; font-size: 1.1rem; color: var(--cyan); text-transform: uppercase; letter-spacing: 0.1em; }\n.rec-item {\n display: flex; align-items: center; gap: 1rem;\n background: var(--surface); border: 1px solid var(--border); border-radius: 8px;\n padding: 1rem 1.5rem; margin-bottom: 0.5rem;\n}\n.rec-critical { border-left: 3px solid var(--red); }\n.rec-high { border-left: 3px solid var(--orange); }\n.rec-medium { border-left: 3px solid var(--yellow); }\n.rec-low { border-left: 3px solid var(--text-dim); }\n.rec-priority {\n font-size: 0.65rem; font-weight: 700; letter-spacing: 0.1em;\n padding: 0.2rem 0.5rem; border-radius: 4px; min-width: 70px; text-align: center;\n}\n.rec-critical .rec-priority { background: rgba(255,68,68,0.2); color: var(--red); }\n.rec-high .rec-priority { background: rgba(255,149,0,0.2); color: var(--orange); }\n.rec-medium .rec-priority { background: rgba(255,217,61,0.2); color: var(--yellow); }\n.rec-low .rec-priority { background: rgba(136,136,170,0.15); color: var(--text-dim); }\n.rec-text { font-size: 0.9rem; }\n.rec-text code { background: var(--bg); padding: 0.15rem 0.4rem; border-radius: 4px; font-family: var(--mono); color: var(--cyan); font-size: 0.8rem; }\n.rec-clean { background: var(--surface); border: 1px solid var(--green); border-radius: 8px; padding: 1.5rem; text-align: center; color: var(--green); }\n\n/* Footer */\n.footer {\n display: flex; justify-content: space-between; align-items: center;\n padding: 1.5rem 2rem; border-top: 1px solid var(--border);\n color: var(--text-dim); font-size: 0.8rem; margin-top: 2rem;\n}\n.footer-brand { display: flex; align-items: center; gap: 1.5rem; }\n.footer-brand strong { color: var(--cyan); }\n.footer-link { color: var(--cyan); }\n\n/* Responsive */\n@media (max-width: 900px) {\n .category-grid { grid-template-columns: repeat(2, 1fr); }\n .fix-grid { grid-template-columns: repeat(2, 1fr); }\n .mc-grid { grid-template-columns: 1fr; }\n .header { flex-direction: column; gap: 1rem; }\n .score-section { flex-direction: column; text-align: center; }\n .score-summary { justify-content: center; }\n}\n@media (max-width: 600px) {\n .category-grid { grid-template-columns: 1fr; }\n .fix-grid { grid-template-columns: 1fr; }\n .deps-grid { grid-template-columns: 1fr; }\n}\n\n/* Print */\n@media print {\n body { background: #fff; color: #111; }\n .container { max-width: 100%; }\n .header, .category-card, .mother-code-section, .fixable-section, .issues-section, .dep-card, .rec-item {\n background: #fafafa; border-color: #ddd;\n }\n}\n"}_js(){return"\n// Animate score ring on load\ndocument.addEventListener('DOMContentLoaded', () => {\n const progress = document.querySelector('.score-progress');\n if (progress) {\n const final = progress.style.strokeDashoffset || progress.getAttribute('stroke-dashoffset');\n progress.style.strokeDashoffset = progress.getAttribute('stroke-dasharray');\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n progress.style.strokeDashoffset = final;\n });\n });\n }\n});\n"}_scoreColor(e){return e>=80?"#00ff88":e>=60?"#ffd93d":"#ff4444"}_esc(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}}module.exports=HtmlReportGenerator;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const fs=require("fs"),path=require("path"),os=require("os"),crypto=require("crypto"),THUBAN_DIR=path.join(os.homedir(),".thuban"),USAGE_FILE=path.join(THUBAN_DIR,"usage.json"),LICENSE_FILE=path.join(THUBAN_DIR,"license.json"),TIERS={free:{name:"Free",maxFiles:100,scansPerMonth:5,showFullDetails:!1,showDashboard:!1,showAutoFix:!1,showCIAction:!1,teaserIssues:3,maxHallucinationDetail:3},pro:{name:"Pro",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!1,teaserIssues:1/0,maxHallucinationDetail:1/0},team:{name:"Team",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!0,teaserIssues:1/0,maxHallucinationDetail:1/0},enterprise:{name:"Enterprise",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!0,teaserIssues:1/0,maxHallucinationDetail:1/0}};class LicenseManager{constructor(){this._ensureDir(),this.machineId=this._getMachineId(),this.usage=this._loadUsage(),this.license=this._loadLicense()}getTier(){return
|
|
1
|
+
const fs=require("fs"),path=require("path"),os=require("os"),crypto=require("crypto"),THUBAN_DIR=path.join(os.homedir(),".thuban"),USAGE_FILE=path.join(THUBAN_DIR,"usage.json"),LICENSE_FILE=path.join(THUBAN_DIR,"license.json"),ED25519_PUBLIC_KEY_DER_B64="MCowBQYDK2VwAyEA9/1g/P5f3OwZ/u1LNS1MY6ytTg7GLn3VIWOMxm4qwMQ=",TIERS={free:{name:"Free",maxFiles:100,scansPerMonth:5,showFullDetails:!1,showDashboard:!1,showAutoFix:!1,showCIAction:!1,teaserIssues:3,maxHallucinationDetail:3},pro:{name:"Pro",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!1,teaserIssues:1/0,maxHallucinationDetail:1/0},team:{name:"Team",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!0,teaserIssues:1/0,maxHallucinationDetail:1/0},enterprise:{name:"Enterprise",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!0,teaserIssues:1/0,maxHallucinationDetail:1/0}};class LicenseManager{constructor(){this._ensureDir(),this.machineId=this._getMachineId(),this.usage=this._loadUsage(),this.license=this._loadLicense()}getTier(){return"true"!==process.env.THUBAN_DEV_MODE||__filename.includes("node_modules")?this.license&&this.license.key&&this._validateKey(this.license.key)&&this.license.tier||"free":"enterprise"}getTierConfig(){return"true"===process.env.THUBAN_DEV_MODE?TIERS.pro:TIERS[this.getTier()]||TIERS.free}canScan(){if("true"===process.env.THUBAN_DEV_MODE&&!__filename.includes("node_modules"))return{allowed:!0,remaining:1/0,devMode:!0};const e=this.getTierConfig();if(e.scansPerMonth===1/0)return{allowed:!0,remaining:1/0};const t=this._currentMonth(),s=this.usage.scans?.[t]||0,n=Math.max(0,e.scansPerMonth-s);return{allowed:n>0,remaining:n,limit:e.scansPerMonth,used:s,resetsOn:this._nextMonthDate()}}recordScan(){if("true"===process.env.THUBAN_DEV_MODE&&!__filename.includes("node_modules"))return;const e=this._currentMonth();this.usage.scans||(this.usage.scans={}),this.usage.scans[e]=(this.usage.scans[e]||0)+1,this.usage.lastScan=(new Date).toISOString(),this.usage.totalScans=(this.usage.totalScans||0)+1,this._saveUsage()}activate(e){const t=this._validateKey(e);return t?(this.license={key:e,tier:t.tier,activatedAt:(new Date).toISOString(),machineId:this.machineId},this._saveLicense(),{success:!0,tier:t.tier,tierName:TIERS[t.tier]?.name||t.tier}):{success:!1,error:"Invalid license key format"}}deactivate(){return this.license={},this._saveLicense(),{success:!0}}getStatus(){const e=this.getTier(),t=this.getTierConfig(),s=this.canScan();let n=!1;return n="true"===process.env.THUBAN_DEV_MODE&&!__filename.includes("node_modules"),{tier:e,tierName:t.name,machineId:this.machineId.substring(0,8)+"...",licensed:"free"!==e,devMode:n,scansUsed:n?0:s.used||0,scansRemaining:s.remaining,scansLimit:n?1/0:s.limit||t.scansPerMonth,totalScans:this.usage.totalScans||0,features:{fullDetails:t.showFullDetails,dashboard:t.showDashboard,autoFix:t.showAutoFix,ciAction:t.showCIAction,scheduledScans:"free"!==e}}}canUseProFeature(e="This feature"){const t=this.getTier();return"free"===t?{allowed:!1,message:`${e} is available on Thuban Pro and above.`}:{allowed:!0,tier:t}}_ensureDir(){try{fs.existsSync(THUBAN_DIR)||fs.mkdirSync(THUBAN_DIR,{recursive:!0})}catch(e){}}_getMachineId(){const e=[os.hostname(),os.userInfo().username,os.platform(),os.arch()].join("|");try{const t=os.networkInterfaces(),s=Object.values(t).flat().filter(e=>!e.internal&&e.mac&&"00:00:00:00:00:00"!==e.mac).map(e=>e.mac).sort();if(s.length>0)return crypto.createHash("sha256").update(e+"|"+s[0]).digest("hex")}catch(e){}return crypto.createHash("sha256").update(e).digest("hex")}_loadUsage(){try{return JSON.parse(fs.readFileSync(USAGE_FILE,"utf-8"))}catch(e){return{scans:{},totalScans:0}}}_atomicWriteSync(e,t){const s=e+".tmp."+process.pid+"."+Date.now();try{fs.writeFileSync(s,t,"utf-8"),fs.renameSync(s,e)}catch(e){try{fs.unlinkSync(s)}catch{}}}_saveUsage(){try{this._atomicWriteSync(USAGE_FILE,JSON.stringify(this.usage,null,2))}catch(e){}}_loadLicense(){try{return JSON.parse(fs.readFileSync(LICENSE_FILE,"utf-8"))}catch(e){return{}}}_saveLicense(){try{this._atomicWriteSync(LICENSE_FILE,JSON.stringify(this.license,null,2))}catch(e){}}_validateKey(e){if(!e||"string"!=typeof e)return null;const t=e.match(/^THUBAN-(FREE|PRO|TEAM|ENT)-([A-Za-z0-9]{16})-([A-Fa-f0-9]{8,})$/);if(!t)return null;const s=t[1],n=t[2],a=t[3],r={FREE:"free",PRO:"pro",TEAM:"team",ENT:"enterprise"};try{const e=crypto.createPublicKey({key:Buffer.from(ED25519_PUBLIC_KEY_DER_B64,"base64"),format:"der",type:"spki"}),t=Buffer.from(`THUBAN-${s}-${n}`),i=Buffer.from(a,"hex");if(crypto.verify(null,t,e,i))return{tier:r[s]||"free",valid:!0,method:"ed25519"}}catch(e){}return null}_currentMonth(){const e=new Date;return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}`}_nextMonthDate(){const e=new Date;return new Date(e.getFullYear(),e.getMonth()+1,1).toISOString().split("T")[0]}static generateKey(e="PRO"){const t=process.env.THUBAN_SIGNING_PRIVATE_KEY;if(!t)throw new Error("THUBAN_SIGNING_PRIVATE_KEY environment variable is required to generate license keys. This keeps the signing key out of source control and off end-user machines.");const s=crypto.createPrivateKey({key:Buffer.from(t,"base64"),format:"der",type:"pkcs8"}),n=crypto.randomBytes(8).toString("hex"),a=Buffer.from(`THUBAN-${e}-${n}`);return`THUBAN-${e}-${n}-${crypto.sign(null,a,s).toString("hex")}`}}module.exports=LicenseManager;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const fs=require("fs"),path=require("path"),{execSync:execSync,exec:exec}=require("child_process"),EventEmitter=require("events"),ROOT=path.join(__dirname,".."),SERVER=path.join(ROOT,"server"),MANIFEST_PATH=path.join(SERVER,"data","health-check-manifest.json");class MasterHealthChecker extends EventEmitter{constructor(t={}){super(),this.config={rootPath:t.rootPath||ROOT,serverPath:t.serverPath||SERVER,timeout:t.timeout||5e3,...t},this.manifest=this._loadManifest(),this.lastResults=null,this.issueCount=0}_loadManifest(){try{if(fs.existsSync(MANIFEST_PATH))return JSON.parse(fs.readFileSync(MANIFEST_PATH,"utf8"))}catch(t){console.error("[MASTER-HEALTH] Failed to load manifest:",t.message)}return null}async runFullCheck(){console.log("[MASTER-HEALTH] Starting full system health check (47 checkpoints)...");const t=Date.now(),s={timestamp:(new Date).toISOString(),duration:0,totalChecks:0,passed:0,failed:0,warnings:0,systems:{},criticalIssues:[],autoHealAttempted:[],humanEscalationNeeded:[]},e=[{id:"server",fn:()=>this._checkServer()},{id:"senate",fn:()=>this._checkSenate()},{id:"citadel",fn:()=>this._checkCitadel()},{id:"shadowRealm",fn:()=>this._checkShadowRealm()},{id:"forge",fn:()=>this._checkForge()},{id:"ocee",fn:()=>this._checkOCEE()},{id:"memory",fn:()=>this._checkMemory()},{id:"external",fn:()=>this._checkExternal()}];for(const t of e)try{const e=await t.fn();s.systems[t.id]=e,s.totalChecks+=e.checks.length,s.passed+=e.checks.filter(t=>"PASS"===t.status).length,s.failed+=e.checks.filter(t=>"FAIL"===t.status).length,s.warnings+=e.checks.filter(t=>"WARN"===t.status).length;const i=e.checks.filter(t=>"FAIL"===t.status&&"CRITICAL"===t.criticality);s.criticalIssues.push(...i);const a=e.checks.filter(t=>t.needsHumanEscalation);s.humanEscalationNeeded.push(...a)}catch(e){s.systems[t.id]={status:"ERROR",error:e.message,checks:[]}}return s.duration=Date.now()-t,s.overallStatus=this._calculateOverallStatus(s),this.lastResults=s,this.emit("checkComplete",s),console.log(`[MASTER-HEALTH] Complete: ${s.passed}/${s.totalChecks} passed (${s.duration}ms)`),s}async runQuickCheck(){console.log("[MASTER-HEALTH] Running quick check (critical systems)...");const t={timestamp:(new Date).toISOString(),type:"quick",checks:[]};t.checks.push(await this._check("SRV-001","Server Process",()=>this._fileExists(path.join(this.config.serverPath,"server.js")))),t.checks.push(await this._check("SEN-003","Model Registry",()=>{const t=path.join(this.config.serverPath,"data","model-registry.json");if(!fs.existsSync(t))return!1;const s=JSON.parse(fs.readFileSync(t,"utf8"));return Object.keys(s.providers||{}).length>=3})),t.checks.push(await this._check("CIT-001","Constitution",()=>{const t=path.join(this.config.rootPath,"citadel","constitution.json");return this._fileExists(t)&&this._isValidJson(t)})),t.checks.push(await this._check("CIT-002","File Guard",()=>this._fileExists(path.join(this.config.rootPath,"citadel","file-guard.js"))));const s=t.checks.filter(t=>"PASS"===t.status).length;return console.log(`[MASTER-HEALTH] Quick check: ${s}/${t.checks.length} passed`),t}async _checkServer(){const t=[];return t.push(await this._check("SRV-001","Server Files",()=>this._fileExists(path.join(this.config.serverPath,"server.js"))&&this._fileExists(path.join(this.config.serverPath,"package.json")),"CRITICAL")),t.push(await this._check("SRV-002","Node Modules",()=>this._fileExists(path.join(this.config.serverPath,"node_modules")),"HIGH","npm install")),t.push(await this._check("SRV-003","Package Valid",()=>this._isValidJson(path.join(this.config.serverPath,"package.json")),"HIGH")),t.push(await this._check("SRV-004","Data Directory",()=>this._dirExists(path.join(this.config.serverPath,"data")),"MEDIUM")),t.push(await this._check("SRV-005","Environment",()=>this._fileExists(path.join(this.config.rootPath,".env"))||void 0!==process.env.ANTHROPIC_API_KEY,"HIGH")),{status:this._systemStatus(t),checks:t}}async _checkSenate(){const t=[];return t.push(await this._check("SEN-001","Model Registry",()=>{const t=path.join(this.config.serverPath,"data","model-registry.json");return this._isValidJson(t)},"CRITICAL","Restore from snapshot")),t.push(await this._check("SEN-002","Senate Weights",()=>{const t=path.join(this.config.serverPath,"data","senate-weights-v2.json");return this._isValidJson(t)},"HIGH","Reset to defaults")),t.push(await this._check("SEN-003","Providers Configured",()=>{const t=path.join(this.config.serverPath,"data","model-registry.json");if(!this._isValidJson(t))return!1;const s=JSON.parse(fs.readFileSync(t,"utf8"));return Object.keys(s.providers||{}).length>=5},"CRITICAL")),t.push(await this._check("SEN-004","Senate Core",()=>this._fileExists(path.join(this.config.serverPath,"senate-v2.js"))||this._fileExists(path.join(this.config.serverPath,"senate.js")),"CRITICAL")),t.push(await this._check("SEN-005","Response Cache",()=>{const t=path.join(this.config.serverPath,"data","response-cache.json");return!this._fileExists(t)||this._isValidJson(t)},"LOW")),{status:this._systemStatus(t),checks:t}}async _checkCitadel(){const t=[];return t.push(await this._check("CIT-001","Constitution",()=>{const t=path.join(this.config.rootPath,"citadel","constitution.json");return this._isValidJson(t)},"CRITICAL","HALT - Safety rules missing")),t.push(await this._check("CIT-002","File Guard",()=>this._fileExists(path.join(this.config.rootPath,"citadel","file-guard.js")),"CRITICAL")),t.push(await this._check("CIT-003","Enforcer",()=>this._fileExists(path.join(this.config.rootPath,"citadel","enforcer.js")),"CRITICAL")),t.push(await this._check("CIT-004","Watchers",()=>this._fileExists(path.join(this.config.rootPath,"citadel","watchers.js")),"HIGH")),t.push(await this._check("CIT-005","Audit Directory",()=>this._dirExists(path.join(this.config.rootPath,"citadel","audit")),"MEDIUM","Create directory")),{status:this._systemStatus(t),checks:t}}async _checkShadowRealm(){const t=[];return t.push(await this._check("SHD-001","Shadow Realm V2",()=>this._fileExists(path.join(this.config.rootPath,"build-engine","orion-core","shadow-realm-v2.js")),"CRITICAL")),t.push(await this._check("SHD-002","Node Binary",()=>{try{return execSync("node --version",{stdio:"pipe"}),!0}catch{return!1}},"CRITICAL")),t.push(await this._check("SHD-003","Temp Writable",()=>{const t=require("os"),s=path.join(t.tmpdir(),"orion-health-test-"+Date.now());try{return fs.writeFileSync(s,"test"),fs.unlinkSync(s),!0}catch{return!1}},"HIGH")),t.push(await this._check("SHD-004","Healing Loop",()=>this._fileExists(path.join(this.config.rootPath,"build-engine","orion-core","healing-loop.js")),"HIGH")),{status:this._systemStatus(t),checks:t}}async _checkForge(){const t=[],s=path.join(this.config.rootPath,"forge");return t.push(await this._check("FRG-001","Forge Core",()=>this._fileExists(path.join(s,"forge-core.js")),"HIGH")),t.push(await this._check("FRG-002","Task Decomposer",()=>this._fileExists(path.join(s,"task-decomposer.js")),"MEDIUM")),t.push(await this._check("FRG-003","Intent Matcher",()=>this._fileExists(path.join(s,"intent-matcher.js")),"MEDIUM")),t.push(await this._check("FRG-004","Diagnostician",()=>this._fileExists(path.join(s,"diagnostician-v2.js"))||this._fileExists(path.join(s,"diagnostician.js")),"MEDIUM")),t.push(await this._check("FRG-005","Foreman",()=>this._fileExists(path.join(this.config.rootPath,"build-engine","orion-core","foreman.js")),"HIGH")),{status:this._systemStatus(t),checks:t}}async _checkOCEE(){const t=[],s=path.join(this.config.serverPath,"ocee");return t.push(await this._check("OCE-001","OCEE Core",()=>this._fileExists(path.join(s,"ocee-core.js")),"HIGH")),t.push(await this._check("OCE-002","Task Queue",()=>this._fileExists(path.join(s,"persistent-task-queue.js")),"HIGH")),t.push(await this._check("OCE-003","Checkpoint Manager",()=>this._fileExists(path.join(s,"checkpoint-manager.js")),"HIGH")),t.push(await this._check("OCE-004","Build Sessions",()=>{const t=path.join(this.config.serverPath,"data","build-sessions.json");return!this._fileExists(t)||this._isValidJson(t)},"MEDIUM")),t.push(await this._check("OCE-005","Executor Status",()=>{const t=path.join(this.config.serverPath,"data","executor-status.json");return this._isValidJson(t)},"MEDIUM")),{status:this._systemStatus(t),checks:t}}async _checkMemory(){const t=[];return t.push(await this._check("MEM-001","Orion Knowledge",()=>this._isValidJson(path.join(this.config.rootPath,"orion-knowledge.json")),"MEDIUM","Reinitialize")),t.push(await this._check("MEM-002","Memory Bank",()=>{const t=path.join(this.config.rootPath,"memory-bank");return this._fileExists(path.join(t,"progress.md"))&&this._fileExists(path.join(t,"activeContext.md"))},"MEDIUM")),t.push(await this._check("MEM-003","Snapshot Manifest",()=>{const t=path.join(this.config.serverPath,".orion","snapshot-manifest.json");return!this._fileExists(t)||this._isValidJson(t)},"MEDIUM")),t.push(await this._check("MEM-004","Build Logs",()=>this._dirExists(path.join(this.config.serverPath,"data","build-logs")),"MEDIUM")),t.push(await this._check("MEM-005","Failure Memory",()=>this._fileExists(path.join(this.config.serverPath,"services","failure-memory.js")),"MEDIUM")),{status:this._systemStatus(t),checks:t}}async _checkExternal(){const t=[];return t.push(await this._check("EXT-001","Zoho Tokens",()=>{const t=path.join(this.config.serverPath,"data","zoho-tokens.json");return!this._fileExists(t)||this._isValidJson(t)},"LOW")),t.push(await this._check("EXT-002","Cartography",()=>this._dirExists(path.join(this.config.rootPath,"cartography")),"LOW")),t.push(await this._check("EXT-003","Notification Config",()=>{const t=path.join(this.config.serverPath,"data","notification-config.json");return!this._fileExists(t)||this._isValidJson(t)},"LOW")),t.push(await this._check("EXT-004","Health History",()=>{const t=path.join(this.config.serverPath,"data","health-history.json");return!this._fileExists(t)||this._isValidJson(t)},"LOW")),{status:this._systemStatus(t),checks:t}}async _check(t,s,e,i="MEDIUM",a=null){const h={id:t,name:s,criticality:i,status:"UNKNOWN",autoHeal:a,needsHumanEscalation:!1};try{const t=await e();h.status=t?"PASS":"FAIL",t||"CRITICAL"!==i||(h.needsHumanEscalation=!0)}catch(t){h.status="ERROR",h.error=t.message,h.needsHumanEscalation=!0}return h}_fileExists(t){try{return fs.existsSync(t)}catch{return!1}}_dirExists(t){try{return fs.existsSync(t)&&fs.statSync(t).isDirectory()}catch{return!1}}_isValidJson(t){try{return!!fs.existsSync(t)&&(JSON.parse(fs.readFileSync(t,"utf8")),!0)}catch{return!1}}_systemStatus(t){const s=t.filter(t=>"FAIL"===t.status||"ERROR"===t.status);return s.filter(t=>"CRITICAL"===t.criticality).length>0?"CRITICAL":s.length>0?"DEGRADED":"HEALTHY"}_calculateOverallStatus(t){return t.criticalIssues.length>0?"CRITICAL":t.failed>0?"DEGRADED":t.warnings>0?"WARNING":"HEALTHY"}getSummary(){return this.lastResults?{status:this.lastResults.overallStatus,passed:this.lastResults.passed,failed:this.lastResults.failed,total:this.lastResults.totalChecks,criticalIssues:this.lastResults.criticalIssues.length,humanActionNeeded:this.lastResults.humanEscalationNeeded.length,lastCheck:this.lastResults.timestamp}:{status:"NO_DATA",message:"No health check run yet"}}}module.exports=MasterHealthChecker;
|
|
1
|
+
const fs=require("fs"),path=require("path"),{execFileSync:execFileSync}=require("child_process"),EventEmitter=require("events"),ROOT=path.join(__dirname,".."),SERVER=path.join(ROOT,"server"),MANIFEST_PATH=path.join(SERVER,"data","health-check-manifest.json");class MasterHealthChecker extends EventEmitter{constructor(t={}){super(),this.config={rootPath:t.rootPath||ROOT,serverPath:t.serverPath||SERVER,timeout:t.timeout||5e3,...t},this.manifest=this._loadManifest(),this.lastResults=null,this.issueCount=0}_loadManifest(){try{if(fs.existsSync(MANIFEST_PATH))return JSON.parse(fs.readFileSync(MANIFEST_PATH,"utf8"))}catch(t){console.error("[MASTER-HEALTH] Failed to load manifest")}return null}async runFullCheck(){console.log("[MASTER-HEALTH] Starting full system health check (47 checkpoints)...");const t=Date.now(),s={timestamp:(new Date).toISOString(),duration:0,totalChecks:0,passed:0,failed:0,warnings:0,systems:{},criticalIssues:[],autoHealAttempted:[],humanEscalationNeeded:[]},e=[{id:"server",fn:()=>this._checkServer()},{id:"senate",fn:()=>this._checkSenate()},{id:"citadel",fn:()=>this._checkCitadel()},{id:"shadowRealm",fn:()=>this._checkShadowRealm()},{id:"forge",fn:()=>this._checkForge()},{id:"ocee",fn:()=>this._checkOCEE()},{id:"memory",fn:()=>this._checkMemory()},{id:"external",fn:()=>this._checkExternal()}];for(const t of e)try{const e=await t.fn();s.systems[t.id]=e,s.totalChecks+=e.checks.length,s.passed+=e.checks.filter(t=>"PASS"===t.status).length,s.failed+=e.checks.filter(t=>"FAIL"===t.status).length,s.warnings+=e.checks.filter(t=>"WARN"===t.status).length;const i=e.checks.filter(t=>"FAIL"===t.status&&"CRITICAL"===t.criticality);s.criticalIssues.push(...i);const a=e.checks.filter(t=>t.needsHumanEscalation);s.humanEscalationNeeded.push(...a)}catch(e){s.systems[t.id]={status:"ERROR",error:e.message,checks:[]}}return s.duration=Date.now()-t,s.overallStatus=this._calculateOverallStatus(s),this.lastResults=s,this.emit("checkComplete",s),console.log(`[MASTER-HEALTH] Complete: ${s.passed}/${s.totalChecks} passed (${s.duration}ms)`),s}async runQuickCheck(){console.log("[MASTER-HEALTH] Running quick check (critical systems)...");const t={timestamp:(new Date).toISOString(),type:"quick",checks:[]};t.checks.push(await this._check("SRV-001","Server Process",()=>this._fileExists(path.join(this.config.serverPath,"server.js")))),t.checks.push(await this._check("SEN-003","Model Registry",()=>{const t=path.join(this.config.serverPath,"data","model-registry.json");if(!fs.existsSync(t))return!1;const s=JSON.parse(fs.readFileSync(t,"utf8"));return Object.keys(s.providers||{}).length>=3})),t.checks.push(await this._check("CIT-001","Constitution",()=>{const t=path.join(this.config.rootPath,"citadel","constitution.json");return this._fileExists(t)&&this._isValidJson(t)})),t.checks.push(await this._check("CIT-002","File Guard",()=>this._fileExists(path.join(this.config.rootPath,"citadel","file-guard.js"))));const s=t.checks.filter(t=>"PASS"===t.status).length;return console.log(`[MASTER-HEALTH] Quick check: ${s}/${t.checks.length} passed`),t}async _checkServer(){const t=[];return t.push(await this._check("SRV-001","Server Files",()=>this._fileExists(path.join(this.config.serverPath,"server.js"))&&this._fileExists(path.join(this.config.serverPath,"package.json")),"CRITICAL")),t.push(await this._check("SRV-002","Node Modules",()=>this._fileExists(path.join(this.config.serverPath,"node_modules")),"HIGH","npm install")),t.push(await this._check("SRV-003","Package Valid",()=>this._isValidJson(path.join(this.config.serverPath,"package.json")),"HIGH")),t.push(await this._check("SRV-004","Data Directory",()=>this._dirExists(path.join(this.config.serverPath,"data")),"MEDIUM")),t.push(await this._check("SRV-005","Environment",()=>this._fileExists(path.join(this.config.rootPath,".env"))||void 0!==process.env.ANTHROPIC_API_KEY,"HIGH")),{status:this._systemStatus(t),checks:t}}async _checkSenate(){const t=[];return t.push(await this._check("SEN-001","Model Registry",()=>{const t=path.join(this.config.serverPath,"data","model-registry.json");return this._isValidJson(t)},"CRITICAL","Restore from snapshot")),t.push(await this._check("SEN-002","Senate Weights",()=>{const t=path.join(this.config.serverPath,"data","senate-weights-v2.json");return this._isValidJson(t)},"HIGH","Reset to defaults")),t.push(await this._check("SEN-003","Providers Configured",()=>{const t=path.join(this.config.serverPath,"data","model-registry.json");if(!this._isValidJson(t))return!1;const s=JSON.parse(fs.readFileSync(t,"utf8"));return Object.keys(s.providers||{}).length>=5},"CRITICAL")),t.push(await this._check("SEN-004","Senate Core",()=>this._fileExists(path.join(this.config.serverPath,"senate-v2.js"))||this._fileExists(path.join(this.config.serverPath,"senate.js")),"CRITICAL")),t.push(await this._check("SEN-005","Response Cache",()=>{const t=path.join(this.config.serverPath,"data","response-cache.json");return!this._fileExists(t)||this._isValidJson(t)},"LOW")),{status:this._systemStatus(t),checks:t}}async _checkCitadel(){const t=[];return t.push(await this._check("CIT-001","Constitution",()=>{const t=path.join(this.config.rootPath,"citadel","constitution.json");return this._isValidJson(t)},"CRITICAL","HALT - Safety rules missing")),t.push(await this._check("CIT-002","File Guard",()=>this._fileExists(path.join(this.config.rootPath,"citadel","file-guard.js")),"CRITICAL")),t.push(await this._check("CIT-003","Enforcer",()=>this._fileExists(path.join(this.config.rootPath,"citadel","enforcer.js")),"CRITICAL")),t.push(await this._check("CIT-004","Watchers",()=>this._fileExists(path.join(this.config.rootPath,"citadel","watchers.js")),"HIGH")),t.push(await this._check("CIT-005","Audit Directory",()=>this._dirExists(path.join(this.config.rootPath,"citadel","audit")),"MEDIUM","Create directory")),{status:this._systemStatus(t),checks:t}}async _checkShadowRealm(){const t=[];return t.push(await this._check("SHD-001","Shadow Realm V2",()=>this._fileExists(path.join(this.config.rootPath,"build-engine","orion-core","shadow-realm-v2.js")),"CRITICAL")),t.push(await this._check("SHD-002","Node Binary",()=>{try{return execFileSync("node",["--version"],{stdio:"pipe"}),!0}catch{return!1}},"CRITICAL")),t.push(await this._check("SHD-003","Temp Writable",()=>{const t=require("os"),s=path.join(t.tmpdir(),"orion-health-test-"+Date.now());try{return fs.writeFileSync(s,"test"),fs.unlinkSync(s),!0}catch{return!1}},"HIGH")),t.push(await this._check("SHD-004","Healing Loop",()=>this._fileExists(path.join(this.config.rootPath,"build-engine","orion-core","healing-loop.js")),"HIGH")),{status:this._systemStatus(t),checks:t}}async _checkForge(){const t=[],s=path.join(this.config.rootPath,"forge");return t.push(await this._check("FRG-001","Forge Core",()=>this._fileExists(path.join(s,"forge-core.js")),"HIGH")),t.push(await this._check("FRG-002","Task Decomposer",()=>this._fileExists(path.join(s,"task-decomposer.js")),"MEDIUM")),t.push(await this._check("FRG-003","Intent Matcher",()=>this._fileExists(path.join(s,"intent-matcher.js")),"MEDIUM")),t.push(await this._check("FRG-004","Diagnostician",()=>this._fileExists(path.join(s,"diagnostician-v2.js"))||this._fileExists(path.join(s,"diagnostician.js")),"MEDIUM")),t.push(await this._check("FRG-005","Foreman",()=>this._fileExists(path.join(this.config.rootPath,"build-engine","orion-core","foreman.js")),"HIGH")),{status:this._systemStatus(t),checks:t}}async _checkOCEE(){const t=[],s=path.join(this.config.serverPath,"ocee");return t.push(await this._check("OCE-001","OCEE Core",()=>this._fileExists(path.join(s,"ocee-core.js")),"HIGH")),t.push(await this._check("OCE-002","Task Queue",()=>this._fileExists(path.join(s,"persistent-task-queue.js")),"HIGH")),t.push(await this._check("OCE-003","Checkpoint Manager",()=>this._fileExists(path.join(s,"checkpoint-manager.js")),"HIGH")),t.push(await this._check("OCE-004","Build Sessions",()=>{const t=path.join(this.config.serverPath,"data","build-sessions.json");return!this._fileExists(t)||this._isValidJson(t)},"MEDIUM")),t.push(await this._check("OCE-005","Executor Status",()=>{const t=path.join(this.config.serverPath,"data","executor-status.json");return this._isValidJson(t)},"MEDIUM")),{status:this._systemStatus(t),checks:t}}async _checkMemory(){const t=[];return t.push(await this._check("MEM-001","Orion Knowledge",()=>this._isValidJson(path.join(this.config.rootPath,"orion-knowledge.json")),"MEDIUM","Reinitialize")),t.push(await this._check("MEM-002","Memory Bank",()=>{const t=path.join(this.config.rootPath,"memory-bank");return this._fileExists(path.join(t,"progress.md"))&&this._fileExists(path.join(t,"activeContext.md"))},"MEDIUM")),t.push(await this._check("MEM-003","Snapshot Manifest",()=>{const t=path.join(this.config.serverPath,".orion","snapshot-manifest.json");return!this._fileExists(t)||this._isValidJson(t)},"MEDIUM")),t.push(await this._check("MEM-004","Build Logs",()=>this._dirExists(path.join(this.config.serverPath,"data","build-logs")),"MEDIUM")),t.push(await this._check("MEM-005","Failure Memory",()=>this._fileExists(path.join(this.config.serverPath,"services","failure-memory.js")),"MEDIUM")),{status:this._systemStatus(t),checks:t}}async _checkExternal(){const t=[];return t.push(await this._check("EXT-001","Zoho Tokens",()=>{const t=path.join(this.config.serverPath,"data","zoho-tokens.json");return!this._fileExists(t)||this._isValidJson(t)},"LOW")),t.push(await this._check("EXT-002","Cartography",()=>this._dirExists(path.join(this.config.rootPath,"cartography")),"LOW")),t.push(await this._check("EXT-003","Notification Config",()=>{const t=path.join(this.config.serverPath,"data","notification-config.json");return!this._fileExists(t)||this._isValidJson(t)},"LOW")),t.push(await this._check("EXT-004","Health History",()=>{const t=path.join(this.config.serverPath,"data","health-history.json");return!this._fileExists(t)||this._isValidJson(t)},"LOW")),{status:this._systemStatus(t),checks:t}}async _check(t,s,e,i="MEDIUM",a=null){const h={id:t,name:s,criticality:i,status:"UNKNOWN",autoHeal:a,needsHumanEscalation:!1};try{const t=await e();h.status=t?"PASS":"FAIL",t||"CRITICAL"!==i||(h.needsHumanEscalation=!0)}catch(t){h.status="ERROR",h.error=t.message,h.needsHumanEscalation=!0}return h}_fileExists(t){try{return fs.existsSync(t)}catch{return!1}}_dirExists(t){try{return fs.existsSync(t)&&fs.statSync(t).isDirectory()}catch{return!1}}_isValidJson(t){try{return!!fs.existsSync(t)&&(JSON.parse(fs.readFileSync(t,"utf8")),!0)}catch{return!1}}_systemStatus(t){const s=t.filter(t=>"FAIL"===t.status||"ERROR"===t.status);return s.filter(t=>"CRITICAL"===t.criticality).length>0?"CRITICAL":s.length>0?"DEGRADED":"HEALTHY"}_calculateOverallStatus(t){return t.criticalIssues.length>0?"CRITICAL":t.failed>0?"DEGRADED":t.warnings>0?"WARNING":"HEALTHY"}getSummary(){return this.lastResults?{status:this.lastResults.overallStatus,passed:this.lastResults.passed,failed:this.lastResults.failed,total:this.lastResults.totalChecks,criticalIssues:this.lastResults.criticalIssues.length,humanActionNeeded:this.lastResults.humanEscalationNeeded.length,lastCheck:this.lastResults.timestamp}:{status:"NO_DATA",message:"No health check run yet"}}}module.exports=MasterHealthChecker;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const path=require("path"),AlertManager=require("./alert-manager.js"),MonitorStore=require("./monitor-store.js"),MonitorNotifier=require("./monitor-notifier.js"),{runScanPipeline:runScanPipeline}=require("./scan-runner.js"),{diffRuns:diffRuns}=require("./scan-diff.js"),{collectFiles:collectFiles}=require("./file-collector.js");function resolveIntervalMs(e,t){return"weekly"===e?6048e5:"custom"===e?Math.max(
|
|
1
|
+
const fs=require("fs"),path=require("path"),AlertManager=require("./alert-manager.js"),MonitorStore=require("./monitor-store.js"),MonitorNotifier=require("./monitor-notifier.js"),{runScanPipeline:runScanPipeline}=require("./scan-runner.js"),{diffRuns:diffRuns}=require("./scan-diff.js"),{collectFiles:collectFiles}=require("./file-collector.js"),MIN_INTERVAL_MS=3e5;function resolveIntervalMs(e,t){return"weekly"===e?6048e5:"custom"===e?Math.max(3e5,Number(t)||864e5):864e5}class MonitorService{constructor(e={}){this.store=e.store||new MonitorStore,this.timers=new Map}configureRepo({repoPath:e,frequency:t="daily",intervalMs:r,notification:o}){const n=path.resolve(e);try{if(!fs.statSync(n).isDirectory())throw new Error(`[MonitorService] repoPath is not a directory: ${n}`)}catch(e){if("ENOENT"===e.code)throw new Error(`[MonitorService] repoPath does not exist: ${n}`);throw e}const s=this.store.getRepoId(n),i=resolveIntervalMs(t,r);return this.store.upsertRepo({repoId:s,repoPath:n,frequency:t,intervalMs:i,notification:o,nextRunAt:new Date(Date.now()+i).toISOString()})}async runRepoScan(e){const t=path.resolve(e.repoPath),r=this._collectFiles(t),o=Date.now(),n=await runScanPipeline({rootPath:t,files:r}),s={repoId:e.repoId,repoPath:t,timestamp:(new Date).toISOString(),durationMs:Date.now()-o,frequency:e.frequency,metrics:n.metrics,summary:n.summary,issues:n.issues},i=this.store.getLatestRun(e.repoId),a=diffRuns(i,s);s.diff=a;const c=this.store.saveRun(e.repoId,s),l=this.store.upsertRepo({...e,lastRunAt:s.timestamp,nextRunAt:new Date(Date.now()+e.intervalMs).toISOString()}),u=new MonitorNotifier({alertManager:new AlertManager({rootPath:t,consoleOutput:!0,fileOutput:!0}),store:this.store,repoConfig:l});return s.notifications=await u.notify(a,s),{run:s,runPath:c,previousRun:i,diff:a}}start(e,{once:t=!1}={}){const r=async()=>{try{await this.runRepoScan(e)}catch(e){console.error("[THUBAN MONITOR] Scheduled scan failed")}};if(r(),t)return;const o=setInterval(r,e.intervalMs);return this.timers.set(e.repoId,o),o}stopAll(){for(const e of this.timers.values())clearInterval(e);this.timers.clear()}_collectFiles(e){return collectFiles(e)}}module.exports={MonitorService:MonitorService,resolveIntervalMs:resolveIntervalMs};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const fs=require("fs"),path=require("path"),os=require("os"),crypto=require("crypto"),THUBAN_DIR=path.join(os.homedir(),".thuban"),MONITOR_DIR=path.join(THUBAN_DIR,"monitor"),CONFIG_FILE=path.join(MONITOR_DIR,"config.json"),RUNS_DIR=path.join(MONITOR_DIR,"runs"),NOTIFICATIONS_FILE=path.join(MONITOR_DIR,"notifications.json");function ensureDir(
|
|
1
|
+
const fs=require("fs"),path=require("path"),os=require("os"),crypto=require("crypto"),THUBAN_DIR=path.join(os.homedir(),".thuban"),MONITOR_DIR=path.join(THUBAN_DIR,"monitor"),CONFIG_FILE=path.join(MONITOR_DIR,"config.json"),RUNS_DIR=path.join(MONITOR_DIR,"runs"),NOTIFICATIONS_FILE=path.join(MONITOR_DIR,"notifications.json");function ensureDir(t){fs.existsSync(t)||fs.mkdirSync(t,{recursive:!0})}function safeReadJson(t,e){try{return JSON.parse(fs.readFileSync(t,"utf-8"))}catch(t){return e}}function atomicWriteSync(t,e){const n=t+".tmp."+process.pid+"."+Date.now();try{fs.writeFileSync(n,e,"utf-8"),fs.renameSync(n,t)}catch(t){try{fs.unlinkSync(n)}catch{}throw t}}class MonitorStore{constructor(){ensureDir(THUBAN_DIR),ensureDir(MONITOR_DIR),ensureDir(RUNS_DIR)}getRepoId(t){return crypto.createHash("sha256").update(path.resolve(t)).digest("hex").substring(0,12)}loadConfig(){return safeReadJson(CONFIG_FILE,{version:1,repos:[]})}saveConfig(t){atomicWriteSync(CONFIG_FILE,JSON.stringify(t,null,2))}upsertRepo(t){const e=this.loadConfig(),n=t.repoId||this.getRepoId(t.repoPath),o={repoId:n,repoPath:path.resolve(t.repoPath),frequency:t.frequency||"daily",intervalMs:t.intervalMs,notification:t.notification||{channels:["inbox"]},createdAt:t.createdAt||(new Date).toISOString(),updatedAt:(new Date).toISOString(),enabled:!1!==t.enabled,lastRunAt:t.lastRunAt||null,nextRunAt:t.nextRunAt||null},r=e.repos.findIndex(t=>t.repoId===n);return r>=0?e.repos[r]={...e.repos[r],...o}:e.repos.push(o),this.saveConfig(e),o}listRepos(){return this.loadConfig().repos||[]}getRepo(t){const e=t?path.resolve(t):null;return this.listRepos().find(n=>n.repoId===t||n.repoPath===e)||null}getRepoRunDir(t){const e=path.join(RUNS_DIR,t);return ensureDir(e),e}saveRun(t,e){const n=this.getRepoRunDir(t),o=path.join(n,`${e.timestamp.replace(/[:.]/g,"-")}.json`);return atomicWriteSync(o,JSON.stringify(e,null,2)),o}listRuns(t){const e=this.getRepoRunDir(t);return fs.readdirSync(e).filter(t=>t.endsWith(".json")).sort().map(t=>safeReadJson(path.join(e,t),null)).filter(Boolean)}getLatestRun(t){const e=this.listRuns(t);return e.length?e[e.length-1]:null}appendNotification(t){const e=safeReadJson(NOTIFICATIONS_FILE,{notifications:[]});e.notifications.push(t),atomicWriteSync(NOTIFICATIONS_FILE,JSON.stringify(e,null,2))}getNotifications(t=20){return safeReadJson(NOTIFICATIONS_FILE,{notifications:[]}).notifications.slice(-t).reverse()}}module.exports=MonitorStore;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const{execFileSync:execFileSync}=require("child_process"),path=require("path"),HELPER_SCRIPT=path.join(__dirname,"python_ast_helper.py"),PYTHON_CANDIDATES="win32"===process.platform?["python","py","python3"]:["python3","python","py"],PROBE_TIMEOUT_MS=3e3,EXEC_TIMEOUT_MS=5e3,MAX_BUFFER=2097152;let cachedPythonBin;function resolvePythonBin(){if(void 0!==cachedPythonBin)return cachedPythonBin;for(const e of PYTHON_CANDIDATES)try{return execFileSync(e,["--version"],{stdio:"pipe",timeout:3e3}),cachedPythonBin=e,cachedPythonBin}catch(e){}return cachedPythonBin=null,cachedPythonBin}function _resetPythonBinCache(){cachedPythonBin=void 0}function snippet(e,n){return!n||n<1||n>e.length?"":e[n-1].trim().substring(0,100)}const RULES_BY_TYPE={unused_import:{id:"AST021",severity:"info",name:"Unused Import (Python AST)"},unused_variable:{id:"AST022",severity:"info",name:"Unused Variable (Python AST)"},high_complexity:{id:"AST023",severity:"warning",name:"High Cyclomatic Complexity (Python AST)"},dead_code:{id:"AST024",severity:"warning",name:"Unreachable Code (Python AST)"},bare_except:{id:"AST025",severity:"warning",name:"Bare Except Clause (Python AST)"},mutable_default_arg:{id:"AST026",severity:"warning",name:"Mutable Default Argument (Python AST)"}};function toIssue(e,n,t){const i=RULES_BY_TYPE[t.type];return i?{file:e,line:t.line||0,category:"quality",type:t.type,severity:i.severity,id:i.id,name:i.name,message:t.message,code:snippet(n,t.line),source:"python-ast"}:null}function analyze(e,n){const t=resolvePythonBin();if(!t)return[];let i,o;try{i=execFileSync(t,[HELPER_SCRIPT],{input:n,encoding:"utf8",timeout:5e3,maxBuffer:2097152,windowsHide:!0})}catch(e){return[]}try{o=JSON.parse(i)}catch(e){return[]}if(!o||!0!==o.ok||!Array.isArray(o.issues))return[];const r=n.split("\n"),a=[];for(const n of o.issues){const t=toIssue(e,r,n);t&&a.push(t)}return a}module.exports={analyze:analyze,_internal:{resolvePythonBin:resolvePythonBin,_resetPythonBinCache:_resetPythonBinCache,RULES_BY_TYPE:RULES_BY_TYPE}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const fs=require("fs"),path=require("path"),crypto=require("crypto"),EventEmitter=require("events"),{execFileSync:execFileSync}=require("child_process");class SecretScanner extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),ignorePatterns:e.ignorePatterns||["node_modules/**",".git/**","dist/**",".next/**","coverage/**"],maxFileSize:e.maxFileSize||1048576,entropyThreshold:e.entropyThreshold||4.2,minEntropyLength:e.minEntropyLength||20,includeGitHistory:!1!==e.includeGitHistory,...e},this.scanExtensions=new Set([".env",".js",".jsx",".ts",".tsx",".py",".json",".yaml",".yml",".toml",".ini",".conf",".config",".properties",".sh",".bash",".zsh"]),this.ignoredDirectories=["node_modules",".git","dist",".next","coverage",".venv","venv","__pycache__"],this.safeLinePatterns=[/process\.env\./i,/process\.env\[/i,/os\.environ/i,/sample/i,/placeholder/i,/dummy/i,/mock/i,/example/i,/replace[_-\s]?me/i,/replace[_-\s]?with/i,/local-dev-token/i,/changeme/i,/your[_-\s]?key/i,/your[_-\s]?(secret|token|password)/i,/<secret>/i,/<token>/i,/x{8,}/i],this.secretPatterns=[{id:"SECRET001",type:"Stripe Secret Key",severity:"critical",recommendation:"Move this key to a cloud secret manager and load it via environment variables.",pattern:/\bsk_(live|test)_[0-9a-zA-Z]{16,}\b/gi},{id:"SECRET002",type:"Stripe Publishable Key",severity:"high",recommendation:"Verify this publishable key is intended to be public; if not, move it to managed secrets.",pattern:/\bpk_(live|test)_[0-9a-zA-Z]{16,}\b/gi},{id:"SECRET003",type:"AWS Access Key",severity:"critical",recommendation:"Rotate the AWS credential immediately and store replacements in a cloud secret manager.",pattern:/\b(AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{16}\b/g},{id:"SECRET004",type:"GitHub Token",severity:"critical",recommendation:"Revoke the GitHub token and replace it with a managed secret or GitHub App credential.",pattern:/\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g},{id:"SECRET005",type:"Slack Token",severity:"critical",recommendation:"Rotate the Slack token and move it into a cloud secret manager.",pattern:/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g},{id:"SECRET006",type:"Twilio Credential",severity:"critical",recommendation:"Rotate the Twilio credential and store it in managed secrets.",pattern:/\b(SK|AC)[0-9a-fA-F]{32}\b/g},{id:"SECRET007",type:"Google API Key",severity:"critical",recommendation:"Restrict and rotate the Google API key, then move it to a cloud secret manager.",pattern:/\bAIza[0-9A-Za-z\-_]{35}\b/g},{id:"SECRET007B",type:"OpenAI Project Key",severity:"critical",recommendation:"Rotate the OpenAI project key and move it to a cloud secret manager.",pattern:/\bsk-proj-[A-Za-z0-9\-_]{20,}\b/g},{id:"SECRET007C",type:"Cartesia API Key",severity:"critical",recommendation:"Rotate the Cartesia API key and move it to a cloud secret manager.",pattern:/\bsk_car_[A-Za-z0-9]{16,}\b/g},{id:"SECRET008",type:"JWT Token",severity:"high",recommendation:"Do not hardcode JWTs; issue them dynamically and store signing secrets in managed secrets.",pattern:/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9._-]{8,}\.[A-Za-z0-9._-]{8,}\b/g},{id:"SECRET009",type:"Bearer Token",severity:"high",recommendation:"Move bearer tokens to a cloud secret manager and inject them at runtime.",pattern:/\bBearer\s+[A-Za-z0-9\-._~+/]+=*\b/g},{id:"SECRET010",type:"Private Key",severity:"critical",recommendation:"Remove the private key from source control, rotate it, and store it in a secret manager.",pattern:/-----BEGIN (RSA |DSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/g},{id:"SECRET011",type:"Database Connection String with Password",severity:"critical",recommendation:"Move the connection string to managed secrets and avoid embedding passwords in code or config.",pattern:/\b(?:postgres(?:ql)?|mysql|mssql|mongodb(?:\+srv)?|redis):\/\/[^:\s]+:[^@\s]+@[^'"`\s]+\b/gi},{id:"SECRET012",type:"Webhook Secret",severity:"high",recommendation:"Store webhook secrets in a cloud secret manager and inject them via environment variables.",pattern:/\b(?:whsec_[A-Za-z0-9]+|webhook[_-]?secret[_:=\s'"]+[A-Za-z0-9_\-]{12,})\b/gi},{id:"SECRET013",type:"Hardcoded Password or Secret Assignment",severity:"high",recommendation:"Replace hardcoded credentials with environment variables backed by a cloud secret manager.",pattern:/\b(?:password|passwd|pwd|secret|client_secret|api[_-]?secret|signing[_-]?secret)\b\s*[:=]\s*['"`][^'"`\s][^'"`]{5,}['"`]/gi},{id:"SECRET014",type:"Hardcoded API Key Assignment",severity:"high",recommendation:"Move API keys to a cloud secret manager and reference them through environment variables.",pattern:/\b(?:api[_-]?key|access[_-]?token|auth[_-]?token|token)\b\s*[:=]\s*['"`][A-Za-z0-9_\-./+=]{12,}['"`]/gi},{id:"SECRET015",type:".env File Present",severity:"medium",recommendation:"Ensure .env files are excluded from git and move production secrets to a cloud secret manager.",filePattern:/(^|[\\/])\.env(\.[^\\/]+)?$/i},{id:"SECRET016",type:"LiveKit API Secret",severity:"critical",recommendation:"Rotate the LiveKit API secret and store it in managed secrets.",pattern:/\b(?:LIVEKIT_API_SECRET|livekit[_-]?api[_-]?secret)\b\s*[:=]\s*['"`]?([A-Za-z0-9]{24,})['"`]?/gi}]}async scanAll(){const e=Date.now(),t=await this._discoverFiles(this.config.rootPath),i=[];for(const e of t)i.push(...await this.scanFile(e));return this.config.includeGitHistory&&i.push(...this.scanGitHistory()),{filesScanned:t.length,issues:i,summary:this._summarizeIssues(i),duration:Date.now()-e}}async scanFiles(e){const t=[];for(const i of e)t.push(...await this.scanFile(i));return this.config.includeGitHistory&&t.push(...this.scanGitHistory()),{filesScanned:e.length,issues:t,summary:this._summarizeIssues(t)}}async scanFile(e){const t=[],i=path.relative(this.config.rootPath,e).replace(/\\/g,"/");try{if(fs.statSync(e).size>this.config.maxFileSize)return t;const s=path.basename(e),n=fs.readFileSync(e,"utf8").split(/\r?\n/);for(const r of this.secretPatterns)if(r.filePattern&&r.filePattern.test(e)){if(this._shouldSkipLine(s,i,r))continue;this._isGitIgnored(i)||t.push(this._createIssue({file:e,line:1,type:r.type,severity:r.severity,recommendation:"Add this file to .gitignore and move any production secrets to a cloud secret manager.",id:r.id,match:s,category:"secret"}))}else if(r.pattern)for(let s=0;s<n.length;s++){const o=n[s];if(this._shouldSkipLine(o,i,r))continue;let a;for(r.pattern.lastIndex=0;null!==(a=r.pattern.exec(o));){const i=a[1]||a[0];this._isLikelyPlaceholder(i)||t.push(this._createIssue({file:e,line:s+1,column:a.index,type:r.type,severity:r.severity,recommendation:r.recommendation,id:r.id,match:a[0],category:"secret"}))}}return t.push(...this._scanEntropy(e,n,i)),this._dedupeIssues(t)}catch(t){return[{file:e,line:1,category:"secret",severity:"medium",id:"SECRET_SCAN_ERROR",type:"Secret Scan Error",recommendation:"Verify file permissions and encoding, then rerun the scan.",message:`Failed to scan file for secrets: ${t.message}`}]}}scanGitHistory(){try{const e=execFileSync("git",["log","-p","--all","--full-history","--","."],{cwd:this.config.rootPath,encoding:"utf8",maxBuffer:20971520}),t=[],i=e.split(/\r?\n/);let s=null,n=null;for(const e of i){if(e.startsWith("commit ")){s=e.replace("commit ","").trim();continue}if(e.startsWith("+++ b/")){n=e.replace("+++ b/","").trim();continue}if(!e.startsWith("+")||e.startsWith("+++"))continue;const i=e.slice(1);if(!this._shouldSkipLine(i,n||""))for(const e of this.secretPatterns){if(!e.pattern)continue;let r;for(e.pattern.lastIndex=0;null!==(r=e.pattern.exec(i));){const o=r[1]||r[0];this._isLikelyPlaceholder(o)||t.push({file:n||"unknown",line:null,category:"secret-history",severity:"medium"===e.severity?"high":e.severity,id:`${e.id}_HISTORY`,type:`${e.type} in Git History`,recommendation:"Purge the secret from git history, rotate it, and move it to a cloud secret manager.",message:`Potential secret remains in git history (${s||"unknown commit"})`,commit:s,code:i.trim().slice(0,140)})}}}return this._dedupeIssues(t)}catch(e){return[]}}_scanEntropy(e,t,i){const s=[],n=/['"`]([A-Za-z0-9+/=_\-]{20,})['"`]/g;for(let r=0;r<t.length;r++){const o=t[r];if(this._shouldSkipLine(o,i))continue;let a;for(n.lastIndex=0;null!==(a=n.exec(o));){const t=a[1];if(t.length<this.config.minEntropyLength)continue;if(!/[A-Z]/.test(t)||!/[a-z]/.test(t)||!/[0-9]/.test(t))continue;const i=this._calculateEntropy(t);i>=this.config.entropyThreshold&&s.push(this._createIssue({file:e,line:r+1,column:a.index,type:"High Entropy Secret Candidate",severity:"medium",recommendation:"Review this value, move it to a cloud secret manager if it is a credential, and reference it via environment variables.",id:"SECRET_ENTROPY",match:t,category:"secret",extraMessage:`High-entropy string detected (entropy ${i.toFixed(2)})`}))}}return s}_createIssue({file:e,line:t,column:i,type:s,severity:n,recommendation:r,id:o,match:a,category:c,extraMessage:l}){return{file:e,line:t,column:i,category:c,severity:n,id:o,type:s,recommendation:r,message:l||`${s} detected`,code:"string"==typeof a?a.slice(0,140):""}}async _discoverFiles(e,t=[]){let i=[];try{i=fs.readdirSync(e,{withFileTypes:!0})}catch{return t}for(const s of i){const i=path.join(e,s.name),n=path.relative(this.config.rootPath,i).replace(/\\/g,"/");if(this._shouldIgnore(n))continue;if(s.isDirectory()){await this._discoverFiles(i,t);continue}if(!s.isFile())continue;const r=path.extname(s.name).toLowerCase();(this.scanExtensions.has(r)||s.name.startsWith(".env"))&&t.push(i)}return t}_shouldIgnore(e){const t=e.replace(/\\/g,"/"),i=this.ignoredDirectories;for(const e of i)if(t===e||t.startsWith(`${e}/`)||t.includes(`/${e}/`))return!0;return this.config.ignorePatterns.some(e=>{const i=e.replace(/\\/g,"/"),s=i.replace(/\./g,"\\.").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*");return new RegExp(`^${s}$`).test(t)||t.startsWith(i.replace("/**",""))})}_shouldSkipLine(e,t,i=null){if(!e||!e.trim())return!0;const s=(t||"").replace(/\\/g,"/").toLowerCase();return!!(s.includes("__tests__")||s.includes("/test/")||s.includes("/tests/")||s.endsWith(".spec.js")||s.endsWith(".test.js")||s.endsWith(".spec.ts")||s.endsWith(".test.ts")||s.endsWith(".example")||s.endsWith(".env.example"))||(!i||"SECRET015"!==i.id)&&this.safeLinePatterns.some(t=>t.test(e))}_calculateEntropy(e){const t=new Map;for(const i of e)t.set(i,(t.get(i)||0)+1);let i=0;for(const s of t.values()){const t=s/e.length;i-=t*Math.log2(t)}return i}_isLikelyPlaceholder(e){return!!e&&[/replace[_-\s]?me/i,/replace[_-\s]?with/i,/example/i,/dummy/i,/placeholder/i,/changeme/i,/your[_-\s]?(key|secret|token|password)/i,/local-dev-token/i,/x{8,}/i].some(t=>t.test(e))}_isGitIgnored(e){try{return execFileSync("git",["check-ignore",e],{cwd:this.config.rootPath,stdio:"ignore"}),!0}catch{return!1}}_dedupeIssues(e){const t=new Set;return e.filter(e=>{const i=crypto.createHash("sha1").update(JSON.stringify([e.file,e.line,e.id,e.code,e.commit||""])).digest("hex");return!t.has(i)&&(t.add(i),!0)})}_summarizeIssues(e){return{total:e.length,bySeverity:{critical:e.filter(e=>"critical"===e.severity).length,high:e.filter(e=>"high"===e.severity).length,medium:e.filter(e=>"medium"===e.severity).length},byType:e.reduce((e,t)=>(e[t.type]=(e[t.type]||0)+1,e),{})}}}module.exports=SecretScanner;
|
|
1
|
+
const fs=require("fs"),path=require("path"),crypto=require("crypto"),EventEmitter=require("events"),{execFileSync:execFileSync}=require("child_process");class SecretScanner extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),ignorePatterns:e.ignorePatterns||["node_modules/**",".git/**","dist/**",".next/**","coverage/**"],maxFileSize:e.maxFileSize||1048576,entropyThreshold:e.entropyThreshold||4.2,minEntropyLength:e.minEntropyLength||20,includeGitHistory:!1!==e.includeGitHistory,...e},this.scanExtensions=new Set([".env",".js",".jsx",".ts",".tsx",".py",".json",".yaml",".yml",".toml",".ini",".conf",".config",".properties",".sh",".bash",".zsh"]),this.ignoredDirectories=["node_modules",".git","dist",".next","coverage",".venv","venv","__pycache__"],this.safeLinePatterns=[/process\.env\./i,/process\.env\[/i,/os\.environ/i,/sample/i,/placeholder/i,/dummy/i,/mock/i,/example/i,/replace[_-\s]?me/i,/replace[_-\s]?with/i,/local-dev-token/i,/changeme/i,/your[_-\s]?key/i,/your[_-\s]?(secret|token|password)/i,/<secret>/i,/<token>/i,/x{8,}/i],this.secretPatterns=[{id:"SECRET001",type:"Stripe Secret Key",severity:"critical",recommendation:"Move this key to a cloud secret manager and load it via environment variables.",pattern:/\bsk_(live|test)_[0-9a-zA-Z]{16,}\b/gi},{id:"SECRET002",type:"Stripe Publishable Key",severity:"high",recommendation:"Verify this publishable key is intended to be public; if not, move it to managed secrets.",pattern:/\bpk_(live|test)_[0-9a-zA-Z]{16,}\b/gi},{id:"SECRET003",type:"AWS Access Key",severity:"critical",recommendation:"Rotate the AWS credential immediately and store replacements in a cloud secret manager.",pattern:/\b(AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{16}\b/g},{id:"SECRET004",type:"GitHub Token",severity:"critical",recommendation:"Revoke the GitHub token and replace it with a managed secret or GitHub App credential.",pattern:/\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/g},{id:"SECRET005",type:"Slack Token",severity:"critical",recommendation:"Rotate the Slack token and move it into a cloud secret manager.",pattern:/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g},{id:"SECRET006",type:"Twilio Credential",severity:"critical",recommendation:"Rotate the Twilio credential and store it in managed secrets.",pattern:/\b(SK|AC)[0-9a-fA-F]{32}\b/g},{id:"SECRET007",type:"Google API Key",severity:"critical",recommendation:"Restrict and rotate the Google API key, then move it to a cloud secret manager.",pattern:/\bAIza[0-9A-Za-z\-_]{35}\b/g},{id:"SECRET007B",type:"OpenAI Project Key",severity:"critical",recommendation:"Rotate the OpenAI project key and move it to a cloud secret manager.",pattern:/\bsk-proj-[A-Za-z0-9\-_]{20,}\b/g},{id:"SECRET007C",type:"Cartesia API Key",severity:"critical",recommendation:"Rotate the Cartesia API key and move it to a cloud secret manager.",pattern:/\bsk_car_[A-Za-z0-9]{16,}\b/g},{id:"SECRET008",type:"JWT Token",severity:"high",recommendation:"Do not hardcode JWTs; issue them dynamically and store signing secrets in managed secrets.",pattern:/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9._-]{8,}\.[A-Za-z0-9._-]{8,}\b/g},{id:"SECRET009",type:"Bearer Token",severity:"high",recommendation:"Move bearer tokens to a cloud secret manager and inject them at runtime.",pattern:/\bBearer\s+[A-Za-z0-9\-._~+/]+=*\b/g},{id:"SECRET010",type:"Private Key",severity:"critical",recommendation:"Remove the private key from source control, rotate it, and store it in a secret manager.",pattern:/-----BEGIN (RSA |DSA |EC |OPENSSH |PGP )?PRIVATE KEY-----/g},{id:"SECRET011",type:"Database Connection String with Password",severity:"critical",recommendation:"Move the connection string to managed secrets and avoid embedding passwords in code or config.",pattern:/\b(?:postgres(?:ql)?|mysql|mssql|mongodb(?:\+srv)?|redis):\/\/[^:\s]+:[^@\s]+@[^'"`\s]+\b/gi},{id:"SECRET012",type:"Webhook Secret",severity:"high",recommendation:"Store webhook secrets in a cloud secret manager and inject them via environment variables.",pattern:/\b(?:whsec_[A-Za-z0-9]+|webhook[_-]?secret[_:=\s'"]+[A-Za-z0-9_\-]{12,})\b/gi},{id:"SECRET013",type:"Hardcoded Password or Secret Assignment",severity:"high",recommendation:"Replace hardcoded credentials with environment variables backed by a cloud secret manager.",pattern:/\b(?:password|passwd|pwd|secret|client_secret|api[_-]?secret|signing[_-]?secret)\b\s*[:=]\s*['"`][^'"`\s][^'"`]{5,}['"`]/gi},{id:"SECRET014",type:"Hardcoded API Key Assignment",severity:"high",recommendation:"Move API keys to a cloud secret manager and reference them through environment variables.",pattern:/\b(?:api[_-]?key|access[_-]?token|auth[_-]?token|token)\b\s*[:=]\s*['"`][A-Za-z0-9_\-./+=]{12,}['"`]/gi},{id:"SECRET015",type:".env File Present",severity:"medium",recommendation:"Ensure .env files are excluded from git and move production secrets to a cloud secret manager.",filePattern:/(^|[\\/])\.env(\.[^\\/]+)?$/i},{id:"SECRET016",type:"LiveKit API Secret",severity:"critical",recommendation:"Rotate the LiveKit API secret and store it in managed secrets.",pattern:/\b(?:LIVEKIT_API_SECRET|livekit[_-]?api[_-]?secret)\b\s*[:=]\s*['"`]?([A-Za-z0-9]{24,})['"`]?/gi}]}async scanAll(){const e=Date.now(),t=await this._discoverFiles(this.config.rootPath),i=[];for(const e of t)i.push(...await this.scanFile(e));return this.config.includeGitHistory&&i.push(...this.scanGitHistory()),{filesScanned:t.length,issues:i,summary:this._summarizeIssues(i),duration:Date.now()-e}}async scanFiles(e){const t=[];for(const i of e)t.push(...await this.scanFile(i));return this.config.includeGitHistory&&t.push(...this.scanGitHistory()),{filesScanned:e.length,issues:t,summary:this._summarizeIssues(t)}}async scanFile(e){const t=[],i=path.relative(this.config.rootPath,e).replace(/\\/g,"/");try{if(fs.statSync(e).size>this.config.maxFileSize)return t;const s=path.basename(e),n=fs.readFileSync(e,"utf8").split(/\r?\n/);for(const r of this.secretPatterns)if(r.filePattern&&r.filePattern.test(e)){if(this._shouldSkipLine(s,i,r))continue;this._isGitIgnored(i)||t.push(this._createIssue({file:e,line:1,type:r.type,severity:r.severity,recommendation:"Add this file to .gitignore and move any production secrets to a cloud secret manager.",id:r.id,match:s,category:"secret"}))}else if(r.pattern)for(let s=0;s<n.length;s++){const o=n[s];if(this._shouldSkipLine(o,i,r))continue;let a;for(r.pattern.lastIndex=0;null!==(a=r.pattern.exec(o));){const i=a[1]||a[0];this._isLikelyPlaceholder(i)||t.push(this._createIssue({file:e,line:s+1,column:a.index,type:r.type,severity:r.severity,recommendation:r.recommendation,id:r.id,match:a[0],category:"secret"}))}}return t.push(...this._scanEntropy(e,n,i)),this._dedupeIssues(t)}catch(t){return[{file:e,line:1,category:"secret",severity:"medium",id:"SECRET_SCAN_ERROR",type:"Secret Scan Error",recommendation:"Verify file permissions and encoding, then rerun the scan.",message:"Failed to scan file for secrets: "+("ENOENT"===t.code?"File not found":"EACCES"===t.code?"Permission denied":"Scan error")}]}}scanGitHistory(){try{const e=execFileSync("git",["log","-p","--all","--full-history","--","."],{cwd:this.config.rootPath,encoding:"utf8",maxBuffer:20971520}),t=[],i=e.split(/\r?\n/);let s=null,n=null;for(const e of i){if(e.startsWith("commit ")){s=e.replace("commit ","").trim();continue}if(e.startsWith("+++ b/")){n=e.replace("+++ b/","").trim();continue}if(!e.startsWith("+")||e.startsWith("+++"))continue;const i=e.slice(1);if(!this._shouldSkipLine(i,n||""))for(const e of this.secretPatterns){if(!e.pattern)continue;let r;for(e.pattern.lastIndex=0;null!==(r=e.pattern.exec(i));){const o=r[1]||r[0];this._isLikelyPlaceholder(o)||t.push({file:n||"unknown",line:null,category:"secret-history",severity:"medium"===e.severity?"high":e.severity,id:`${e.id}_HISTORY`,type:`${e.type} in Git History`,recommendation:"Purge the secret from git history, rotate it, and move it to a cloud secret manager.",message:`Potential secret remains in git history (${s||"unknown commit"})`,commit:s,code:i.trim().slice(0,140)})}}}return this._dedupeIssues(t)}catch(e){return[]}}_scanEntropy(e,t,i){const s=[],n=/['"`]([A-Za-z0-9+/=_\-]{20,})['"`]/g;for(let r=0;r<t.length;r++){const o=t[r];if(this._shouldSkipLine(o,i))continue;let a;for(n.lastIndex=0;null!==(a=n.exec(o));){const t=a[1];if(t.length<this.config.minEntropyLength)continue;if(!/[A-Z]/.test(t)||!/[a-z]/.test(t)||!/[0-9]/.test(t))continue;const i=this._calculateEntropy(t);i>=this.config.entropyThreshold&&s.push(this._createIssue({file:e,line:r+1,column:a.index,type:"High Entropy Secret Candidate",severity:"medium",recommendation:"Review this value, move it to a cloud secret manager if it is a credential, and reference it via environment variables.",id:"SECRET_ENTROPY",match:t,category:"secret",extraMessage:`High-entropy string detected (entropy ${i.toFixed(2)})`}))}}return s}_createIssue({file:e,line:t,column:i,type:s,severity:n,recommendation:r,id:o,match:a,category:c,extraMessage:l}){return{file:e,line:t,column:i,category:c,severity:n,id:o,type:s,recommendation:r,message:l||`${s} detected`,code:"string"==typeof a?a.slice(0,140):""}}async _discoverFiles(e,t=[]){let i=[];try{i=fs.readdirSync(e,{withFileTypes:!0})}catch{return t}for(const s of i){const i=path.join(e,s.name),n=path.relative(this.config.rootPath,i).replace(/\\/g,"/");if(this._shouldIgnore(n))continue;if(s.isDirectory()){await this._discoverFiles(i,t);continue}if(!s.isFile())continue;const r=path.extname(s.name).toLowerCase();(this.scanExtensions.has(r)||s.name.startsWith(".env"))&&t.push(i)}return t}_shouldIgnore(e){const t=e.replace(/\\/g,"/"),i=this.ignoredDirectories;for(const e of i)if(t===e||t.startsWith(`${e}/`)||t.includes(`/${e}/`))return!0;return this.config.ignorePatterns.some(e=>{const i=e.replace(/\\/g,"/"),s=i.replace(/\./g,"\\.").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*");return new RegExp(`^${s}$`).test(t)||t.startsWith(i.replace("/**",""))})}_shouldSkipLine(e,t,i=null){if(!e||!e.trim())return!0;const s=(t||"").replace(/\\/g,"/").toLowerCase();return!!(s.includes("__tests__")||s.includes("/test/")||s.includes("/tests/")||s.endsWith(".spec.js")||s.endsWith(".test.js")||s.endsWith(".spec.ts")||s.endsWith(".test.ts")||s.endsWith(".example")||s.endsWith(".env.example"))||(!i||"SECRET015"!==i.id)&&this.safeLinePatterns.some(t=>t.test(e))}_calculateEntropy(e){const t=new Map;for(const i of e)t.set(i,(t.get(i)||0)+1);let i=0;for(const s of t.values()){const t=s/e.length;i-=t*Math.log2(t)}return i}_isLikelyPlaceholder(e){return!!e&&[/replace[_-\s]?me/i,/replace[_-\s]?with/i,/example/i,/dummy/i,/placeholder/i,/changeme/i,/your[_-\s]?(key|secret|token|password)/i,/local-dev-token/i,/x{8,}/i].some(t=>t.test(e))}_isGitIgnored(e){try{return execFileSync("git",["check-ignore",e],{cwd:this.config.rootPath,stdio:"ignore"}),!0}catch{return!1}}_dedupeIssues(e){const t=new Set;return e.filter(e=>{const i=crypto.createHash("sha1").update(JSON.stringify([e.file,e.line,e.id,e.code,e.commit||""])).digest("hex");return!t.has(i)&&(t.add(i),!0)})}_summarizeIssues(e){return{total:e.length,bySeverity:{critical:e.filter(e=>"critical"===e.severity).length,high:e.filter(e=>"high"===e.severity).length,medium:e.filter(e=>"medium"===e.severity).length},byType:e.reduce((e,t)=>(e[t.type]=(e[t.type]||0)+1,e),{})}}}module.exports=SecretScanner;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const fs=require("fs"),path=require("path"),EventEmitter=require("events");let FileWatcher,CodeScanner,DriftDetector,HealthChecker,AlertManager,SecretScanner;class SentinelCore extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),watchPatterns:e.watchPatterns||["**/*.js","**/*.ts","**/*.json"],ignorePatterns:e.ignorePatterns||["node_modules/**",".git/**","dist/**",".orion/snapshots/**"],scanIntervalHours:e.scanIntervalHours||6,dailyReportHour:e.dailyReportHour||8,weeklyReportDay:e.weeklyReportDay||1,enableRealtime:!1!==e.enableRealtime,enableScheduled:!1!==e.enableScheduled,alertThreshold:e.alertThreshold||"warning",...e},this.state={isRunning:!1,lastFullScan:null,lastHealthCheck:null,lastDriftCheck:null,filesWatched:0,issuesFound:[],metrics:{totalScans:0,issuesDetected:0,issuesResolved:0,averageScanTime:0}},this.components={fileWatcher:null,codeScanner:null,driftDetector:null,healthChecker:null,alertManager:null},this.scheduledJobs=[]}async initialize(){console.log("[SENTINEL] Initializing Sentinel Core...");try{return FileWatcher=require("./file-watcher.js"),CodeScanner=require("./code-scanner.js"),DriftDetector=require("./drift-detector.js"),HealthChecker=require("./health-checker.js"),AlertManager=require("./alert-manager.js"),SecretScanner=require("./secret-scanner.js"),this.components.fileWatcher=new FileWatcher(this.config),this.components.codeScanner=new CodeScanner(this.config),this.components.driftDetector=new DriftDetector(this.config),this.components.healthChecker=new HealthChecker(this.config),this.components.alertManager=new AlertManager(this.config),this.components.secretScanner=new SecretScanner(this.config),this._setupEventHandlers(),console.log("[SENTINEL] All components initialized"),{success:!0}}catch(e){return console.error("[SENTINEL] Initialization failed:",e.message),{success:!1,error:e.message}}}async start(){if(this.state.isRunning)return console.log("[SENTINEL] Already running"),{success:!0,message:"Already running"};console.log("[SENTINEL] Starting Sentinel monitoring...");try{this.config.enableRealtime&&this.components.fileWatcher&&(await this.components.fileWatcher.start(),this.state.filesWatched=this.components.fileWatcher.getWatchedCount()),this.config.enableScheduled&&this._setupScheduledJobs(),await this.runHealthCheck();const e=await this.runFullScan();return this.state.isRunning=!0,this.emit("started",{timestamp:(new Date).toISOString()}),console.log("[SENTINEL] Monitoring active"),console.log(`[SENTINEL] Watching ${this.state.filesWatched} files`),console.log(`[SENTINEL] Initial scan found ${e.issues?.length||0} issues`),{success:!0,filesWatched:this.state.filesWatched,initialIssues:e.issues?.length||0}}catch(e){return console.error("[SENTINEL] Start failed:",e.message),{success:!1,error:e.message}}}async stop(){return this.state.isRunning?(console.log("[SENTINEL] Stopping Sentinel monitoring..."),this.components.fileWatcher&&await this.components.fileWatcher.stop(),this.scheduledJobs.forEach(e=>clearInterval(e)),this.scheduledJobs=[],this.state.isRunning=!1,this.emit("stopped",{timestamp:(new Date).toISOString()}),console.log("[SENTINEL] Monitoring stopped"),{success:!0}):{success:!0,message:"Not running"}}async runFullScan(e={}){const t=Date.now();console.log("[SENTINEL] Starting full codebase scan...");const s={timestamp:(new Date).toISOString(),duration:0,filesScanned:0,issues:[],summary:{}};try{if(!this.components.codeScanner)throw new Error("Code scanner not initialized");const e=await this.components.codeScanner.scanAll();if(s.filesScanned=e.filesScanned||0,s.issues.push(...e.issues||[]),this.components.secretScanner){const e=await this.components.secretScanner.scanAll();s.filesScanned+=e.filesScanned||0,s.issues.push(...e.issues||[])}if(this.components.driftDetector){const e=await this.components.driftDetector.detectAll();s.issues.push(...e.issues||[]),this.state.lastDriftCheck=(new Date).toISOString()}return s.duration=Date.now()-t,this.state.lastFullScan=s.timestamp,this.state.metrics.totalScans++,this.state.metrics.issuesDetected+=s.issues.length,this.state.metrics.averageScanTime=(this.state.metrics.averageScanTime*(this.state.metrics.totalScans-1)+s.duration)/this.state.metrics.totalScans,s.summary=this._generateScanSummary(s.issues),this.state.issuesFound=s.issues,await this._processIssues(s.issues),console.log(`[SENTINEL] Scan complete: ${s.filesScanned} files, ${s.issues.length} issues, ${s.duration}ms`),this.emit("scanComplete",s),s}catch(e){return console.error("[SENTINEL] Full scan failed:",e.message),s.error=e.message,s}}async runQuickScan(e){if(!this.components.codeScanner)return{success:!1,error:"Code scanner not initialized"};console.log(`[SENTINEL] Quick scan on ${e.length} files...`);const t=await this.components.codeScanner.scanFiles(e);return t.issues&&t.issues.length>0&&await this._processIssues(t.issues),t}async runHealthCheck(){if(console.log("[SENTINEL] Running health check..."),!this.components.healthChecker)return{success:!1,error:"Health checker not initialized"};const e=await this.components.healthChecker.checkAll();return this.state.lastHealthCheck=(new Date).toISOString(),e.unhealthySystems&&e.unhealthySystems.length>0&&await(this.components.alertManager?.sendAlert({level:"warning",title:"System Health Issues Detected",message:`${e.unhealthySystems.length} systems are unhealthy`,details:e.unhealthySystems})),this.emit("healthCheck",e),e}async generateDailyDigest(){console.log("[SENTINEL] Generating daily digest...");const e={date:(new Date).toISOString().split("T")[0],timestamp:(new Date).toISOString(),summary:{issuesFound:this.state.issuesFound.length,criticalIssues:this.state.issuesFound.filter(e=>"critical"===e.severity).length,warningIssues:this.state.issuesFound.filter(e=>"warning"===e.severity).length,filesWatched:this.state.filesWatched,lastScan:this.state.lastFullScan,lastHealthCheck:this.state.lastHealthCheck},topIssues:this.state.issuesFound.sort((e,t)=>this._severityScore(t.severity)-this._severityScore(e.severity)).slice(0,10),metrics:this.state.metrics,recommendations:this._generateRecommendations()};return this.components.alertManager&&await this.components.alertManager.sendDigest(e),this.emit("dailyDigest",e),e}async generateWeeklyReport(){console.log("[SENTINEL] Generating weekly report...");const e={weekOf:(new Date).toISOString().split("T")[0],timestamp:(new Date).toISOString(),summary:this.state.metrics,issuesTrend:this._calculateTrend(),codeQuality:await this._assessCodeQuality(),technicalDebt:await this._assessTechnicalDebt(),recommendations:this._generateRecommendations(),fullIssueList:this.state.issuesFound};return this.components.alertManager&&await this.components.alertManager.sendWeeklyReport(e),this.emit("weeklyReport",e),e}getStatus(){return{isRunning:this.state.isRunning,config:this.config,state:{...this.state,componentsLoaded:{fileWatcher:!!this.components.fileWatcher,codeScanner:!!this.components.codeScanner,driftDetector:!!this.components.driftDetector,healthChecker:!!this.components.healthChecker,alertManager:!!this.components.alertManager}},uptime:this.state.isRunning?Date.now()-new Date(this.state.lastFullScan).getTime():0}}getIssues(e={}){let t=[...this.state.issuesFound];return e.severity&&(t=t.filter(t=>t.severity===e.severity)),e.category&&(t=t.filter(t=>t.category===e.category)),e.file&&(t=t.filter(t=>t.file&&t.file.includes(e.file))),t}_setupEventHandlers(){this.components.fileWatcher&&(this.components.fileWatcher.on("change",async e=>{console.log(`[SENTINEL] File changed: ${e.filePath}`),await this.runQuickScan([e.filePath])}),this.components.fileWatcher.on("error",e=>{console.error("[SENTINEL] File watcher error:",e)})),this.components.codeScanner&&this.components.codeScanner.on("issueFound",async e=>{await this._processIssues([e])})}_setupScheduledJobs(){const e=60*this.config.scanIntervalHours*60*1e3;this.scheduledJobs.push(setInterval(()=>this.runFullScan(),e)),this.scheduledJobs.push(setInterval(()=>this.runHealthCheck(),36e5)),this.scheduledJobs.push(setInterval(()=>{const e=new Date;e.getHours()===this.config.dailyReportHour&&e.getMinutes()<5&&this.generateDailyDigest()},36e5)),this.scheduledJobs.push(setInterval(()=>{const e=new Date;e.getDay()===this.config.weeklyReportDay&&e.getHours()===this.config.dailyReportHour&&e.getMinutes()<5&&this.generateWeeklyReport()},36e5)),console.log(`[SENTINEL] Scheduled jobs: scan every ${this.config.scanIntervalHours}h, daily digest at ${this.config.dailyReportHour}:00`)}async _processIssues(e){for(const t of e)this._meetsThreshold(t.severity)&&this.components.alertManager&&await this.components.alertManager.sendAlert({level:t.severity,title:`[${t.category}] ${t.title||t.message}`,message:t.message,file:t.file,line:t.line,details:t})}_meetsThreshold(e){const t=["info","warning","error","critical"];return t.indexOf(e)>=t.indexOf(this.config.alertThreshold)}_severityScore(e){return{info:1,warning:2,error:3,critical:4}[e]||0}_generateScanSummary(e){return{total:e.length,bySeverity:{critical:e.filter(e=>"critical"===e.severity).length,error:e.filter(e=>"error"===e.severity).length,warning:e.filter(e=>"warning"===e.severity).length,info:e.filter(e=>"info"===e.severity).length},byCategory:e.reduce((e,t)=>(e[t.category]=(e[t.category]||0)+1,e),{})}}_generateRecommendations(){const e=[],t=this.state.issuesFound,s=t.filter(e=>"security"===e.category);s.length>0&&e.push({priority:"high",category:"security",message:`Address ${s.length} security issues before deploying`,issues:s.slice(0,5)});const n=t.filter(e=>"quality"===e.category);n.length>10&&e.push({priority:"medium",category:"quality",message:`Consider refactoring - ${n.length} quality issues detected`,issues:n.slice(0,5)});const i=t.filter(e=>"drift"===e.category);return i.length>0&&e.push({priority:"medium",category:"drift",message:`${i.length} files have drifted from their declared contracts`,issues:i.slice(0,5)}),e}_calculateTrend(){return{direction:"stable",change:0,note:"Trend analysis requires historical data"}}async _assessCodeQuality(){return{score:75,grade:"B",factors:{complexity:70,maintainability:80,documentation:65,testCoverage:40}}}async _assessTechnicalDebt(){return{estimatedHours:40,categories:{refactoring:20,documentation:10,testing:10},priorityItems:[]}}}function createSentinel(e={}){return new SentinelCore(e)}async function main(){const e=createSentinel({rootPath:path.resolve(__dirname,".."),enableRealtime:!0,enableScheduled:!1}),t=await e.initialize();t.success||(console.error("Failed to initialize Sentinel:",t.error),process.exit(1)),await e.start(),console.log("Sentinel status:",e.getStatus()),process.on("SIGINT",async()=>{console.log("\nShutting down Sentinel..."),await e.stop(),process.exit(0)})}require.main===module&&main().catch(console.error),module.exports={SentinelCore:SentinelCore,createSentinel:createSentinel};
|
|
1
|
+
const fs=require("fs"),path=require("path"),EventEmitter=require("events");let FileWatcher,CodeScanner,DriftDetector,HealthChecker,AlertManager,SecretScanner;class SentinelCore extends EventEmitter{constructor(e={}){super();const t={...e};delete t.__proto__,delete t.constructor,delete t.prototype,this.config={rootPath:t.rootPath||process.cwd(),watchPatterns:t.watchPatterns||["**/*.js","**/*.ts","**/*.json"],ignorePatterns:t.ignorePatterns||["node_modules/**",".git/**","dist/**",".orion/snapshots/**"],scanIntervalHours:t.scanIntervalHours||6,dailyReportHour:t.dailyReportHour||8,weeklyReportDay:t.weeklyReportDay||1,enableRealtime:!1!==t.enableRealtime,enableScheduled:!1!==t.enableScheduled,alertThreshold:t.alertThreshold||"warning"},this.state={isRunning:!1,lastFullScan:null,lastHealthCheck:null,lastDriftCheck:null,filesWatched:0,issuesFound:[],metrics:{totalScans:0,issuesDetected:0,issuesResolved:0,averageScanTime:0}},this.components={fileWatcher:null,codeScanner:null,driftDetector:null,healthChecker:null,alertManager:null},this.scheduledJobs=[]}async initialize(){console.log("[SENTINEL] Initializing Sentinel Core...");try{return FileWatcher=require("./file-watcher.js"),CodeScanner=require("./code-scanner.js"),DriftDetector=require("./drift-detector.js"),HealthChecker=require("./health-checker.js"),AlertManager=require("./alert-manager.js"),SecretScanner=require("./secret-scanner.js"),this.components.fileWatcher=new FileWatcher(this.config),this.components.codeScanner=new CodeScanner(this.config),this.components.driftDetector=new DriftDetector(this.config),this.components.healthChecker=new HealthChecker(this.config),this.components.alertManager=new AlertManager(this.config),this.components.secretScanner=new SecretScanner(this.config),this._setupEventHandlers(),console.log("[SENTINEL] All components initialized"),{success:!0}}catch(e){return console.error("[SENTINEL] Initialization failed"),{success:!1,error:"Initialization failed"}}}async start(){if(this.state.isRunning)return console.log("[SENTINEL] Already running"),{success:!0,message:"Already running"};console.log("[SENTINEL] Starting Sentinel monitoring...");try{this.config.enableRealtime&&this.components.fileWatcher&&(await this.components.fileWatcher.start(),this.state.filesWatched=this.components.fileWatcher.getWatchedCount()),this.config.enableScheduled&&this._setupScheduledJobs(),await this.runHealthCheck();const e=await this.runFullScan();return this.state.isRunning=!0,this.emit("started",{timestamp:(new Date).toISOString()}),console.log("[SENTINEL] Monitoring active"),console.log(`[SENTINEL] Watching ${this.state.filesWatched} files`),console.log(`[SENTINEL] Initial scan found ${e.issues?.length||0} issues`),{success:!0,filesWatched:this.state.filesWatched,initialIssues:e.issues?.length||0}}catch(e){return console.error("[SENTINEL] Start failed"),{success:!1,error:"Start failed"}}}async stop(){return this.state.isRunning?(console.log("[SENTINEL] Stopping Sentinel monitoring..."),this.components.fileWatcher&&await this.components.fileWatcher.stop(),this.scheduledJobs.forEach(e=>clearInterval(e)),this.scheduledJobs=[],this.state.isRunning=!1,this.emit("stopped",{timestamp:(new Date).toISOString()}),console.log("[SENTINEL] Monitoring stopped"),{success:!0}):{success:!0,message:"Not running"}}async runFullScan(e={}){const t=Date.now();console.log("[SENTINEL] Starting full codebase scan...");const s={timestamp:(new Date).toISOString(),duration:0,filesScanned:0,issues:[],summary:{}};try{if(!this.components.codeScanner)throw new Error("Code scanner not initialized");const e=await this.components.codeScanner.scanAll();if(s.filesScanned=e.filesScanned||0,s.issues.push(...e.issues||[]),this.components.secretScanner){const e=await this.components.secretScanner.scanAll();s.filesScanned+=e.filesScanned||0,s.issues.push(...e.issues||[])}if(this.components.driftDetector){const e=await this.components.driftDetector.detectAll();s.issues.push(...e.issues||[]),this.state.lastDriftCheck=(new Date).toISOString()}return s.duration=Date.now()-t,this.state.lastFullScan=s.timestamp,this.state.metrics.totalScans++,this.state.metrics.issuesDetected+=s.issues.length,this.state.metrics.averageScanTime=(this.state.metrics.averageScanTime*(this.state.metrics.totalScans-1)+s.duration)/this.state.metrics.totalScans,s.summary=this._generateScanSummary(s.issues),this.state.issuesFound=s.issues,await this._processIssues(s.issues),console.log(`[SENTINEL] Scan complete: ${s.filesScanned} files, ${s.issues.length} issues, ${s.duration}ms`),this.emit("scanComplete",s),s}catch(e){return console.error("[SENTINEL] Full scan failed"),s.error="Scan failed",s}}async runQuickScan(e){if(!this.components.codeScanner)return{success:!1,error:"Code scanner not initialized"};console.log(`[SENTINEL] Quick scan on ${e.length} files...`);const t=await this.components.codeScanner.scanFiles(e);return t.issues&&t.issues.length>0&&await this._processIssues(t.issues),t}async runHealthCheck(){if(console.log("[SENTINEL] Running health check..."),!this.components.healthChecker)return{success:!1,error:"Health checker not initialized"};const e=await this.components.healthChecker.checkAll();return this.state.lastHealthCheck=(new Date).toISOString(),e.unhealthySystems&&e.unhealthySystems.length>0&&await(this.components.alertManager?.sendAlert({level:"warning",title:"System Health Issues Detected",message:`${e.unhealthySystems.length} systems are unhealthy`,details:e.unhealthySystems})),this.emit("healthCheck",e),e}async generateDailyDigest(){console.log("[SENTINEL] Generating daily digest...");const e={date:(new Date).toISOString().split("T")[0],timestamp:(new Date).toISOString(),summary:{issuesFound:this.state.issuesFound.length,criticalIssues:this.state.issuesFound.filter(e=>"critical"===e.severity).length,warningIssues:this.state.issuesFound.filter(e=>"warning"===e.severity).length,filesWatched:this.state.filesWatched,lastScan:this.state.lastFullScan,lastHealthCheck:this.state.lastHealthCheck},topIssues:this.state.issuesFound.sort((e,t)=>this._severityScore(t.severity)-this._severityScore(e.severity)).slice(0,10),metrics:this.state.metrics,recommendations:this._generateRecommendations()};return this.components.alertManager&&await this.components.alertManager.sendDigest(e),this.emit("dailyDigest",e),e}async generateWeeklyReport(){console.log("[SENTINEL] Generating weekly report...");const e={weekOf:(new Date).toISOString().split("T")[0],timestamp:(new Date).toISOString(),summary:this.state.metrics,issuesTrend:this._calculateTrend(),codeQuality:await this._assessCodeQuality(),technicalDebt:await this._assessTechnicalDebt(),recommendations:this._generateRecommendations(),fullIssueList:this.state.issuesFound};return this.components.alertManager&&await this.components.alertManager.sendWeeklyReport(e),this.emit("weeklyReport",e),e}getStatus(){return{isRunning:this.state.isRunning,config:this.config,state:{...this.state,componentsLoaded:{fileWatcher:!!this.components.fileWatcher,codeScanner:!!this.components.codeScanner,driftDetector:!!this.components.driftDetector,healthChecker:!!this.components.healthChecker,alertManager:!!this.components.alertManager}},uptime:this.state.isRunning?Date.now()-new Date(this.state.lastFullScan).getTime():0}}getIssues(e={}){let t=[...this.state.issuesFound];return e.severity&&(t=t.filter(t=>t.severity===e.severity)),e.category&&(t=t.filter(t=>t.category===e.category)),e.file&&(t=t.filter(t=>t.file&&t.file.includes(e.file))),t}_setupEventHandlers(){this.components.fileWatcher&&(this.components.fileWatcher.on("change",async e=>{console.log(`[SENTINEL] File changed: ${e.filePath}`),await this.runQuickScan([e.filePath])}),this.components.fileWatcher.on("error",e=>{console.error("[SENTINEL] File watcher error:",e)})),this.components.codeScanner&&this.components.codeScanner.on("issueFound",async e=>{await this._processIssues([e])})}_setupScheduledJobs(){const e=60*this.config.scanIntervalHours*60*1e3;this.scheduledJobs.push(setInterval(()=>this.runFullScan(),e)),this.scheduledJobs.push(setInterval(()=>this.runHealthCheck(),36e5)),this.scheduledJobs.push(setInterval(()=>{const e=new Date;e.getHours()===this.config.dailyReportHour&&e.getMinutes()<5&&this.generateDailyDigest()},36e5)),this.scheduledJobs.push(setInterval(()=>{const e=new Date;e.getDay()===this.config.weeklyReportDay&&e.getHours()===this.config.dailyReportHour&&e.getMinutes()<5&&this.generateWeeklyReport()},36e5)),console.log(`[SENTINEL] Scheduled jobs: scan every ${this.config.scanIntervalHours}h, daily digest at ${this.config.dailyReportHour}:00`)}async _processIssues(e){for(const t of e)this._meetsThreshold(t.severity)&&this.components.alertManager&&await this.components.alertManager.sendAlert({level:t.severity,title:`[${t.category}] ${t.title||t.message}`,message:t.message,file:t.file,line:t.line,details:t})}_meetsThreshold(e){const t=["info","warning","error","critical"];return t.indexOf(e)>=t.indexOf(this.config.alertThreshold)}_severityScore(e){return{info:1,warning:2,error:3,critical:4}[e]||0}_generateScanSummary(e){return{total:e.length,bySeverity:{critical:e.filter(e=>"critical"===e.severity).length,error:e.filter(e=>"error"===e.severity).length,warning:e.filter(e=>"warning"===e.severity).length,info:e.filter(e=>"info"===e.severity).length},byCategory:e.reduce((e,t)=>(e[t.category]=(e[t.category]||0)+1,e),{})}}_generateRecommendations(){const e=[],t=this.state.issuesFound,s=t.filter(e=>"security"===e.category);s.length>0&&e.push({priority:"high",category:"security",message:`Address ${s.length} security issues before deploying`,issues:s.slice(0,5)});const n=t.filter(e=>"quality"===e.category);n.length>10&&e.push({priority:"medium",category:"quality",message:`Consider refactoring - ${n.length} quality issues detected`,issues:n.slice(0,5)});const i=t.filter(e=>"drift"===e.category);return i.length>0&&e.push({priority:"medium",category:"drift",message:`${i.length} files have drifted from their declared contracts`,issues:i.slice(0,5)}),e}_calculateTrend(){return{direction:"stable",change:0,note:"Trend analysis requires historical data"}}async _assessCodeQuality(){return{score:75,grade:"B",factors:{complexity:70,maintainability:80,documentation:65,testCoverage:40}}}async _assessTechnicalDebt(){return{estimatedHours:40,categories:{refactoring:20,documentation:10,testing:10},priorityItems:[]}}}function createSentinel(e={}){return new SentinelCore(e)}async function main(){const e=createSentinel({rootPath:path.resolve(__dirname,".."),enableRealtime:!0,enableScheduled:!1}),t=await e.initialize();t.success||(console.error("Failed to initialize Sentinel:",t.error),process.exit(1)),await e.start(),console.log("Sentinel status:",e.getStatus()),process.on("SIGINT",async()=>{console.log("\nShutting down Sentinel..."),await e.stop(),process.exit(0)})}require.main===module&&main().catch(console.error),module.exports={SentinelCore:SentinelCore,createSentinel:createSentinel};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const parser=require("@babel/parser"),path=require("path"),fs=require("fs"),{joinLogicalStatements:joinLogicalStatements,extractBalanced:extractBalanced,splitTopLevelArgs:splitTopLevelArgs,isDynamicPyString:isDynamicPyString}=require("./ast-analyzer")._internal,BABEL_PLUGINS=["estree","typescript","jsx","classProperties","decorators-legacy"],MAX_TAINT_FILE_SIZE=2e4,MAX_FIXPOINT_ITERATIONS=10;function walk(e,t){if(e&&"object"==typeof e&&"string"==typeof e.type){t(e);for(const n in e){if("loc"===n||"range"===n||"start"===n||"end"===n)continue;const i=e[n];if(Array.isArray(i))for(const e of i)e&&"object"==typeof e&&"string"==typeof e.type&&walk(e,t);else i&&"object"==typeof i&&"string"==typeof i.type&&walk(i,t)}}}function lineOf(e){return e&&e.loc?e.loc.start.line:0}function snippet(e,t){return!t||t<1||t>e.length?"":e[t-1].trim().substring(0,100)}function mkTaintIssue(e,t,n,i){const r={file:e,line:t,category:"security",type:i.type,severity:i.severity||"critical",id:i.id,name:i.name,message:i.message,code:snippet(n,t),source:"taint",taintPath:i.taintPath};return i.crossFile&&(r.crossFile=!0,r.sourceFile=i.sourceFile),r}function fileTooLargeIssue(e,t){return{file:e,line:0,category:"quality",type:"file_too_large",severity:"info",id:"TAINT003",name:"Taint Analysis Skipped",message:`File too large (${t} chars) - taint analysis skipped (cap: 20000 chars)`,code:"",source:"taint"}}const SANITIZER_FUNCS=new Set(["parseInt","parseFloat","Number","Boolean","encodeURIComponent","encodeURI","escape","sanitize","sanitizeHtml","stripTags"]),SANITIZER_MEMBER=new Set(["sanitize","escape","escapeHtml","stripTags","clean"]),SANITIZER_RECEIVER_NAMES=new Set(["dompurify","sanitizehtml","sanitize-html","html","xss","validator","bleach","markupsafe","purify","sqlstring","sanitizer","striptags","xssfilters","insane"]);function hasKnownSanitizerReceiver(e){let t=e,n=0;for(;t&&n++<20;){if("Identifier"===t.type)return SANITIZER_RECEIVER_NAMES.has(t.name.toLowerCase());if("MemberExpression"!==t.type)return!1;t=t.object}return!1}const JS_SOURCE_PATH_PATTERNS=[{re:/^req\.(params|query|body|headers)(\.|$)/,severity:"critical"},{re:/^request\.(params|query|body|headers)(\.|$)/,severity:"critical"},{re:/^process\.argv/,severity:"critical"},{re:/^process\.env/,severity:"warning"},{re:/^document\.location/,severity:"critical"},{re:/^window\.location/,severity:"critical"},{re:/^location\.(search|hash|href)/,severity:"critical"}];function memberChainToPath(e){const t=[];let n=e,i=0;for(;n&&i++<20;){if("Identifier"===n.type){t.unshift(n.name);break}if("ThisExpression"===n.type){t.unshift("this");break}if("MemberExpression"!==n.type)return null;n.computed||"Identifier"!==n.property.type?t.unshift("*"):t.unshift(n.property.name),n=n.object}return t.length?t.join("."):null}function jsSourceSeverity(e){if(!e)return null;if("CallExpression"===e.type&&"Identifier"===e.callee.type&&"prompt"===e.callee.name)return"critical";if("NewExpression"===e.type&&"Identifier"===e.callee.type&&("URLSearchParams"===e.callee.name||"FormData"===e.callee.name))return"critical";const t=memberChainToPath(e);if(t)for(const e of JS_SOURCE_PATH_PATTERNS)if(e.re.test(t))return e.severity;return null}function isJSSourceExpr(e){return null!==jsSourceSeverity(e)}function describeJSSource(e){return memberChainToPath(e)||("CallExpression"===e.type&&"Identifier"===e.callee.type&&"prompt"===e.callee.name?"prompt()":"NewExpression"===e.type&&"Identifier"===e.callee.type?`new ${e.callee.name}()`:"tainted source")}function isSanitizerCall(e){return!(!e||"CallExpression"!==e.type)&&(!("Identifier"!==e.callee.type||!SANITIZER_FUNCS.has(e.callee.name))||!("MemberExpression"!==e.callee.type||"Identifier"!==e.callee.property.type||!SANITIZER_MEMBER.has(e.callee.property.name))&&hasKnownSanitizerReceiver(e.callee.object))}function isTaintedExpr(e,t,n){if(n=n||0,!e||n>12)return!1;if("Identifier"===e.type)return t.has(e.name);if("CallExpression"===e.type){if(isSanitizerCall(e))return!1;if(isJSSourceExpr(e))return!0;if("MemberExpression"===e.callee.type&&isTaintedExpr(e.callee.object,t,n+1))return!0;for(const i of e.arguments)if(isTaintedExpr(i,t,n+1))return!0;return!1}return"MemberExpression"===e.type?!!isJSSourceExpr(e)||isTaintedExpr(e.object,t,n+1):"NewExpression"===e.type?isJSSourceExpr(e):"BinaryExpression"===e.type&&"+"===e.operator?isTaintedExpr(e.left,t,n+1)||isTaintedExpr(e.right,t,n+1):"TemplateLiteral"===e.type?(e.expressions||[]).some(e=>isTaintedExpr(e,t,n+1)):"AssignmentExpression"===e.type?isTaintedExpr(e.right,t,n+1):"ConditionalExpression"===e.type?isTaintedExpr(e.consequent,t,n+1)||isTaintedExpr(e.alternate,t,n+1):"LogicalExpression"===e.type?isTaintedExpr(e.left,t,n+1)||isTaintedExpr(e.right,t,n+1):!("SequenceExpression"!==e.type||!e.expressions.length)&&isTaintedExpr(e.expressions[e.expressions.length-1],t,n+1)}function isDynamicTaintedBuild(e,t){return!!e&&("TemplateLiteral"===e.type?(e.expressions||[]).some(e=>isTaintedExpr(e,t)):"BinaryExpression"===e.type&&"+"===e.operator&&(isTaintedExpr(e.left,t)||isTaintedExpr(e.right,t)))}function collectFunctionReturnNodes(e){const t=[];return e.body&&"BlockStatement"===e.body.type?function n(i){if(i&&"object"==typeof i&&"string"==typeof i.type&&(i===e||"FunctionDeclaration"!==i.type&&"FunctionExpression"!==i.type&&"ArrowFunctionExpression"!==i.type)){"ReturnStatement"===i.type&&i.argument&&t.push(i.argument);for(const e in i){if("loc"===e||"range"===e||"start"===e||"end"===e)continue;const t=i[e];if(Array.isArray(t))for(const e of t)e&&"object"==typeof e&&"string"==typeof e.type&&n(e);else t&&"object"==typeof t&&"string"==typeof t.type&&n(t)}}}(e.body):e.body&&t.push(e.body),t}function findTaintOrigin(e,t){let n=null;return walk(e,e=>{if(!n)if("Identifier"===e.type&&t.has(e.name))n=t.get(e.name);else{const t=jsSourceSeverity(e);t&&(n={desc:describeJSSource(e),line:lineOf(e),severity:t})}}),n}function collectTaintedVars(e,t){const n=new Set,i=new Map,r=new Set,a=new Map;if(t)for(const[e,r]of t)n.add(e),i.set(e,r);let s=!0,o=0;function l(e){return!(!e||"CallExpression"!==e.type||"Identifier"!==e.callee.type||!r.has(e.callee.name))}for(;s&&o<10;)s=!1,o++,walk(e,e=>{if("FunctionDeclaration"!==e.type&&"FunctionExpression"!==e.type&&"ArrowFunctionExpression"!==e.type)return;const t=e.id&&e.id.name;if(t&&!r.has(t))for(const o of collectFunctionReturnNodes(e))if(isTaintedExpr(o,n)||l(o)){r.add(t),a.set(t,findTaintOrigin(o,i)||(l(o)?a.get(o.callee.name):null)||{desc:describeJSSource(o),line:lineOf(o),severity:jsSourceSeverity(o)||"critical"}),s=!0;break}}),walk(e,e=>{let t=null,r=null;if("VariableDeclarator"===e.type&&e.init&&"Identifier"===e.id.type?(t=e.id.name,r=e.init):"AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type&&(t=e.left.name,r=e.right),!t||n.has(t))return;const o=l(r);if(isTaintedExpr(r,n)||o){n.add(t),s=!0;const l=findTaintOrigin(r,i)||(o?a.get(r.callee.name):null)||{desc:describeJSSource(r),line:lineOf(e),severity:jsSourceSeverity(r)||"critical"};i.set(t,l)}});return{taintedVars:n,origins:i}}const SQL_METHODS=new Set(["query","execute","raw","where"]);function parseJS(e){try{return parser.parse(e,{sourceType:"unambiguous",plugins:BABEL_PLUGINS,allowReturnOutsideFunction:!0,allowImportExportEverywhere:!0,allowAwaitOutsideFunction:!0,allowSuperOutsideMethod:!0,allowUndeclaredExports:!0,errorRecovery:!1}).program}catch(e){return null}}function analyzeJSTaint(e,t,n){const i=[],r=t.split("\n"),a=parseJS(t);if(!a)return i;const{taintedVars:s,origins:o}=collectTaintedVars(a,n),l=new Set;function c(t,n){const a=lineOf(t),s=`${a}:${n.id}`;if(l.has(s))return;l.add(s);const c=findTaintOrigin(t,o);c&&(n=Object.assign({},n,{severity:c.severity||"critical"}),c.crossFile&&(n=Object.assign({},n,{crossFile:!0,sourceFile:c.sourceFile}))),i.push(mkTaintIssue(e,a,r,n))}function p(e){const t=findTaintOrigin(e,o);if(t)return`${t.crossFile?`${t.sourceFile}: `:""}${t.desc} (line ${t.line}) -> sink (line ${lineOf(e)})`}return walk(a,e=>{if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.property.type&&SQL_METHODS.has(e.callee.property.name)){const t=e.arguments[0];t&&(isDynamicTaintedBuild(t,s)||isTaintedExpr(t,s))&&c(e,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection",message:`Tainted user input flows into .${e.callee.property.name}() without sanitization - SQL injection risk`,taintPath:p(e)})}if("AssignmentExpression"!==e.type||"MemberExpression"!==e.left.type||"Identifier"!==e.left.property.type||"innerHTML"!==e.left.property.name&&"outerHTML"!==e.left.property.name||(isTaintedExpr(e.right,s)||isDynamicTaintedBuild(e.right,s))&&c(e,{type:"xss",id:"TAINT002",name:"Tainted XSS via "+e.left.property.name,message:`Tainted user input flows into ${e.left.property.name} without sanitization - XSS risk`,taintPath:p(e)}),"CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&"document"===e.callee.object.name&&"Identifier"===e.callee.property.type&&("write"===e.callee.property.name||"writeln"===e.callee.property.name)){const t=e.arguments[0];t&&(isTaintedExpr(t,s)||isDynamicTaintedBuild(t,s))&&c(e,{type:"xss",id:"TAINT002",name:`Tainted XSS via document.${e.callee.property.name}()`,message:`Tainted user input flows into document.${e.callee.property.name}() without sanitization - XSS risk`,taintPath:p(e)})}if("JSXAttribute"===e.type&&e.name&&"dangerouslySetInnerHTML"===e.name.name&&e.value&&"JSXExpressionContainer"===e.value.type){const t=e.value.expression;if(t&&"ObjectExpression"===t.type){const n=t.properties.find(e=>e.key&&("__html"===e.key.name||"__html"===e.key.value));n&&(isTaintedExpr(n.value,s)||isDynamicTaintedBuild(n.value,s))&&c(e,{type:"xss",id:"TAINT002",name:"Tainted XSS via dangerouslySetInnerHTML",message:"Tainted user input flows into dangerouslySetInnerHTML without sanitization - XSS risk",taintPath:p(e)})}}if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&/^res(ponse)?$/.test(e.callee.object.name)&&"Identifier"===e.callee.property.type&&("send"===e.callee.property.name||"write"===e.callee.property.name)){const t=e.arguments[0];t&&"ObjectExpression"!==t.type&&"ArrayExpression"!==t.type&&(isTaintedExpr(t,s)||isDynamicTaintedBuild(t,s))&&c(e,{type:"xss",id:"TAINT002",name:`Reflected XSS via ${e.callee.object.name}.${e.callee.property.name}()`,message:`Tainted user input is reflected via ${e.callee.object.name}.${e.callee.property.name}() without sanitization - reflected XSS risk`,taintPath:p(e)})}}),i}const PY_SOURCE_PATTERNS=[/\brequest\.args\b/,/\brequest\.form\b/,/\brequest\.json\b/,/\brequest\.data\b/,/\brequest\.GET\b/,/\brequest\.POST\b/,/\bsys\.argv\b/,/\bos\.environ\b/,/\binput\s*\(/],PY_SANITIZER_WRAP_RE=/^\s*(int|float|re\.escape|html\.escape|escape|markupsafe\.escape|bleach\.clean)\s*\(/;function describePySource(e){for(const t of PY_SOURCE_PATTERNS){const n=e.match(t);if(n)return n[0]}return"tainted source"}function isPySourceText(e){return PY_SOURCE_PATTERNS.some(t=>t.test(e))}function isDynamicPyText(e){return isDynamicPyString(e)}function collectPyTaintedVars(e){const t=new Set,n=new Map;let i=!0,r=0;const a=/^\s*([A-Za-z_]\w*)\s*=(?!=)\s*([\s\S]+?)\s*$/;for(;i&&r<10;){i=!1,r++;for(const r of e){const e=r.text.split("\n")[0].match(a)||r.text.match(a);if(!e)continue;const s=e[1];if(t.has(s))continue;const o=r.text.slice(r.text.indexOf("=")+1);if(PY_SANITIZER_WRAP_RE.test(o.trim()))continue;let l=!1,c=null;if(isPySourceText(o))l=!0,c=describePySource(o);else for(const e of t)if(new RegExp("\\b"+e+"\\b").test(o)){l=!0,c=n.has(e)?n.get(e).desc:e;break}l&&(t.add(s),n.set(s,{desc:c,line:r.startLine}),i=!0)}}return{taintedVars:t,origins:n}}function textReferencesTaint(e,t){if(isPySourceText(e))return!0;for(const n of t)if(new RegExp("\\b"+n+"\\b").test(e))return!0;return!1}function analyzePythonTaint(e,t){const n=[],i=t.split("\n"),r=joinLogicalStatements(t),{taintedVars:a,origins:s}=collectPyTaintedVars(r),o=new Set;function l(t,r){const a=`${t}:${r.id}`;o.has(a)||(o.add(a),n.push(mkTaintIssue(e,t,i,r)))}function c(e,t){for(const n of a)if(new RegExp("\\b"+n+"\\b").test(e)&&s.has(n)){const e=s.get(n);return`${e.desc} (line ${e.line}) -> sink (line ${t})`}if(isPySourceText(e))return`${describePySource(e)} -> sink (line ${t})`}for(const e of r){const t=e.text,n=e.startLine,i=t.match(/\b(?:cursor|db|conn|connection)\.execute\s*\(/);if(i){const e=t.indexOf("(",i.index),r=extractBalanced(t,e),s=splitTopLevelArgs(r)[0]||"";isDynamicPyText(s)&&textReferencesTaint(s,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection",message:"Tainted user input flows into .execute() without sanitization - SQL injection risk",taintPath:c(s,n)})}const r=t.match(/\.(raw|extra)\s*\(|\bRawSQL\s*\(/);if(r){const e=t.indexOf("(",r.index),i=extractBalanced(t,e),s=splitTopLevelArgs(i)[0]||"";(isDynamicPyText(s)||isPySourceText(s))&&textReferencesTaint(s,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection (Django)",message:"Tainted user input flows into raw SQL construct without sanitization - SQL injection risk",taintPath:c(s,n)})}const s=t.match(/\btext\s*\(/);if(s){const e=t.indexOf("(",s.index),i=extractBalanced(t,e);isDynamicPyText(i)&&textReferencesTaint(i,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection (SQLAlchemy text())",message:"Tainted user input flows into SQLAlchemy text() without sanitization - SQL injection risk",taintPath:c(i,n)})}const o=t.match(/\bmark_safe\s*\(/);if(o){const e=t.indexOf("(",o.index),i=extractBalanced(t,e);textReferencesTaint(i,a)&&l(n,{type:"xss",id:"TAINT002",name:"Tainted XSS via mark_safe()",message:"Tainted user input flows into mark_safe() without sanitization - XSS risk",taintPath:c(i,n)})}const p=t.match(/\{\{\s*([A-Za-z_][\w.]*)[^}]*\|\s*safe\b/);p&&textReferencesTaint(p[1],a)&&l(n,{type:"xss",id:"TAINT002",name:"Tainted XSS via Jinja2 |safe filter",message:"Tainted user input is rendered with the |safe filter without sanitization - XSS risk",taintPath:c(p[1],n)})}return n}const JS_RESOLVE_EXTS=["",".js",".jsx",".ts",".tsx",".mjs",".cjs","/index.js","/index.ts","/index.jsx","/index.tsx"];function resolveImportPath(e,t,n){if(!t||!t.startsWith(".")&&!t.startsWith("/"))return null;const i=path.dirname(e),r=t.startsWith("/")?t:path.resolve(i,t);for(const e of JS_RESOLVE_EXTS){const t=r+e;if(n(t))return t}return null}function defaultFileExists(e){try{return fs.statSync(e).isFile()}catch(e){return!1}}function extractImportBindings(e){const t=[];return walk(e,e=>{if("ImportDeclaration"===e.type&&e.source&&"string"==typeof e.source.value){const n=e.source.value;for(const i of e.specifiers||[])if("ImportDefaultSpecifier"===i.type)t.push({localName:i.local.name,importedName:"default",source:n});else if("ImportNamespaceSpecifier"===i.type)t.push({localName:i.local.name,importedName:"*",source:n});else if("ImportSpecifier"===i.type){const e=i.imported&&(i.imported.name||i.imported.value);t.push({localName:i.local.name,importedName:e||i.local.name,source:n})}}if("VariableDeclarator"===e.type&&e.init){const n=unwrapRequireCall(e.init);if(n)if("Identifier"===e.id.type)t.push({localName:e.id.name,importedName:"*",source:n});else if("ObjectPattern"===e.id.type)for(const i of e.id.properties)if("ObjectProperty"===i.type||"Property"===i.type){const e=i.key&&(i.key.name||i.key.value),r=i.value&&"Identifier"===i.value.type?i.value.name:e;e&&r&&t.push({localName:r,importedName:e,source:n})}}if("AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type){const n=unwrapRequireCall(e.right);n&&t.push({localName:e.left.name,importedName:"*",source:n})}if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.property.type&&"then"===e.callee.property.name&&"ImportExpression"===e.callee.object.type&&e.callee.object.source&&"string"==typeof e.callee.object.source.value){const n=e.callee.object.source.value,i=e.arguments[0];i&&("ArrowFunctionExpression"===i.type||"FunctionExpression"===i.type)&&i.params[0]&&"Identifier"===i.params[0].type&&t.push({localName:i.params[0].name,importedName:"*",source:n})}}),t}function unwrapRequireCall(e){if(!e||"CallExpression"!==e.type)return null;if("Identifier"!==e.callee.type||"require"!==e.callee.name)return null;const t=e.arguments[0];return t&&"string"==typeof t.value?t.value:null}function extractReExports(e){const t=[];return walk(e,e=>{if("ExportNamedDeclaration"===e.type&&e.source&&"string"==typeof e.source.value){const n=e.source.value;for(const i of e.specifiers||[]){const e=i.local&&(i.local.name||i.local.value),r=i.exported&&(i.exported.name||i.exported.value);e&&r&&t.push({importedName:e,exportedName:r,source:n})}}if("ExportAllDeclaration"===e.type&&e.source&&"string"==typeof e.source.value&&t.push({importedName:"*",exportedName:"*",source:e.source.value}),"AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&"module.exports"===memberChainToPath(e.left)){const n=unwrapRequireCall(e.right);n&&t.push({importedName:"*",exportedName:"*",source:n})}}),t}function extractLocalExports(e){const t=[];return walk(e,e=>{if("AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&!e.left.computed&&"Identifier"===e.left.property.type){const n=memberChainToPath(e.left.object);"module.exports"!==n&&"exports"!==n||t.push({exportedName:e.left.property.name,localName:null,inlineNode:e.right})}if("AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&"module.exports"===memberChainToPath(e.left)&&"ObjectExpression"===e.right.type)for(const n of e.right.properties){if("ObjectProperty"!==n.type&&"Property"!==n.type)continue;const e=n.key&&(n.key.name||n.key.value);e&&(n.value&&"Identifier"===n.value.type?t.push({exportedName:e,localName:n.value.name,inlineNode:null}):t.push({exportedName:e,localName:null,inlineNode:n.value}))}if("ExportNamedDeclaration"===e.type&&e.declaration){const n=e.declaration;if("VariableDeclaration"===n.type)for(const e of n.declarations)"Identifier"===e.id.type&&t.push({exportedName:e.id.name,localName:e.id.name,inlineNode:e.init||null});else"FunctionDeclaration"!==n.type&&"ClassDeclaration"!==n.type||!n.id||t.push({exportedName:n.id.name,localName:n.id.name,inlineNode:null})}if("ExportNamedDeclaration"===e.type&&!e.source)for(const n of e.specifiers||[]){const e=n.local&&(n.local.name||n.local.value),i=n.exported&&(n.exported.name||n.exported.value);e&&i&&t.push({exportedName:i,localName:e,inlineNode:null})}if("ExportDefaultDeclaration"===e.type){const n=e.declaration;n&&("FunctionDeclaration"===n.type||"ClassDeclaration"===n.type)&&n.id?t.push({exportedName:"default",localName:n.id.name,inlineNode:null}):n&&"Identifier"===n.type?t.push({exportedName:"default",localName:n.name,inlineNode:null}):t.push({exportedName:"default",localName:null,inlineNode:n})}}),t}function buildExportTaintMap(e,t){const n=(t=t||{}).readFile||(e=>fs.readFileSync(e,"utf8")),i=t.fileExists||defaultFileExists,r=t.parsedCache||null,a=new Map;for(const t of e){const e=path.extname(t).toLowerCase();if(!JS_EXTS.has(e))continue;let i;try{i=n(t)}catch(e){continue}if(r&&r.set(t,{content:i,ast:null}),i.length>2e4)continue;const s=parseJS(i);r&&(r.get(t).ast=s),s&&a.set(t,{ast:s,content:i})}const s=new Map;for(const e of a.keys())s.set(e,new Map);const o=Math.max(10,a.size+1);let l=!0,c=0;for(;l&&c<o;){l=!1,c++;for(const[e,{ast:t}]of a){const n=s.get(e),r=extractImportBindings(t),a=new Map;for(const t of r){const n=resolveImportPath(e,t.source,i);if(!n||!s.has(n))continue;const r=s.get(n);if("*"===t.importedName)for(const[,e]of r)a.has(t.localName)||a.set(t.localName,e);else r.has(t.importedName)&&a.set(t.localName,r.get(t.importedName))}const{taintedVars:o,origins:c}=collectTaintedVars(t,a),p=extractLocalExports(t);for(const t of p){if(n.has(t.exportedName))continue;let i=null;if(t.localName&&o.has(t.localName)?i=c.get(t.localName)||{desc:t.localName,line:0}:t.inlineNode&&(isTaintedExpr(t.inlineNode,o)||isDynamicTaintedBuild(t.inlineNode,o))&&(i=findTaintOrigin(t.inlineNode,c)||{desc:describeJSSource(t.inlineNode),line:lineOf(t.inlineNode)}),i){const r=i.crossFile?i.sourceFile:e;n.set(t.exportedName,{desc:i.desc,line:i.line,sourceFile:r,crossFile:!0}),l=!0}}const u=extractReExports(t);for(const t of u){const r=resolveImportPath(e,t.source,i);if(!r||!s.has(r))continue;const a=s.get(r);if("*"===t.importedName)for(const[e,t]of a)n.has(e)||(n.set(e,t),l=!0);else a.has(t.importedName)&&!n.has(t.exportedName)&&(n.set(t.exportedName,a.get(t.importedName)),l=!0)}}}return s}function buildImportSeeds(e,t,n,i){const r=new Map,a=extractImportBindings(t);for(const t of a){const a=resolveImportPath(e,t.source,i);if(!a||!n.has(a))continue;const s=n.get(a);if("*"===t.importedName)for(const[,e]of s)r.has(t.localName)||r.set(t.localName,e);else s.has(t.importedName)&&r.set(t.localName,s.get(t.importedName))}return r}function analyzeProject(e,t){const n=(t=t||{}).readFile||(e=>fs.readFileSync(e,"utf8")),i=t.fileExists||defaultFileExists,r=new Map,a=buildExportTaintMap(e,{readFile:n,fileExists:i,parsedCache:r}),s=[];for(const t of e){const e=path.extname(t).toLowerCase(),o=r.get(t);let l;if(o)l=o.content;else try{l=n(t)}catch(e){continue}if(l.length>2e4)(JS_EXTS.has(e)||PY_EXTS.has(e))&&s.push(fileTooLargeIssue(t,l.length));else try{if(JS_EXTS.has(e)){const e=o?o.ast:parseJS(l);if(!e)continue;const n=buildImportSeeds(t,e,a,i);s.push(...analyzeJSTaint(t,l,n.size?n:void 0))}else PY_EXTS.has(e)&&s.push(...analyzePythonTaint(t,l))}catch(e){}}return s}const JS_EXTS=new Set([".js",".jsx",".ts",".tsx",".mjs",".cjs"]),PY_EXTS=new Set([".py",".pyw"]);function analyze(e,t){if(t.length>2e4)return[fileTooLargeIssue(e,t.length)];const n=path.extname(e).toLowerCase();try{if(JS_EXTS.has(n))return analyzeJSTaint(e,t);if(PY_EXTS.has(n))return analyzePythonTaint(e,t)}catch(e){return[]}return[]}module.exports={analyze:analyze,analyzeProject:analyzeProject,analyzeJSTaint:analyzeJSTaint,analyzePythonTaint:analyzePythonTaint,buildExportTaintMap:buildExportTaintMap,buildImportSeeds:buildImportSeeds,resolveImportPath:resolveImportPath,_internal:{walk:walk,isTaintedExpr:isTaintedExpr,isJSSourceExpr:isJSSourceExpr,isSanitizerCall:isSanitizerCall,collectTaintedVars:collectTaintedVars,memberChainToPath:memberChainToPath,isPySourceText:isPySourceText,isDynamicPyText:isDynamicPyText,collectPyTaintedVars:collectPyTaintedVars,resolveImportPath:resolveImportPath,extractImportBindings:extractImportBindings,extractReExports:extractReExports,extractLocalExports:extractLocalExports,parseJS:parseJS}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const fs=require("fs"),path=require("path"),CodeScanner=require("./code-scanner"),DependencyGraph=require("./dependency-graph"),DriftDetector=require("./drift-detector"),WidgetGenerator=require("./widget-generator");function formatWidgetComment(e,t){const s=path.extname(t).toLowerCase();return".py"===s?e.replace(/^\/\*\*\n/,"").replace(/\s*\*\/\s*$/,"").split("\n").map(e=>e.replace(/^\s*\*\s?/,"# ")).join("\n"):".go"===s?e.replace(/^\/\*\*\n/,"").replace(/\s*\*\/\s*$/,"").split("\n").map(e=>e.replace(/^\s*\*\s?/,"// ")).join("\n"):e}class TechDebtAnalyzer{constructor(e={}){this.config={rootPath:e.rootPath||process.cwd(),...e},this.scanner=new CodeScanner(this.config),this.depGraph=new DependencyGraph(this.config),this.driftDetector=new DriftDetector(this.config),this.widgetGen=new WidgetGenerator({...this.config,dryRun:!0})}async analyze(e){const t=Date.now();await this.depGraph.build(e),this.widgetGen.dependencyGraph=this.depGraph;const s=await this.scanner.scanFiles(e),i=(await this.driftDetector.detectAll(),{scores:{overall:100,security:100,architecture:100,codeQuality:100,documentation:100,dependencies:100,motherCode:100},fixable:[],manual:[],info:[],stats:{totalFiles:e.length,totalIssues:0,fixableCount:0,manualCount:0,estimatedFixTime:"0 minutes",scanTime:0},categories:{}}),a=[];for(const e of s.issues||[])(e.id?.startsWith("SEC")||"security"===e.category)&&a.push({file:path.relative(this.config.rootPath,e.file),...e,fixable:"SEC001"===e.id||"SEC002"===e.id,fixAction:"SEC001"===e.id||"SEC002"===e.id?"extract_to_env":null});i.scores.security=Math.max(0,100-25*a.filter(e=>"critical"===e.severity).length-10*a.filter(e=>"high"===e.severity).length),i.categories.security={score:i.scores.security,issues:a,label:"Security",icon:"shield"};const o=[],r=this.depGraph.circularDeps||[],n=this.depGraph.getOrphanFiles()||[],c=this.depGraph.getMostCriticalFiles(5)||[];for(const e of r)o.push({type:"circular_dependency",severity:"high",files:Array.isArray(e)?e.map(e=>path.relative(this.config.rootPath,e)):[String(e)],message:"Circular dependency detected",fixable:!0,fixAction:"break_circular",suggestion:"Extract shared code into a new module to break the cycle"});for(const e of n){const t="string"==typeof e?e:e.file||e.path||String(e);o.push({type:"orphan_file",severity:"low",file:path.relative(this.config.rootPath,t),message:"File has no imports and no exports consumed — potential dead code",fixable:!0,fixAction:"flag_for_removal",suggestion:"Review and remove if truly unused"})}for(const e of c){const t="string"==typeof e?e:e.file||e.path||String(e),s="object"==typeof e&&(e.dependents||e.score||e.count)||0;s>10&&o.push({type:"god_file",severity:"medium",file:path.relative(this.config.rootPath,t),dependents:s,message:`${s} files depend on this — high blast radius if changed`,fixable:!1,suggestion:"Consider splitting into smaller, focused modules"})}i.scores.architecture=Math.max(0,100-15*r.length-1*n.length-5*o.filter(e=>"god_file"===e.type).length),i.categories.architecture={score:i.scores.architecture,issues:o,label:"Architecture",icon:"building"};const l=[],d=new(require("./hallucination-detector"))(this.config),u=await d.scan(e);for(const e of u.deprecatedAPIs||[])l.push({file:path.relative(this.config.rootPath,e.file),type:"deprecated_api",severity:"high",message:`${e.name} — deprecated since ${e.since}`,fixable:!0,fixAction:"update_deprecated_api",suggestion:e.fix});for(const e of u.aiSmells||[])e.message&&e.message.includes("localhost")&&l.push({file:path.relative(this.config.rootPath,e.file),type:"hardcoded_localhost",severity:"medium",message:e.message,fixable:!0,fixAction:"wrap_localhost_url",suggestion:"Use environment variable with localhost fallback"});for(const e of s.issues||[])(e.id?.startsWith("QUAL")||"quality"===e.category||"maintainability"===e.category)&&l.push({file:path.relative(this.config.rootPath,e.file),...e,fixable:["QUAL001","QUAL003","QUAL005"].includes(e.id),fixAction:"QUAL001"===e.id?"remove_console_log":null});i.scores.codeQuality=Math.max(0,100-5*l.filter(e=>"high"===e.severity).length-2*l.filter(e=>"medium"===e.severity).length),i.categories.codeQuality={score:i.scores.codeQuality,issues:l,label:"Code Quality",icon:"code"};const f=[];let h=0,p=0,g=0;for(const t of e)try{const e=fs.readFileSync(t,"utf-8");if(/\/\*\*[\s\S]*?@(purpose|module|layer)[\s\S]*?\*\//.test(e)){h++;const e=await this.driftDetector.analyzeFile(t);if(e.issues&&e.issues.length>0){g++;for(const s of e.issues)f.push({file:path.relative(this.config.rootPath,t),...s,fixable:!0,fixAction:"regenerate_widget"})}}else p++,f.push({file:path.relative(this.config.rootPath,t),type:"missing_mother_code",severity:"info",message:"No Mother Code DNA — file lacks contextual awareness",fixable:!0,fixAction:"inject_widget"})}catch(e){}const m=e.length>0?Math.round(h/e.length*100):0;i.scores.motherCode=Math.min(100,m+(g>0?-10:0)),i.scores.documentation=i.scores.motherCode,i.categories.motherCode={score:i.scores.motherCode,coverage:m,annotated:h,missing:p,drifted:g,issues:f,label:"Mother Code",icon:"dna"},this.depGraph.getModuleSummary&&this.depGraph.getModuleSummary(),i.scores.dependencies=Math.max(0,100-10*r.length-.5*n.length),i.categories.dependencies={score:i.scores.dependencies,issues:[],label:"Dependencies",icon:"link"};i.scores.overall=Math.round(.25*i.scores.security+.2*i.scores.architecture+.2*i.scores.codeQuality+.2*i.scores.motherCode+.15*i.scores.dependencies);for(const e of Object.values(i.categories))for(const t of e.issues||[])i.stats.totalIssues++,t.fixable?(i.fixable.push(t),i.stats.fixableCount++):"info"===t.severity?i.info.push(t):(i.manual.push(t),i.stats.manualCount++);const $=.5*i.stats.fixableCount+15*i.stats.manualCount;return i.stats.estimatedFixTime=$<60?`${Math.round($)} minutes`:`${Math.round($/60)} hours`,i.stats.scanTime=Date.now()-t,i}async fix(e,t={}){const s=await this.analyze(e),i=[],a=[];for(const e of s.fixable)if(t.dryRun)i.push({...e,status:"would_fix"});else try{switch(e.fixAction){case"inject_widget":{const t=path.resolve(this.config.rootPath,e.file),s=fs.readFileSync(t,"utf-8"),a=await this.widgetGen.generateWidget(t,s);if(a&&a.widgetString){const o=formatWidgetComment(a.widgetString,t);fs.writeFileSync(t,o+"\n\n"+s,"utf-8"),i.push({...e,status:"fixed"})}break}case"regenerate_widget":{const t=path.resolve(this.config.rootPath,e.file),s=fs.readFileSync(t,"utf-8").replace(/\/\*\*[\s\S]*?@(purpose|module|layer)[\s\S]*?\*\/\s*\n?\s*\n?/,""),a=await this.widgetGen.generateWidget(t,s);if(a&&a.widgetString){const o=formatWidgetComment(a.widgetString,t);fs.writeFileSync(t,o+"\n\n"+s,"utf-8"),i.push({...e,status:"fixed"})}break}case"remove_console_log":{const t=path.resolve(this.config.rootPath,e.file);let s=fs.readFileSync(t,"utf-8");const a=s;s=s.replace(/^\s*console\.log\(.*?\);\s*$/gm,""),s!==a&&(fs.writeFileSync(t,s,"utf-8"),i.push({...e,status:"fixed"}));break}case"update_deprecated_api":{const t=path.resolve(this.config.rootPath,e.file);let s=fs.readFileSync(t,"utf-8");const a=s;s=s.replace(/url\.parse\(([^)]+)\)/g,"new URL($1)"),s=s.replace(/new Buffer\(([^)]+)\)/g,"Buffer.from($1)"),s=s.replace(/require\(['"]sys['"]\)/g,"require('util')"),s=s.replace(/util\.isArray\(/g,"Array.isArray("),s=s.replace(/fs\.exists\(([^,]+),\s*/g,"fs.access($1, "),s!==a?(fs.writeFileSync(t,s,"utf-8"),i.push({...e,status:"fixed",detail:"Replaced deprecated API with modern equivalent"})):i.push({...e,status:"skipped",reason:"Pattern not matched for auto-replacement"});break}case"wrap_localhost_url":{const t=path.resolve(this.config.rootPath,e.file);let s=fs.readFileSync(t,"utf-8");const a=s;s=s.replace(/(['"`])(https?:\/\/localhost:\d{2,5}(?:\/[^'"`]*)?)\1/g,(e,t,s)=>{const i=s.match(/:(\d+)/);return`(process.env.SERVICE_URL_${i?i[1]:"PORT"} || ${t}${s}${t})`}),s!==a?(fs.writeFileSync(t,s,"utf-8"),i.push({...e,status:"fixed",detail:"Wrapped localhost URL in env var fallback"})):i.push({...e,status:"skipped",reason:"Pattern not matched for auto-replacement"});break}case"extract_to_env":i.push({...e,status:"flagged_for_review",reason:"Secrets require manual extraction to .env"});break;case"flag_for_removal":i.push({...e,status:"confirmed_orphan",reason:"Verified no imports. Safe to remove manually."});break;default:i.push({...e,status:"skipped",reason:"No auto-fix available"})}}catch(t){a.push({...e,status:"failed",error:t.message})}return{totalFixable:s.fixable.length,fixed:i.filter(e=>"fixed"===e.status).length,flagged:i.filter(e=>"flagged_for_review"===e.status||"confirmed_orphan"===e.status).length,skipped:i.filter(e=>"skipped"===e.status).length,failed:a.length,fixes:i,failures:a,debtBefore:s.scores.overall,...t.dryRun?{}:{note:"Run thuban report again to see updated scores"}}}formatReport(e){const t="[0m",s="[1m",i="[31m",a="[32m",o="[33m",r="[36m",n="[90m",c=e=>{const s=Math.round(e/5),r=20-s,c=e>=80?a:e>=60?o:i;return`${c}${"█".repeat(s)}${n}${"░".repeat(r)}${t} ${c}${e}%${t}`};let l="";var d;l+=`\n${r} ╔══════════════════════════════════════════════╗${t}\n`,l+=`${r} ║${s} THUBAN TECH DEBT REPORT ${t}${r}║${t}\n`,l+=`${r} ╚══════════════════════════════════════════════╝${t}\n\n`,l+=` ${s}Overall Health:${t} ${c(e.scores.overall)} Grade: ${d=e.scores.overall,d>=90?`${a}A${t}`:d>=80?`${a}B${t}`:d>=70?`${o}C${t}`:d>=60?`${o}D${t}`:`${i}F${t}`}\n\n`,l+=` ${s}Category Breakdown:${t}\n\n`;const u=[["Security",e.scores.security,"shield"],["Architecture",e.scores.architecture,"building"],["Code Quality",e.scores.codeQuality,"code"],["Mother Code",e.scores.motherCode,"dna"],["Dependencies",e.scores.dependencies,"link"]];for(const[e,t]of u)l+=` ${e.padEnd(18)} ${c(t)}\n`;if(l+="\n",e.categories.motherCode){const n=e.categories.motherCode;l+=` ${s}Mother Code Coverage:${t}\n`,l+=` Annotated: ${r}${n.annotated}${t} | Missing: ${o}${n.missing}${t} | Drifted: ${n.drifted>0?i:a}${n.drifted}${t} | Coverage: ${n.coverage>=80?a:n.coverage>=40?o:i}${n.coverage}%${t}\n\n`}if(l+=` ${s}Tech Debt Summary:${t}\n`,l+=` Total issues: ${s}${e.stats.totalIssues}${t}\n`,l+=` Auto-fixable: ${a}${e.stats.fixableCount}${t} ${n}← run 'thuban fix' to resolve${t}\n`,l+=` Manual review: ${o}${e.stats.manualCount}${t}\n`,l+=` Est. manual time: ${n}${e.stats.estimatedFixTime}${t}\n`,l+=` Scan time: ${n}${e.stats.scanTime}ms${t}\n\n`,e.fixable.length>0){l+=` ${s}Top Auto-Fixable Items:${t}\n`;const i={};for(const t of e.fixable){const e=t.fixAction||"unknown";i[e]||(i[e]={count:0,label:e}),i[e].count++}for(const[e,o]of Object.entries(i).sort((e,t)=>t[1].count-e[1].count))l+=` ${a}→${t} ${{inject_widget:"Inject Mother Code DNA",regenerate_widget:"Update stale annotations",remove_console_log:"Remove debug console.logs",extract_to_env:"Extract hardcoded secrets to .env",flag_for_removal:"Remove orphan files",break_circular:"Break circular dependencies"}[e]||e}: ${s}${o.count}${t} items\n`;l+=`\n ${r}Run 'thuban fix [path]' to auto-fix all ${e.stats.fixableCount} items${t}\n\n`}if(e.manual.length>0){l+=` ${s}Manual Review Required:${t}\n`;for(const s of e.manual.slice(0,5))l+=` ${"critical"===s.severity||"high"===s.severity?i:o}${(s.severity||"").toUpperCase().padEnd(8)}${t} ${r}${s.file||""}${t}\n`,l+=` ${n} ${s.message||s.suggestion||""}${t}\n`;e.manual.length>5&&(l+=` ${n} ... and ${e.manual.length-5} more${t}\n`),l+="\n"}return l}toJSON(e){return{timestamp:(new Date).toISOString(),scores:e.scores,categories:Object.fromEntries(Object.entries(e.categories).map(([e,t])=>[e,{score:t.score,label:t.label,issueCount:t.issues?.length||0,coverage:t.coverage}])),stats:e.stats,fixable:e.fixable.map(e=>({file:e.file,action:e.fixAction,severity:e.severity})),manual:e.manual.map(e=>({file:e.file,message:e.message,severity:e.severity}))}}}module.exports=TechDebtAnalyzer;
|
|
1
|
+
const fs=require("fs"),path=require("path"),CodeScanner=require("./code-scanner"),DependencyGraph=require("./dependency-graph"),DriftDetector=require("./drift-detector"),WidgetGenerator=require("./widget-generator"),SecretScanner=require("./secret-scanner");function formatWidgetComment(e,t){const s=path.extname(t).toLowerCase();return".py"===s?e.replace(/^\/\*\*\n/,"").replace(/\s*\*\/\s*$/,"").split("\n").map(e=>e.replace(/^\s*\*\s?/,"# ")).join("\n"):".go"===s?e.replace(/^\/\*\*\n/,"").replace(/\s*\*\/\s*$/,"").split("\n").map(e=>e.replace(/^\s*\*\s?/,"// ")).join("\n"):e}class TechDebtAnalyzer{constructor(e={}){this.config={rootPath:e.rootPath||process.cwd(),...e},this.scanner=new CodeScanner(this.config),this.depGraph=new DependencyGraph(this.config),this.driftDetector=new DriftDetector(this.config),this.widgetGen=new WidgetGenerator({...this.config,dryRun:!0}),this.secretScanner=new SecretScanner(this.config)}async analyze(e){const t=Date.now();await this.depGraph.build(e),this.widgetGen.dependencyGraph=this.depGraph;const s=await this.scanner.scanFiles(e);await this.driftDetector.detectAll();let i={issues:[]};try{i=await this.secretScanner.scanFiles(e)}catch(e){}const a={scores:{overall:100,security:100,architecture:100,codeQuality:100,documentation:100,dependencies:100,motherCode:100},fixable:[],manual:[],info:[],stats:{totalFiles:e.length,totalIssues:0,fixableCount:0,manualCount:0,estimatedFixTime:"0 minutes",scanTime:0},categories:{}},o=[];for(const e of s.issues||[])(e.id?.startsWith("SEC")||"security"===e.category)&&o.push({file:path.relative(this.config.rootPath,e.file),...e,fixable:"SEC001"===e.id||"SEC002"===e.id,fixAction:"SEC001"===e.id||"SEC002"===e.id?"extract_to_env":null});for(const e of i.issues||[])o.push({file:e.file?path.relative(this.config.rootPath,e.file):e.file||"unknown",id:e.id,category:"security",severity:e.severity||"critical",message:e.message||e.type,type:e.type,line:e.line,fixable:!1,fixAction:null});a.scores.security=Math.max(0,100-25*o.filter(e=>"critical"===e.severity).length-10*o.filter(e=>"high"===e.severity).length),a.categories.security={score:a.scores.security,issues:o,label:"Security",icon:"shield"};const r=[],n=this.depGraph.circularDeps||[],c=this.depGraph.getOrphanFiles()||[],l=this.depGraph.getMostCriticalFiles(5)||[];for(const e of n)r.push({type:"circular_dependency",severity:"high",files:Array.isArray(e)?e.map(e=>path.relative(this.config.rootPath,e)):[String(e)],message:"Circular dependency detected",fixable:!0,fixAction:"break_circular",suggestion:"Extract shared code into a new module to break the cycle"});for(const e of c){const t="string"==typeof e?e:e.file||e.path||String(e);r.push({type:"orphan_file",severity:"low",file:path.relative(this.config.rootPath,t),message:"File has no imports and no exports consumed — potential dead code",fixable:!0,fixAction:"flag_for_removal",suggestion:"Review and remove if truly unused"})}for(const e of l){const t="string"==typeof e?e:e.file||e.path||String(e),s="object"==typeof e&&(e.dependents||e.score||e.count)||0;s>10&&r.push({type:"god_file",severity:"medium",file:path.relative(this.config.rootPath,t),dependents:s,message:`${s} files depend on this — high blast radius if changed`,fixable:!1,suggestion:"Consider splitting into smaller, focused modules"})}a.scores.architecture=Math.max(0,100-15*n.length-1*c.length-5*r.filter(e=>"god_file"===e.type).length),a.categories.architecture={score:a.scores.architecture,issues:r,label:"Architecture",icon:"building"};const f=[],u=new(require("./hallucination-detector"))(this.config),d=await u.scan(e);for(const e of d.deprecatedAPIs||[])f.push({file:path.relative(this.config.rootPath,e.file),type:"deprecated_api",severity:"high",message:`${e.name} — deprecated since ${e.since}`,fixable:!0,fixAction:"update_deprecated_api",suggestion:e.fix});for(const e of d.aiSmells||[])e.message&&e.message.includes("localhost")&&f.push({file:path.relative(this.config.rootPath,e.file),type:"hardcoded_localhost",severity:"medium",message:e.message,fixable:!0,fixAction:"wrap_localhost_url",suggestion:"Use environment variable with localhost fallback"});for(const e of s.issues||[])(e.id?.startsWith("QUAL")||"quality"===e.category||"maintainability"===e.category)&&f.push({file:path.relative(this.config.rootPath,e.file),...e,fixable:["QUAL001","QUAL003","QUAL005"].includes(e.id),fixAction:"QUAL001"===e.id?"remove_console_log":null});a.scores.codeQuality=Math.max(0,100-5*f.filter(e=>"high"===e.severity).length-2*f.filter(e=>"medium"===e.severity).length),a.categories.codeQuality={score:a.scores.codeQuality,issues:f,label:"Code Quality",icon:"code"};const h=[];let p=0,g=0,m=0;for(const t of e)try{const e=fs.readFileSync(t,"utf-8");if(/\/\*\*[\s\S]*?@(purpose|module|layer)[\s\S]*?\*\//.test(e)){p++;const e=await this.driftDetector.analyzeFile(t);if(e.issues&&e.issues.length>0){m++;for(const s of e.issues)h.push({file:path.relative(this.config.rootPath,t),...s,fixable:!0,fixAction:"regenerate_widget"})}}else g++,h.push({file:path.relative(this.config.rootPath,t),type:"missing_mother_code",severity:"info",message:"No Mother Code DNA — file lacks contextual awareness",fixable:!0,fixAction:"inject_widget"})}catch(e){}const $=e.length>0?Math.round(p/e.length*100):0;a.scores.motherCode=Math.min(100,$+(m>0?-10:0)),a.scores.documentation=a.scores.motherCode,a.categories.motherCode={score:a.scores.motherCode,coverage:$,annotated:p,missing:g,drifted:m,issues:h,label:"Mother Code",icon:"dna"},this.depGraph.getModuleSummary&&this.depGraph.getModuleSummary(),a.scores.dependencies=Math.max(0,100-10*n.length-.5*c.length),a.categories.dependencies={score:a.scores.dependencies,issues:[],label:"Dependencies",icon:"link"};a.scores.overall=Math.round(.25*a.scores.security+.2*a.scores.architecture+.2*a.scores.codeQuality+.2*a.scores.motherCode+.15*a.scores.dependencies);for(const e of Object.values(a.categories))for(const t of e.issues||[])a.stats.totalIssues++,t.fixable?(a.fixable.push(t),a.stats.fixableCount++):"info"===t.severity?a.info.push(t):(a.manual.push(t),a.stats.manualCount++);const y=.5*a.stats.fixableCount+15*a.stats.manualCount;return a.stats.estimatedFixTime=y<60?`${Math.round(y)} minutes`:`${Math.round(y/60)} hours`,a.stats.scanTime=Date.now()-t,a}async fix(e,t={}){const s=await this.analyze(e),i=[],a=[],o=path.resolve(this.config.rootPath);for(const e of s.fixable)if(t.dryRun)i.push({...e,status:"would_fix"});else try{const t=path.resolve(this.config.rootPath,e.file);if(!t.startsWith(o+path.sep)&&t!==o){i.push({...e,status:"skipped",reason:"Path escapes project root — blocked for safety"});continue}switch(e.fixAction){case"inject_widget":{const t=path.resolve(this.config.rootPath,e.file),s=fs.readFileSync(t,"utf-8"),a=await this.widgetGen.generateWidget(t,s);if(a&&a.widgetString){const o=formatWidgetComment(a.widgetString,t);fs.writeFileSync(t,o+"\n\n"+s,"utf-8"),i.push({...e,status:"fixed"})}break}case"regenerate_widget":{const t=path.resolve(this.config.rootPath,e.file),s=fs.readFileSync(t,"utf-8").replace(/\/\*\*[\s\S]*?@(purpose|module|layer)[\s\S]*?\*\/\s*\n?\s*\n?/,""),a=await this.widgetGen.generateWidget(t,s);if(a&&a.widgetString){const o=formatWidgetComment(a.widgetString,t);fs.writeFileSync(t,o+"\n\n"+s,"utf-8"),i.push({...e,status:"fixed"})}break}case"remove_console_log":{const t=path.resolve(this.config.rootPath,e.file);let s=fs.readFileSync(t,"utf-8");const a=s;s=s.replace(/^\s*console\.log\(.*?\);\s*$/gm,""),s!==a&&(fs.writeFileSync(t,s,"utf-8"),i.push({...e,status:"fixed"}));break}case"update_deprecated_api":{const t=path.resolve(this.config.rootPath,e.file);let s=fs.readFileSync(t,"utf-8");const a=s;s=s.replace(/url\.parse\(([^)]+)\)/g,"new URL($1)"),s=s.replace(/new Buffer\(([^)]+)\)/g,(e,t)=>{const s=t.trim();return/\d/.test(s)&&/^[\d\s+\-*/%().]+$/.test(s)?`Buffer.alloc(${s})`:`Buffer.from(${t})`}),s=s.replace(/require\(['"]sys['"]\)/g,"require('util')"),s=s.replace(/util\.isArray\(/g,"Array.isArray("),s=s.replace(/fs\.exists\(([^,]+),\s*/g,"fs.access($1, "),s!==a?(fs.writeFileSync(t,s,"utf-8"),i.push({...e,status:"fixed",detail:"Replaced deprecated API with modern equivalent"})):i.push({...e,status:"skipped",reason:"Pattern not matched for auto-replacement"});break}case"wrap_localhost_url":{const t=path.resolve(this.config.rootPath,e.file);let s=fs.readFileSync(t,"utf-8");const a=s;s=s.replace(/(['"`])(https?:\/\/localhost:\d{2,5}(?:\/[^'"`]*)?)\1/g,(e,t,s)=>{const i=s.match(/:(\d+)/);return`(process.env.SERVICE_URL_${i?i[1]:"PORT"} || ${t}${s}${t})`}),s!==a?(fs.writeFileSync(t,s,"utf-8"),i.push({...e,status:"fixed",detail:"Wrapped localhost URL in env var fallback"})):i.push({...e,status:"skipped",reason:"Pattern not matched for auto-replacement"});break}case"extract_to_env":i.push({...e,status:"flagged_for_review",reason:"Secrets require manual extraction to .env"});break;case"flag_for_removal":i.push({...e,status:"confirmed_orphan",reason:"Verified no imports. Safe to remove manually."});break;default:i.push({...e,status:"skipped",reason:"No auto-fix available"})}}catch(t){a.push({...e,status:"failed",error:t.message})}return{totalFixable:s.fixable.length,fixed:i.filter(e=>"fixed"===e.status).length,flagged:i.filter(e=>"flagged_for_review"===e.status||"confirmed_orphan"===e.status).length,skipped:i.filter(e=>"skipped"===e.status).length,failed:a.length,fixes:i,failures:a,debtBefore:s.scores.overall,...t.dryRun?{}:{note:"Run thuban report again to see updated scores"}}}formatReport(e){const t="[0m",s="[1m",i="[31m",a="[32m",o="[33m",r="[36m",n="[90m",c=e=>{const s=Math.round(e/5),r=20-s,c=e>=80?a:e>=60?o:i;return`${c}${"█".repeat(s)}${n}${"░".repeat(r)}${t} ${c}${e}%${t}`};let l="";var f;l+=`\n${r} ╔══════════════════════════════════════════════╗${t}\n`,l+=`${r} ║${s} THUBAN TECH DEBT REPORT ${t}${r}║${t}\n`,l+=`${r} ╚══════════════════════════════════════════════╝${t}\n\n`,l+=` ${s}Overall Health:${t} ${c(e.scores.overall)} Grade: ${f=e.scores.overall,f>=90?`${a}A${t}`:f>=80?`${a}B${t}`:f>=70?`${o}C${t}`:f>=60?`${o}D${t}`:`${i}F${t}`}\n\n`,l+=` ${s}Category Breakdown:${t}\n\n`;const u=[["Security",e.scores.security,"shield"],["Architecture",e.scores.architecture,"building"],["Code Quality",e.scores.codeQuality,"code"],["Mother Code",e.scores.motherCode,"dna"],["Dependencies",e.scores.dependencies,"link"]];for(const[e,t]of u)l+=` ${e.padEnd(18)} ${c(t)}\n`;if(l+="\n",e.categories.motherCode){const n=e.categories.motherCode;l+=` ${s}Mother Code Coverage:${t}\n`,l+=` Annotated: ${r}${n.annotated}${t} | Missing: ${o}${n.missing}${t} | Drifted: ${n.drifted>0?i:a}${n.drifted}${t} | Coverage: ${n.coverage>=80?a:n.coverage>=40?o:i}${n.coverage}%${t}\n\n`}if(l+=` ${s}Tech Debt Summary:${t}\n`,l+=` Total issues: ${s}${e.stats.totalIssues}${t}\n`,l+=` Auto-fixable: ${a}${e.stats.fixableCount}${t} ${n}← run 'thuban fix' to resolve${t}\n`,l+=` Manual review: ${o}${e.stats.manualCount}${t}\n`,l+=` Est. manual time: ${n}${e.stats.estimatedFixTime}${t}\n`,l+=` Scan time: ${n}${e.stats.scanTime}ms${t}\n\n`,e.fixable.length>0){l+=` ${s}Top Auto-Fixable Items:${t}\n`;const i={};for(const t of e.fixable){const e=t.fixAction||"unknown";i[e]||(i[e]={count:0,label:e}),i[e].count++}for(const[e,o]of Object.entries(i).sort((e,t)=>t[1].count-e[1].count))l+=` ${a}→${t} ${{inject_widget:"Inject Mother Code DNA",regenerate_widget:"Update stale annotations",remove_console_log:"Remove debug console.logs",extract_to_env:"Extract hardcoded secrets to .env",flag_for_removal:"Remove orphan files",break_circular:"Break circular dependencies"}[e]||e}: ${s}${o.count}${t} items\n`;l+=`\n ${r}Run 'thuban fix [path]' to auto-fix all ${e.stats.fixableCount} items${t}\n\n`}if(e.manual.length>0){l+=` ${s}Manual Review Required:${t}\n`;for(const s of e.manual.slice(0,5))l+=` ${"critical"===s.severity||"high"===s.severity?i:o}${(s.severity||"").toUpperCase().padEnd(8)}${t} ${r}${s.file||""}${t}\n`,l+=` ${n} ${s.message||s.suggestion||""}${t}\n`;e.manual.length>5&&(l+=` ${n} ... and ${e.manual.length-5} more${t}\n`),l+="\n"}return l}toJSON(e){return{timestamp:(new Date).toISOString(),scores:e.scores,categories:Object.fromEntries(Object.entries(e.categories).map(([e,t])=>[e,{score:t.score,label:t.label,issueCount:t.issues?.length||0,coverage:t.coverage}])),stats:e.stats,fixable:e.fixable.map(e=>({file:e.file,action:e.fixAction,severity:e.severity})),manual:e.manual.map(e=>({file:e.file,message:e.message,severity:e.severity}))}}}module.exports=TechDebtAnalyzer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const fs=require("fs"),path=require("path"),COST_MODEL={hourlyRate:80,weeklyDriftHours:3,manualFixMinutes:{hallucination:30,deprecated:15,security:45,circular_dep:60,orphan_file:5,missing_annotation:2,copy_paste:20,ghost_code:10,code_smell:10,drift:15},thubanFixMinutes:{hallucination:.5,deprecated:.5,security:5,circular_dep:5,orphan_file:.5,missing_annotation:.1,copy_paste:2,ghost_code:.5,code_smell:1,drift:.5}};class TechDebtCostCalculator{constructor(t={}){this.hourlyRate=t.hourlyRate||COST_MODEL.hourlyRate,this.currency=t.currency||"£"}calculate(t,a=0){const n={};let
|
|
1
|
+
const fs=require("fs"),path=require("path"),COST_MODEL={hourlyRate:80,weeklyDriftHours:3,thubanMonthlyCost:29,manualFixMinutes:{hallucination:30,deprecated:15,security:45,circular_dep:60,orphan_file:5,missing_annotation:2,copy_paste:20,ghost_code:10,code_smell:10,drift:15},thubanFixMinutes:{hallucination:.5,deprecated:.5,security:5,circular_dep:5,orphan_file:.5,missing_annotation:.1,copy_paste:2,ghost_code:.5,code_smell:1,drift:.5}};class TechDebtCostCalculator{constructor(t={}){this.hourlyRate=t.hourlyRate||COST_MODEL.hourlyRate,this.currency=t.currency||"£"}calculate(t,a=0){const n={};let o=0,e=0;for(const a of t){const t=this._categorize(a);n[t]||(n[t]={count:0,manualMinutes:0,thubanMinutes:0});const u=COST_MODEL.manualFixMinutes[t]||10,s=COST_MODEL.thubanFixMinutes[t]||1;n[t].count++,n[t].manualMinutes+=u,n[t].thubanMinutes+=s,o+=u,e+=s}const u=o/60,s=e/60,r=Math.round(10*u)/10,h=Math.round(10*s)/10,i=r*this.hourlyRate,l=h*this.hourlyRate,c=i-l,d=i>0?Math.round(c/i*100):0,p=a/1e3*COST_MODEL.weeklyDriftHours,m=4*p*this.hourlyRate,y=COST_MODEL.thubanMonthlyCost,M=c>0?Math.round(c/y):0;return{summary:{totalIssues:t.length,manualHours:r,manualCost:Math.round(i),thubanHours:h,thubanCost:Math.round(l),savings:Math.round(c),savingsPercent:d,timeSaved:this._formatTime(o-e)},breakdown:Object.entries(n).map(([t,a])=>{const n=Math.round(a.manualMinutes/60*10)/10,o=Math.round(a.thubanMinutes/60*10)/10;return{category:t,count:a.count,manualTime:this._formatTime(a.manualMinutes),thubanTime:this._formatTime(a.thubanMinutes),manualCost:Math.round(n*this.hourlyRate),thubanCost:Math.round(o*this.hourlyRate)}}).sort((t,a)=>a.manualCost-t.manualCost),projection:{weeklyNewDebtHours:Math.round(10*p)/10,monthlyNewDebtCost:Math.round(m),monthlyThubanCost:y,monthlyROI:`${M}x`,annualSavings:Math.round(c+12*m),verdict:M>=5?"Thuban pays for itself within the first scan.":M>=2?"Thuban pays for itself within the first week.":"Thuban will save you money within the first month."}}}formatCLI(t){const a=[],n=t.summary,o=t.projection,e=this.currency;a.push(""),a.push(" TECH DEBT COST ESTIMATE"),a.push(""),a.push(` Current debt: ${n.manualHours} developer-hours to fix manually`),a.push(` At ${e}${this.hourlyRate}/hr: ${e}${n.manualCost.toLocaleString()} if you fix it yourself`),a.push(` With Thuban: ${n.thubanHours} hours (${n.timeSaved} saved)`),a.push(` You save: ${e}${n.savings.toLocaleString()} (${n.savingsPercent}%)`),a.push(""),a.push(" BREAKDOWN"),a.push(` ${"Category".padEnd(20)} ${"Count".padEnd(8)} ${"Manual".padEnd(12)} ${"Thuban".padEnd(12)} ${"Saving".padEnd(10)}`),a.push(" "+"-".repeat(62));for(const n of t.breakdown){const t=`${e}${n.manualCost-n.thubanCost}`;a.push(` ${n.category.padEnd(20)} ${String(n.count).padEnd(8)} ${(e+n.manualCost).padEnd(12)} ${(e+n.thubanCost).padEnd(12)} ${t.padEnd(10)}`)}return a.push(""),a.push(" PROJECTION"),a.push(` Every week you wait, ${o.weeklyNewDebtHours} hours of new debt accumulates.`),a.push(` That's ${e}${o.monthlyNewDebtCost}/month in growing technical debt.`),a.push(` Thuban Pro costs ${e}${o.monthlyThubanCost}/month. ROI: ${o.monthlyROI}.`),a.push(""),a.push(` ${o.verdict}`),a.push(""),a.join("\n")}_categorize(t){const a=(t.category||"").toLowerCase(),n=(t.id||"").toLowerCase();return"hallucination"===a||n.startsWith("hall")?"hallucination":"deprecated"===a||n.startsWith("depr")?"deprecated":"security"===a?"security":"circular"===a||n.includes("circular")?"circular_dep":"orphan"===a||n.includes("orphan")?"orphan_file":"drift"===a?"drift":"ghost"===a||n.includes("ghost")?"ghost_code":"copy_paste"===a||n.includes("duplicate")?"copy_paste":"annotation"===a||n.includes("mother")?"missing_annotation":"code_smell"}_formatTime(t){if(t<60)return`${Math.round(t)} mins`;const a=Math.floor(t/60),n=Math.round(t%60);return 0===n?`${a}h`:`${a}h ${n}m`}}module.exports=TechDebtCostCalculator;
|