@vyuhlabs/dxkit 2.4.6 → 2.4.7
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/CHANGELOG.md +885 -0
- package/README.md +131 -26
- package/dist/analysis-result.d.ts +112 -0
- package/dist/analysis-result.d.ts.map +1 -0
- package/dist/analysis-result.js +52 -0
- package/dist/analysis-result.js.map +1 -0
- package/dist/analyzers/bom/detailed.d.ts.map +1 -1
- package/dist/analyzers/bom/detailed.js +19 -0
- package/dist/analyzers/bom/detailed.js.map +1 -1
- package/dist/analyzers/bom/gather.d.ts +27 -26
- package/dist/analyzers/bom/gather.d.ts.map +1 -1
- package/dist/analyzers/bom/gather.js +26 -87
- package/dist/analyzers/bom/gather.js.map +1 -1
- package/dist/analyzers/bom/index.d.ts +0 -7
- package/dist/analyzers/bom/index.d.ts.map +1 -1
- package/dist/analyzers/bom/index.js +98 -48
- package/dist/analyzers/bom/index.js.map +1 -1
- package/dist/analyzers/bom/types.d.ts +11 -13
- package/dist/analyzers/bom/types.d.ts.map +1 -1
- package/dist/analyzers/cache.d.ts +95 -0
- package/dist/analyzers/cache.d.ts.map +1 -0
- package/dist/analyzers/cache.js +309 -0
- package/dist/analyzers/cache.js.map +1 -0
- package/dist/analyzers/coverage-runner.d.ts +56 -0
- package/dist/analyzers/coverage-runner.d.ts.map +1 -0
- package/dist/analyzers/coverage-runner.js +72 -0
- package/dist/analyzers/coverage-runner.js.map +1 -0
- package/dist/analyzers/dashboard/index.d.ts +24 -0
- package/dist/analyzers/dashboard/index.d.ts.map +1 -0
- package/dist/analyzers/dashboard/index.js +666 -0
- package/dist/analyzers/dashboard/index.js.map +1 -0
- package/dist/analyzers/developer/gather.d.ts.map +1 -1
- package/dist/analyzers/developer/gather.js +205 -37
- package/dist/analyzers/developer/gather.js.map +1 -1
- package/dist/analyzers/developer/index.d.ts +1 -1
- package/dist/analyzers/developer/index.d.ts.map +1 -1
- package/dist/analyzers/developer/index.js +19 -8
- package/dist/analyzers/developer/index.js.map +1 -1
- package/dist/analyzers/dispatcher.d.ts +37 -0
- package/dist/analyzers/dispatcher.d.ts.map +1 -1
- package/dist/analyzers/dispatcher.js +56 -9
- package/dist/analyzers/dispatcher.js.map +1 -1
- package/dist/analyzers/docs/shallow.d.ts +17 -5
- package/dist/analyzers/docs/shallow.d.ts.map +1 -1
- package/dist/analyzers/docs/shallow.js +65 -2
- package/dist/analyzers/docs/shallow.js.map +1 -1
- package/dist/analyzers/dx/shallow.d.ts +17 -5
- package/dist/analyzers/dx/shallow.d.ts.map +1 -1
- package/dist/analyzers/dx/shallow.js +66 -2
- package/dist/analyzers/dx/shallow.js.map +1 -1
- package/dist/analyzers/health/actions.d.ts +1 -1
- package/dist/analyzers/health/actions.d.ts.map +1 -1
- package/dist/analyzers/health/actions.js +27 -9
- package/dist/analyzers/health/actions.js.map +1 -1
- package/dist/analyzers/health/detailed.d.ts +2 -1
- package/dist/analyzers/health/detailed.d.ts.map +1 -1
- package/dist/analyzers/health/detailed.js +11 -7
- package/dist/analyzers/health/detailed.js.map +1 -1
- package/dist/analyzers/health.d.ts +27 -0
- package/dist/analyzers/health.d.ts.map +1 -1
- package/dist/analyzers/health.js +271 -33
- package/dist/analyzers/health.js.map +1 -1
- package/dist/analyzers/licenses/gather.d.ts +35 -8
- package/dist/analyzers/licenses/gather.d.ts.map +1 -1
- package/dist/analyzers/licenses/gather.js +70 -13
- package/dist/analyzers/licenses/gather.js.map +1 -1
- package/dist/analyzers/licenses/index.d.ts +1 -1
- package/dist/analyzers/licenses/index.d.ts.map +1 -1
- package/dist/analyzers/licenses/index.js +52 -11
- package/dist/analyzers/licenses/index.js.map +1 -1
- package/dist/analyzers/licenses/types.d.ts +15 -0
- package/dist/analyzers/licenses/types.d.ts.map +1 -1
- package/dist/analyzers/maintainability/shallow.d.ts +17 -5
- package/dist/analyzers/maintainability/shallow.d.ts.map +1 -1
- package/dist/analyzers/maintainability/shallow.js +80 -2
- package/dist/analyzers/maintainability/shallow.js.map +1 -1
- package/dist/analyzers/quality/detailed.d.ts.map +1 -1
- package/dist/analyzers/quality/detailed.js +4 -6
- package/dist/analyzers/quality/detailed.js.map +1 -1
- package/dist/analyzers/quality/gather.d.ts +1 -14
- package/dist/analyzers/quality/gather.d.ts.map +1 -1
- package/dist/analyzers/quality/gather.js +48 -137
- package/dist/analyzers/quality/gather.js.map +1 -1
- package/dist/analyzers/quality/index.d.ts +9 -2
- package/dist/analyzers/quality/index.d.ts.map +1 -1
- package/dist/analyzers/quality/index.js +189 -117
- package/dist/analyzers/quality/index.js.map +1 -1
- package/dist/analyzers/quality/shallow.d.ts +50 -5
- package/dist/analyzers/quality/shallow.d.ts.map +1 -1
- package/dist/analyzers/quality/shallow.js +155 -2
- package/dist/analyzers/quality/shallow.js.map +1 -1
- package/dist/analyzers/quality/types.d.ts +14 -0
- package/dist/analyzers/quality/types.d.ts.map +1 -1
- package/dist/analyzers/security/actions.d.ts +11 -4
- package/dist/analyzers/security/actions.d.ts.map +1 -1
- package/dist/analyzers/security/actions.js +87 -37
- package/dist/analyzers/security/actions.js.map +1 -1
- package/dist/analyzers/security/aggregator.d.ts +236 -0
- package/dist/analyzers/security/aggregator.d.ts.map +1 -0
- package/dist/analyzers/security/aggregator.js +347 -0
- package/dist/analyzers/security/aggregator.js.map +1 -0
- package/dist/analyzers/security/detailed.d.ts +2 -2
- package/dist/analyzers/security/detailed.d.ts.map +1 -1
- package/dist/analyzers/security/detailed.js +10 -9
- package/dist/analyzers/security/detailed.js.map +1 -1
- package/dist/analyzers/security/gather.d.ts +103 -1
- package/dist/analyzers/security/gather.d.ts.map +1 -1
- package/dist/analyzers/security/gather.js +281 -9
- package/dist/analyzers/security/gather.js.map +1 -1
- package/dist/analyzers/security/index.d.ts +15 -0
- package/dist/analyzers/security/index.d.ts.map +1 -1
- package/dist/analyzers/security/index.js +463 -50
- package/dist/analyzers/security/index.js.map +1 -1
- package/dist/analyzers/security/shallow.d.ts +50 -6
- package/dist/analyzers/security/shallow.d.ts.map +1 -1
- package/dist/analyzers/security/shallow.js +154 -2
- package/dist/analyzers/security/shallow.js.map +1 -1
- package/dist/analyzers/security/types.d.ts +51 -0
- package/dist/analyzers/security/types.d.ts.map +1 -1
- package/dist/analyzers/tests/detailed.d.ts.map +1 -1
- package/dist/analyzers/tests/detailed.js +2 -3
- package/dist/analyzers/tests/detailed.js.map +1 -1
- package/dist/analyzers/tests/gather.d.ts +2 -1
- package/dist/analyzers/tests/gather.d.ts.map +1 -1
- package/dist/analyzers/tests/gather.js +98 -69
- package/dist/analyzers/tests/gather.js.map +1 -1
- package/dist/analyzers/tests/index.d.ts +11 -2
- package/dist/analyzers/tests/index.d.ts.map +1 -1
- package/dist/analyzers/tests/index.js +83 -18
- package/dist/analyzers/tests/index.js.map +1 -1
- package/dist/analyzers/tests/shallow.d.ts +19 -5
- package/dist/analyzers/tests/shallow.d.ts.map +1 -1
- package/dist/analyzers/tests/shallow.js +89 -2
- package/dist/analyzers/tests/shallow.js.map +1 -1
- package/dist/analyzers/tests/types.d.ts +41 -1
- package/dist/analyzers/tests/types.d.ts.map +1 -1
- package/dist/analyzers/tools/autogen-header.d.ts +8 -0
- package/dist/analyzers/tools/autogen-header.d.ts.map +1 -0
- package/dist/analyzers/tools/autogen-header.js +107 -0
- package/dist/analyzers/tools/autogen-header.js.map +1 -0
- package/dist/analyzers/tools/cloc.d.ts.map +1 -1
- package/dist/analyzers/tools/cloc.js +36 -5
- package/dist/analyzers/tools/cloc.js.map +1 -1
- package/dist/analyzers/tools/debug-statements.d.ts +17 -0
- package/dist/analyzers/tools/debug-statements.d.ts.map +1 -0
- package/dist/analyzers/tools/debug-statements.js +58 -0
- package/dist/analyzers/tools/debug-statements.js.map +1 -0
- package/dist/analyzers/tools/default-exclusions.gitignore +28 -0
- package/dist/analyzers/tools/exclusions.d.ts +33 -6
- package/dist/analyzers/tools/exclusions.d.ts.map +1 -1
- package/dist/analyzers/tools/exclusions.js +95 -26
- package/dist/analyzers/tools/exclusions.js.map +1 -1
- package/dist/analyzers/tools/generic.d.ts +17 -2
- package/dist/analyzers/tools/generic.d.ts.map +1 -1
- package/dist/analyzers/tools/generic.js +206 -109
- package/dist/analyzers/tools/generic.js.map +1 -1
- package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
- package/dist/analyzers/tools/gitleaks.js +48 -1
- package/dist/analyzers/tools/gitleaks.js.map +1 -1
- package/dist/analyzers/tools/graphify.d.ts +30 -2
- package/dist/analyzers/tools/graphify.d.ts.map +1 -1
- package/dist/analyzers/tools/graphify.js +131 -15
- package/dist/analyzers/tools/graphify.js.map +1 -1
- package/dist/analyzers/tools/jscpd.d.ts +12 -2
- package/dist/analyzers/tools/jscpd.d.ts.map +1 -1
- package/dist/analyzers/tools/jscpd.js +129 -6
- package/dist/analyzers/tools/jscpd.js.map +1 -1
- package/dist/analyzers/tools/minified-detection.d.ts +9 -0
- package/dist/analyzers/tools/minified-detection.d.ts.map +1 -0
- package/dist/analyzers/tools/minified-detection.js +147 -0
- package/dist/analyzers/tools/minified-detection.js.map +1 -0
- package/dist/analyzers/tools/nuget-package-reference.d.ts +131 -0
- package/dist/analyzers/tools/nuget-package-reference.d.ts.map +1 -0
- package/dist/analyzers/tools/nuget-package-reference.js +175 -0
- package/dist/analyzers/tools/nuget-package-reference.js.map +1 -0
- package/dist/analyzers/tools/osv-scanner-deps.d.ts +3 -2
- package/dist/analyzers/tools/osv-scanner-deps.d.ts.map +1 -1
- package/dist/analyzers/tools/osv-scanner-deps.js +32 -14
- package/dist/analyzers/tools/osv-scanner-deps.js.map +1 -1
- package/dist/analyzers/tools/osv.d.ts +36 -0
- package/dist/analyzers/tools/osv.d.ts.map +1 -1
- package/dist/analyzers/tools/osv.js +26 -0
- package/dist/analyzers/tools/osv.js.map +1 -1
- package/dist/analyzers/tools/parallel.d.ts +1 -1
- package/dist/analyzers/tools/parallel.d.ts.map +1 -1
- package/dist/analyzers/tools/parallel.js +2 -2
- package/dist/analyzers/tools/parallel.js.map +1 -1
- package/dist/analyzers/tools/risk-score.d.ts +7 -0
- package/dist/analyzers/tools/risk-score.d.ts.map +1 -1
- package/dist/analyzers/tools/risk-score.js +9 -2
- package/dist/analyzers/tools/risk-score.js.map +1 -1
- package/dist/analyzers/tools/run-tests-helper.d.ts +43 -0
- package/dist/analyzers/tools/run-tests-helper.d.ts.map +1 -0
- package/dist/analyzers/tools/run-tests-helper.js +156 -0
- package/dist/analyzers/tools/run-tests-helper.js.map +1 -0
- package/dist/analyzers/tools/runner.d.ts.map +1 -1
- package/dist/analyzers/tools/runner.js +75 -12
- package/dist/analyzers/tools/runner.js.map +1 -1
- package/dist/analyzers/tools/semgrep.d.ts +39 -2
- package/dist/analyzers/tools/semgrep.d.ts.map +1 -1
- package/dist/analyzers/tools/semgrep.js +131 -9
- package/dist/analyzers/tools/semgrep.js.map +1 -1
- package/dist/analyzers/tools/timing.d.ts +17 -3
- package/dist/analyzers/tools/timing.d.ts.map +1 -1
- package/dist/analyzers/tools/timing.js +36 -14
- package/dist/analyzers/tools/timing.js.map +1 -1
- package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
- package/dist/analyzers/tools/tool-registry.js +11 -1
- package/dist/analyzers/tools/tool-registry.js.map +1 -1
- package/dist/analyzers/tools/tools-unavailable-prose.d.ts +18 -0
- package/dist/analyzers/tools/tools-unavailable-prose.d.ts.map +1 -0
- package/dist/analyzers/tools/tools-unavailable-prose.js +69 -0
- package/dist/analyzers/tools/tools-unavailable-prose.js.map +1 -0
- package/dist/analyzers/tools/upgrade-plan-resolver.d.ts.map +1 -1
- package/dist/analyzers/tools/upgrade-plan-resolver.js +7 -0
- package/dist/analyzers/tools/upgrade-plan-resolver.js.map +1 -1
- package/dist/analyzers/tools/vendored-advisor.d.ts +43 -0
- package/dist/analyzers/tools/vendored-advisor.d.ts.map +1 -0
- package/dist/analyzers/tools/vendored-advisor.js +107 -0
- package/dist/analyzers/tools/vendored-advisor.js.map +1 -0
- package/dist/analyzers/tools/walk-paths.d.ts +78 -0
- package/dist/analyzers/tools/walk-paths.d.ts.map +1 -0
- package/dist/analyzers/tools/walk-paths.js +150 -0
- package/dist/analyzers/tools/walk-paths.js.map +1 -0
- package/dist/analyzers/tools/walk-source-files.d.ts +70 -0
- package/dist/analyzers/tools/walk-source-files.d.ts.map +1 -0
- package/dist/analyzers/tools/walk-source-files.js +369 -0
- package/dist/analyzers/tools/walk-source-files.js.map +1 -0
- package/dist/analyzers/types.d.ts +204 -4
- package/dist/analyzers/types.d.ts.map +1 -1
- package/dist/analyzers/xlsx/bom.d.ts.map +1 -1
- package/dist/analyzers/xlsx/bom.js +8 -1
- package/dist/analyzers/xlsx/bom.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +557 -189
- package/dist/cli.js.map +1 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +24 -7
- package/dist/detect.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +103 -53
- package/dist/doctor.js.map +1 -1
- package/dist/languages/capabilities/provider.d.ts +130 -1
- package/dist/languages/capabilities/provider.d.ts.map +1 -1
- package/dist/languages/capabilities/types.d.ts +68 -7
- package/dist/languages/capabilities/types.d.ts.map +1 -1
- package/dist/languages/csharp.d.ts +15 -1
- package/dist/languages/csharp.d.ts.map +1 -1
- package/dist/languages/csharp.js +624 -146
- package/dist/languages/csharp.js.map +1 -1
- package/dist/languages/go.d.ts.map +1 -1
- package/dist/languages/go.js +89 -11
- package/dist/languages/go.js.map +1 -1
- package/dist/languages/index.d.ts +131 -2
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +206 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/java.d.ts.map +1 -1
- package/dist/languages/java.js +113 -26
- package/dist/languages/java.js.map +1 -1
- package/dist/languages/kotlin.d.ts.map +1 -1
- package/dist/languages/kotlin.js +132 -26
- package/dist/languages/kotlin.js.map +1 -1
- package/dist/languages/python.d.ts.map +1 -1
- package/dist/languages/python.js +149 -44
- package/dist/languages/python.js.map +1 -1
- package/dist/languages/ruby.d.ts +39 -1
- package/dist/languages/ruby.d.ts.map +1 -1
- package/dist/languages/ruby.js +178 -44
- package/dist/languages/ruby.js.map +1 -1
- package/dist/languages/rust.d.ts.map +1 -1
- package/dist/languages/rust.js +103 -16
- package/dist/languages/rust.js.map +1 -1
- package/dist/languages/types.d.ts +228 -5
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript.d.ts.map +1 -1
- package/dist/languages/typescript.js +201 -14
- package/dist/languages/typescript.js.map +1 -1
- package/dist/scoring/dimensions/documentation.d.ts +53 -0
- package/dist/scoring/dimensions/documentation.d.ts.map +1 -0
- package/dist/scoring/dimensions/documentation.js +106 -0
- package/dist/scoring/dimensions/documentation.js.map +1 -0
- package/dist/scoring/dimensions/dx.d.ts +53 -0
- package/dist/scoring/dimensions/dx.d.ts.map +1 -0
- package/dist/scoring/dimensions/dx.js +105 -0
- package/dist/scoring/dimensions/dx.js.map +1 -0
- package/dist/scoring/dimensions/maintainability.d.ts +53 -0
- package/dist/scoring/dimensions/maintainability.d.ts.map +1 -0
- package/dist/scoring/dimensions/maintainability.js +101 -0
- package/dist/scoring/dimensions/maintainability.js.map +1 -0
- package/dist/scoring/dimensions/quality.d.ts +108 -0
- package/dist/scoring/dimensions/quality.d.ts.map +1 -0
- package/dist/scoring/dimensions/quality.js +174 -0
- package/dist/scoring/dimensions/quality.js.map +1 -0
- package/dist/scoring/dimensions/security.d.ts +84 -0
- package/dist/scoring/dimensions/security.d.ts.map +1 -0
- package/dist/scoring/dimensions/security.js +135 -0
- package/dist/scoring/dimensions/security.js.map +1 -0
- package/dist/scoring/dimensions/testing.d.ts +56 -0
- package/dist/scoring/dimensions/testing.d.ts.map +1 -0
- package/dist/scoring/dimensions/testing.js +98 -0
- package/dist/scoring/dimensions/testing.js.map +1 -0
- package/dist/scoring/evaluator.d.ts +27 -0
- package/dist/scoring/evaluator.d.ts.map +1 -0
- package/dist/scoring/evaluator.js +124 -0
- package/dist/scoring/evaluator.js.map +1 -0
- package/dist/scoring/format.d.ts +34 -0
- package/dist/scoring/format.d.ts.map +1 -0
- package/dist/scoring/format.js +63 -0
- package/dist/scoring/format.js.map +1 -0
- package/dist/scoring/index.d.ts +37 -0
- package/dist/scoring/index.d.ts.map +1 -0
- package/dist/scoring/index.js +57 -0
- package/dist/scoring/index.js.map +1 -0
- package/dist/scoring/overall.d.ts +54 -0
- package/dist/scoring/overall.d.ts.map +1 -0
- package/dist/scoring/overall.js +76 -0
- package/dist/scoring/overall.js.map +1 -0
- package/dist/scoring/result.d.ts +111 -0
- package/dist/scoring/result.d.ts.map +1 -0
- package/dist/scoring/result.js +14 -0
- package/dist/scoring/result.js.map +1 -0
- package/dist/scoring/spec.d.ts +76 -0
- package/dist/scoring/spec.d.ts.map +1 -0
- package/dist/scoring/spec.js +22 -0
- package/dist/scoring/spec.js.map +1 -0
- package/dist/scoring/thresholds.d.ts +56 -0
- package/dist/scoring/thresholds.d.ts.map +1 -0
- package/dist/scoring/thresholds.js +75 -0
- package/dist/scoring/thresholds.js.map +1 -0
- package/dist/tools-cli.d.ts.map +1 -1
- package/dist/tools-cli.js +21 -2
- package/dist/tools-cli.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/.claude/commands/dashboard.md +17 -9
- package/dist/analyzers/scoring.d.ts +0 -49
- package/dist/analyzers/scoring.d.ts.map +0 -1
- package/dist/analyzers/scoring.js +0 -422
- package/dist/analyzers/scoring.js.map +0 -1
- package/dist/analyzers/security/scoring.d.ts +0 -29
- package/dist/analyzers/security/scoring.d.ts.map +0 -1
- package/dist/analyzers/security/scoring.js +0 -40
- package/dist/analyzers/security/scoring.js.map +0 -1
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.analyzeDashboard = analyzeDashboard;
|
|
37
|
+
/**
|
|
38
|
+
* Dashboard analyzer — deterministic templating that stitches every
|
|
39
|
+
* report under `.dxkit/reports/` into a single self-contained HTML
|
|
40
|
+
* dashboard. Closes D020: the prior agent-based path failed at the
|
|
41
|
+
* Write step in the sub-agent sandbox, and agent rendering is
|
|
42
|
+
* non-deterministic across Claude Code versions anyway. The agent
|
|
43
|
+
* (`dashboard-builder.md`) stays available for LLM-narrative use
|
|
44
|
+
* cases ("explain the dashboard"); this CLI owns the HTML.
|
|
45
|
+
*
|
|
46
|
+
* Inputs (both default to `<cwd>/.dxkit/reports`):
|
|
47
|
+
* - Markdown reports — per-tab content. One file per report stem
|
|
48
|
+
* (`health-audit-*.md`, `vulnerability-scan-*.md`, …). When both
|
|
49
|
+
* `<stem>-*-detailed.md` and `<stem>-*.md` exist we prefer the
|
|
50
|
+
* detailed one (richer evidence + ranked actions).
|
|
51
|
+
* - JSON reports — synthesis data for the Overview tab (hero
|
|
52
|
+
* score, dimension breakdown, badge counts, critical issues).
|
|
53
|
+
* Best-effort: missing JSON degrades to empty sections, never
|
|
54
|
+
* throws.
|
|
55
|
+
*
|
|
56
|
+
* Reference: `~/projects/external-repos/_runs/gen-dashboard-v2.js`,
|
|
57
|
+
* the script we ran by hand 2026-05-07 when the agent denied Write.
|
|
58
|
+
* That template is the source-of-truth structure being ported here.
|
|
59
|
+
*/
|
|
60
|
+
const fs = __importStar(require("fs"));
|
|
61
|
+
const path = __importStar(require("path"));
|
|
62
|
+
/** Known report stems → display config. Order here = sidebar order. */
|
|
63
|
+
const REPORT_STEMS = [
|
|
64
|
+
{ key: 'health', stem: 'health-audit', icon: '🏥', label: 'Health Audit', color: '#3fb950' },
|
|
65
|
+
{
|
|
66
|
+
key: 'vulnerabilities',
|
|
67
|
+
stem: 'vulnerability-scan',
|
|
68
|
+
icon: '🔒',
|
|
69
|
+
label: 'Vulnerability Scan',
|
|
70
|
+
color: '#f85149',
|
|
71
|
+
},
|
|
72
|
+
{ key: 'testGaps', stem: 'test-gaps', icon: '🧪', label: 'Test Gaps', color: '#d29922' },
|
|
73
|
+
{ key: 'quality', stem: 'quality-review', icon: '✨', label: 'Code Quality', color: '#bc8cff' },
|
|
74
|
+
{
|
|
75
|
+
key: 'dev',
|
|
76
|
+
stem: 'developer-report',
|
|
77
|
+
icon: '👥',
|
|
78
|
+
label: 'Developer Report',
|
|
79
|
+
color: '#58a6ff',
|
|
80
|
+
},
|
|
81
|
+
{ key: 'licenses', stem: 'licenses', icon: '📜', label: 'Licenses', color: '#39d2c0' },
|
|
82
|
+
{ key: 'bom', stem: 'bom', icon: '📦', label: 'Bill of Materials', color: '#39d2c0' },
|
|
83
|
+
];
|
|
84
|
+
function analyzeDashboard(cwd, options = {}) {
|
|
85
|
+
const reportsDir = options.reportsDir ?? path.join(cwd, '.dxkit', 'reports');
|
|
86
|
+
const jsonDir = options.jsonDir ?? reportsDir;
|
|
87
|
+
const projectName = options.projectName ?? deriveProjectName(cwd);
|
|
88
|
+
if (!fs.existsSync(reportsDir)) {
|
|
89
|
+
throw new Error(`Reports directory not found: ${reportsDir}\n` +
|
|
90
|
+
`Run 'vyuh-dxkit health .' (or any other report command) first to populate it.`);
|
|
91
|
+
}
|
|
92
|
+
const entries = fs.readdirSync(reportsDir);
|
|
93
|
+
const mdFiles = entries.filter((f) => f.endsWith('.md'));
|
|
94
|
+
const jsonFiles = fs.existsSync(jsonDir)
|
|
95
|
+
? fs.readdirSync(jsonDir).filter((f) => f.endsWith('.json'))
|
|
96
|
+
: [];
|
|
97
|
+
// For each known stem, pick the most-recent markdown (preferring
|
|
98
|
+
// -detailed.md over the plain variant) and the most-recent JSON
|
|
99
|
+
// (preferring -detailed.json, falling back to bare `<key>.json`
|
|
100
|
+
// for users of the legacy synth-JSON layout that `gen-dashboard-v2`
|
|
101
|
+
// documented).
|
|
102
|
+
const reports = {};
|
|
103
|
+
const jsonData = {};
|
|
104
|
+
const navEntries = [];
|
|
105
|
+
for (const cfg of REPORT_STEMS) {
|
|
106
|
+
const md = pickMostRecent(mdFiles, cfg.stem);
|
|
107
|
+
if (md) {
|
|
108
|
+
const reportKey = md.replace(/\.md$/, '');
|
|
109
|
+
reports[reportKey] = fs.readFileSync(path.join(reportsDir, md), 'utf-8');
|
|
110
|
+
const jsonPath = findJsonFor(jsonFiles, cfg.stem, cfg.key, jsonDir);
|
|
111
|
+
if (jsonPath) {
|
|
112
|
+
try {
|
|
113
|
+
jsonData[cfg.key] = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Bad JSON degrades to "missing"; the Overview falls back
|
|
117
|
+
// to empty values rather than failing the whole dashboard.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
navEntries.push({
|
|
121
|
+
key: cfg.key,
|
|
122
|
+
reportKey,
|
|
123
|
+
icon: cfg.icon,
|
|
124
|
+
label: cfg.label,
|
|
125
|
+
color: cfg.color,
|
|
126
|
+
badge: '', // filled in once we've computed Overview numbers below
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Synthesize Overview data.
|
|
131
|
+
const health = jsonData.health ?? {};
|
|
132
|
+
const vulns = jsonData.vulnerabilities ?? {};
|
|
133
|
+
const testGaps = jsonData.testGaps ?? {};
|
|
134
|
+
const quality = jsonData.quality ?? {};
|
|
135
|
+
const bom = jsonData.bom ?? {};
|
|
136
|
+
const licenses = jsonData.licenses ?? {};
|
|
137
|
+
const healthSummary = health.summary ?? {};
|
|
138
|
+
const healthScore = typeof healthSummary.overallScore === 'number' ? healthSummary.overallScore : null;
|
|
139
|
+
const healthGrade = typeof healthSummary.rating === 'string' ? healthSummary.rating : null;
|
|
140
|
+
const dims = health.dimensions ?? {};
|
|
141
|
+
const orderedDims = [
|
|
142
|
+
['Testing', dims.testing],
|
|
143
|
+
['Code Quality', dims.quality],
|
|
144
|
+
['Documentation', dims.documentation],
|
|
145
|
+
['Security', dims.security],
|
|
146
|
+
['Maintainability', dims.maintainability],
|
|
147
|
+
['Developer Experience', dims.developerExperience],
|
|
148
|
+
];
|
|
149
|
+
// D047 (2.4.7 fix-forward): the dashboard previously read only
|
|
150
|
+
// `vulns.findings` (code findings — semgrep/gitleaks). Dependency
|
|
151
|
+
// vulnerabilities live in `vulns.summary.dependencies.findings`
|
|
152
|
+
// (per VulnReport schema v11+). Reading only the code-findings
|
|
153
|
+
// array caused the Vulnerabilities tile to show 0 on dpl-studio
|
|
154
|
+
// even when osv-scanner-nuget-direct surfaced 1 HIGH (MongoDB.Driver)
|
|
155
|
+
// + 1 MEDIUM (SharpCompress). Union both arrays so the tile and
|
|
156
|
+
// the "Critical Issues at a Glance" section reflect the full
|
|
157
|
+
// security signal.
|
|
158
|
+
const codeFindings = Array.isArray(vulns.findings)
|
|
159
|
+
? vulns.findings
|
|
160
|
+
: [];
|
|
161
|
+
const summary = vulns.summary;
|
|
162
|
+
const codeBucket = summary?.findings;
|
|
163
|
+
const depBucket = summary?.dependencies;
|
|
164
|
+
const depFindings = Array.isArray(depBucket?.findings)
|
|
165
|
+
? depBucket?.findings
|
|
166
|
+
: [];
|
|
167
|
+
// `vulnFindings` is still the per-finding union: the dashboard needs
|
|
168
|
+
// it for the per-finding "Critical Issues at a Glance" filter +
|
|
169
|
+
// length count. Post-C1.2 both arrays are already dedup'd by the
|
|
170
|
+
// canonical aggregator, so the union is unique-by-construction.
|
|
171
|
+
const vulnFindings = [...codeFindings, ...depFindings];
|
|
172
|
+
// G_v4_8 (2.4.7 Phase C1.5): severity buckets read directly from
|
|
173
|
+
// `vulns.summary.findings` + `vulns.summary.dependencies` — both
|
|
174
|
+
// populated upstream in C1.2 from `aggregate.codeBySeverity` /
|
|
175
|
+
// `aggregate.secretsBySeverity` + `aggregate.depBySeverity`.
|
|
176
|
+
//
|
|
177
|
+
// Pre-C1.5 dashboard had its own
|
|
178
|
+
// `for (const f of vulnFindings) vulnBySeverity[f.severity]++` loop
|
|
179
|
+
// that produced numerically-identical results today — but as a
|
|
180
|
+
// parallel aggregation path it was structurally at risk of drifting
|
|
181
|
+
// from health + vuln-scan when inclusion rules evolve. Reading the
|
|
182
|
+
// canonical buckets directly closes the architectural gap.
|
|
183
|
+
const vulnBySeverity = {
|
|
184
|
+
critical: (codeBucket?.critical ?? 0) + (depBucket?.critical ?? 0),
|
|
185
|
+
high: (codeBucket?.high ?? 0) + (depBucket?.high ?? 0),
|
|
186
|
+
medium: (codeBucket?.medium ?? 0) + (depBucket?.medium ?? 0),
|
|
187
|
+
low: (codeBucket?.low ?? 0) + (depBucket?.low ?? 0),
|
|
188
|
+
};
|
|
189
|
+
const gaps = Array.isArray(testGaps.gaps) ? testGaps.gaps : [];
|
|
190
|
+
const gapsByRisk = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
191
|
+
for (const g of gaps) {
|
|
192
|
+
const risk = g.risk ?? 'unknown';
|
|
193
|
+
gapsByRisk[risk] = (gapsByRisk[risk] ?? 0) + 1;
|
|
194
|
+
}
|
|
195
|
+
const gapCount = gaps.length;
|
|
196
|
+
const bomSummary = bom.summary ?? {};
|
|
197
|
+
const advisoryCount = bomSummary.totalAdvisories ?? 0;
|
|
198
|
+
const topPackages = bomSummary.totalPackages ?? 0;
|
|
199
|
+
const totalPackages = bomSummary.unfilteredTotalPackages ?? 0;
|
|
200
|
+
const qualityMetrics = quality.metrics ?? {};
|
|
201
|
+
const slopScore = typeof qualityMetrics.slopScore === 'number' ? qualityMetrics.slopScore : null;
|
|
202
|
+
const licenseSummary = licenses.summary ?? {};
|
|
203
|
+
const totalLicensePkgs = licenseSummary.totalPackages ?? 0;
|
|
204
|
+
const unknownLicenses = licenseSummary.unknownCount ?? 0;
|
|
205
|
+
// Top critical issues from each surface. D068 (2.4.7): track the
|
|
206
|
+
// unfiltered totals alongside the capped slice so the renderer can
|
|
207
|
+
// disclose "showing N of M" when items are dropped. Pre-fix the
|
|
208
|
+
// dashboard silently capped at 3+3+2 = 8 with no indication that
|
|
209
|
+
// more critical issues exist — a customer with 20 CRITICAL untested
|
|
210
|
+
// files would see 3 and infer "only 3 critical things in the repo."
|
|
211
|
+
const criticalIssues = [];
|
|
212
|
+
const vulnCriticals = vulnFindings.filter((f) => f.severity === 'critical' || f.severity === 'high');
|
|
213
|
+
for (const f of vulnCriticals.slice(0, 3)) {
|
|
214
|
+
criticalIssues.push({
|
|
215
|
+
type: 'vuln',
|
|
216
|
+
label: `${f.tool ?? 'security'} · ${f.rule ?? f.id ?? 'finding'}`,
|
|
217
|
+
detail: f.file
|
|
218
|
+
? `${f.file}:${f.line ?? '?'}`
|
|
219
|
+
: f.package
|
|
220
|
+
? `${f.package}@${f.installedVersion ?? '?'}`
|
|
221
|
+
: '',
|
|
222
|
+
severity: f.severity ?? 'high',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
const gapCriticals = gaps.filter((g) => g.risk === 'critical' || g.risk === 'high');
|
|
226
|
+
for (const g of gapCriticals.slice(0, 3)) {
|
|
227
|
+
criticalIssues.push({
|
|
228
|
+
type: 'gap',
|
|
229
|
+
label: `Untested (${g.risk}): ${g.path ?? g.file ?? '?'}`,
|
|
230
|
+
detail: typeof g.lines === 'number' ? `${g.lines} lines` : (g.reason ?? ''),
|
|
231
|
+
severity: g.risk ?? 'high',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
const bomTriage = bomSummary.triage ?? [];
|
|
235
|
+
for (const t of bomTriage.slice(0, 2)) {
|
|
236
|
+
criticalIssues.push({
|
|
237
|
+
type: 'bom',
|
|
238
|
+
label: `Upgrade: ${t.package ?? '?'} → resolves ${t.advisoryCount ?? '?'} advisories`,
|
|
239
|
+
detail: t.advice ?? '',
|
|
240
|
+
severity: t.severity ?? 'high',
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
const criticalIssuesTotal = vulnCriticals.length + gapCriticals.length + bomTriage.length;
|
|
244
|
+
// Fill in sidebar badges now that we have the synthesis numbers.
|
|
245
|
+
for (const e of navEntries) {
|
|
246
|
+
if (e.key === 'health' && healthScore !== null)
|
|
247
|
+
e.badge = `${healthScore}/100`;
|
|
248
|
+
else if (e.key === 'vulnerabilities')
|
|
249
|
+
e.badge = `${vulnFindings.length}`;
|
|
250
|
+
else if (e.key === 'testGaps')
|
|
251
|
+
e.badge = `${gapCount}`;
|
|
252
|
+
else if (e.key === 'quality' && slopScore !== null)
|
|
253
|
+
e.badge = `${slopScore}/100`;
|
|
254
|
+
else if (e.key === 'licenses' && totalLicensePkgs)
|
|
255
|
+
e.badge = `${totalLicensePkgs}`;
|
|
256
|
+
else if (e.key === 'bom' && advisoryCount)
|
|
257
|
+
e.badge = `${advisoryCount} adv`;
|
|
258
|
+
}
|
|
259
|
+
const overviewBadge = healthScore !== null ? `${healthScore}/100 (${healthGrade ?? '?'})` : '';
|
|
260
|
+
const generationDate = new Date().toISOString().slice(0, 10);
|
|
261
|
+
const html = renderHtml({
|
|
262
|
+
projectName,
|
|
263
|
+
generationDate,
|
|
264
|
+
healthScore,
|
|
265
|
+
healthGrade,
|
|
266
|
+
overviewBadge,
|
|
267
|
+
orderedDims,
|
|
268
|
+
vulnFindings,
|
|
269
|
+
vulnBySeverity,
|
|
270
|
+
gapCount,
|
|
271
|
+
gapsByRisk,
|
|
272
|
+
advisoryCount,
|
|
273
|
+
topPackages,
|
|
274
|
+
totalPackages,
|
|
275
|
+
slopScore,
|
|
276
|
+
qualityMetrics,
|
|
277
|
+
totalLicensePkgs,
|
|
278
|
+
unknownLicenses,
|
|
279
|
+
licenseByCount: Object.keys(licenseSummary.byLicense ?? {}).length,
|
|
280
|
+
testGapsSummary: testGaps.summary ?? {},
|
|
281
|
+
criticalIssues,
|
|
282
|
+
criticalIssuesTotal,
|
|
283
|
+
reports,
|
|
284
|
+
navEntries,
|
|
285
|
+
});
|
|
286
|
+
return {
|
|
287
|
+
html,
|
|
288
|
+
reportCount: Object.keys(reports).length,
|
|
289
|
+
criticalIssueCount: criticalIssues.length,
|
|
290
|
+
summary: {
|
|
291
|
+
healthScore,
|
|
292
|
+
healthGrade,
|
|
293
|
+
vulnCount: vulnFindings.length,
|
|
294
|
+
gapCount,
|
|
295
|
+
advisoryCount,
|
|
296
|
+
slopScore,
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function deriveProjectName(cwd) {
|
|
301
|
+
try {
|
|
302
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8'));
|
|
303
|
+
if (typeof pkg.name === 'string' && pkg.name.length > 0)
|
|
304
|
+
return pkg.name;
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
// No package.json or unreadable — fall through to basename.
|
|
308
|
+
}
|
|
309
|
+
return path.basename(path.resolve(cwd));
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Pick the most-recent markdown file matching `<stem>-*.md`, preferring
|
|
313
|
+
* the `*-detailed.md` variant when both exist for the same date.
|
|
314
|
+
* "Most recent" is determined by lexical sort on the filename, which
|
|
315
|
+
* works because every dxkit report file embeds an ISO date prefix.
|
|
316
|
+
*/
|
|
317
|
+
function pickMostRecent(files, stem) {
|
|
318
|
+
const stemPrefix = `${stem}-`;
|
|
319
|
+
const matching = files.filter((f) => f.startsWith(stemPrefix));
|
|
320
|
+
if (matching.length === 0)
|
|
321
|
+
return undefined;
|
|
322
|
+
matching.sort();
|
|
323
|
+
// Take the latest date that exists, then prefer -detailed within that
|
|
324
|
+
// date if it's available.
|
|
325
|
+
const latest = matching[matching.length - 1];
|
|
326
|
+
// Extract date prefix `<stem>-<YYYY-MM-DD>`. If we can't parse one,
|
|
327
|
+
// fall back to the lexically-latest file.
|
|
328
|
+
const dateMatch = latest.match(new RegExp(`^${escapeRegex(stemPrefix)}(\\d{4}-\\d{2}-\\d{2})`));
|
|
329
|
+
if (!dateMatch)
|
|
330
|
+
return latest;
|
|
331
|
+
const date = dateMatch[1];
|
|
332
|
+
const detailed = `${stemPrefix}${date}-detailed.md`;
|
|
333
|
+
if (matching.includes(detailed))
|
|
334
|
+
return detailed;
|
|
335
|
+
const plain = `${stemPrefix}${date}.md`;
|
|
336
|
+
if (matching.includes(plain))
|
|
337
|
+
return plain;
|
|
338
|
+
return latest;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Locate a JSON synthesis file for a report stem. Priority:
|
|
342
|
+
* 1. `<stem>-<date>-detailed.json` (what `--detailed --json` produces)
|
|
343
|
+
* 2. `<key>.json` (bare-named, gen-dashboard-v2 legacy layout)
|
|
344
|
+
* Returns an absolute path or undefined.
|
|
345
|
+
*/
|
|
346
|
+
function findJsonFor(jsonFiles, stem, key, jsonDir) {
|
|
347
|
+
const detailed = jsonFiles
|
|
348
|
+
.filter((f) => f.startsWith(`${stem}-`) && f.endsWith('-detailed.json'))
|
|
349
|
+
.sort();
|
|
350
|
+
if (detailed.length > 0) {
|
|
351
|
+
return path.join(jsonDir, detailed[detailed.length - 1]);
|
|
352
|
+
}
|
|
353
|
+
const bare = `${key}.json`;
|
|
354
|
+
if (jsonFiles.includes(bare))
|
|
355
|
+
return path.join(jsonDir, bare);
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
function escapeRegex(s) {
|
|
359
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
360
|
+
}
|
|
361
|
+
function escapeHtml(s) {
|
|
362
|
+
return String(s)
|
|
363
|
+
.replace(/&/g, '&')
|
|
364
|
+
.replace(/</g, '<')
|
|
365
|
+
.replace(/>/g, '>')
|
|
366
|
+
.replace(/"/g, '"');
|
|
367
|
+
}
|
|
368
|
+
function sevColor(sev) {
|
|
369
|
+
if (sev === 'critical')
|
|
370
|
+
return '#f85149';
|
|
371
|
+
if (sev === 'high')
|
|
372
|
+
return '#ff7b72';
|
|
373
|
+
if (sev === 'medium')
|
|
374
|
+
return '#d29922';
|
|
375
|
+
if (sev === 'low')
|
|
376
|
+
return '#3fb950';
|
|
377
|
+
return '#8b949e';
|
|
378
|
+
}
|
|
379
|
+
function renderHtml(a) {
|
|
380
|
+
const reportsJson = JSON.stringify(a.reports);
|
|
381
|
+
const navJson = JSON.stringify(a.navEntries);
|
|
382
|
+
// Server-rendered Overview tab (the JS layer just swaps innerHTML
|
|
383
|
+
// when the user navigates between tabs). Keeps the dashboard usable
|
|
384
|
+
// even if marked.min.js fails to load from the CDN — the Overview
|
|
385
|
+
// is always visible.
|
|
386
|
+
return `<!DOCTYPE html>
|
|
387
|
+
<html lang="en">
|
|
388
|
+
<head>
|
|
389
|
+
<meta charset="UTF-8">
|
|
390
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
391
|
+
<title>${escapeHtml(a.projectName)} — DXKit Dashboard</title>
|
|
392
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
393
|
+
<style>
|
|
394
|
+
:root {
|
|
395
|
+
--bg-primary: #0d1117;
|
|
396
|
+
--bg-secondary: #161b22;
|
|
397
|
+
--bg-tertiary: #21262d;
|
|
398
|
+
--bg-card: #1e2630;
|
|
399
|
+
--border: #30363d;
|
|
400
|
+
--text-primary: #f0f6fc;
|
|
401
|
+
--text-secondary: #c9d1d9;
|
|
402
|
+
--text-muted: #8b949e;
|
|
403
|
+
--accent-blue: #58a6ff;
|
|
404
|
+
--accent-green: #3fb950;
|
|
405
|
+
--accent-red: #f85149;
|
|
406
|
+
--accent-orange: #d29922;
|
|
407
|
+
--accent-purple: #bc8cff;
|
|
408
|
+
--accent-cyan: #39d2c0;
|
|
409
|
+
--sidebar-width: 320px;
|
|
410
|
+
}
|
|
411
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
412
|
+
body {
|
|
413
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
|
|
414
|
+
background: var(--bg-primary);
|
|
415
|
+
color: var(--text-secondary);
|
|
416
|
+
display: flex;
|
|
417
|
+
height: 100vh;
|
|
418
|
+
overflow: hidden;
|
|
419
|
+
}
|
|
420
|
+
.sidebar { width: var(--sidebar-width); background: var(--bg-secondary); border-right: 1px solid var(--border); display: flex; flex-direction: column; flex-shrink: 0; overflow: hidden; }
|
|
421
|
+
.sidebar-header { padding: 22px 20px; border-bottom: 1px solid var(--border); }
|
|
422
|
+
.sidebar-header h1 { font-size: 17px; color: var(--text-primary); font-weight: 600; }
|
|
423
|
+
.sidebar-header .project-name { font-size: 13px; color: var(--accent-blue); margin-top: 4px; }
|
|
424
|
+
.sidebar-header .generated { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
|
|
425
|
+
.sidebar-nav { flex: 1; overflow-y: auto; padding: 12px; }
|
|
426
|
+
.nav-section-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.8px; color: var(--text-muted); padding: 12px 10px 6px; }
|
|
427
|
+
.nav-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; background: none; border: none; color: var(--text-secondary); padding: 10px 12px; border-radius: 8px; cursor: pointer; font-size: 13px; margin-bottom: 2px; transition: all 0.15s ease; font-family: inherit; }
|
|
428
|
+
.nav-item:hover { background: var(--bg-tertiary); color: var(--text-primary); }
|
|
429
|
+
.nav-item.active { background: var(--accent-blue); color: white; font-weight: 500; }
|
|
430
|
+
.nav-item .icon { font-size: 16px; flex-shrink: 0; }
|
|
431
|
+
.nav-item .label { flex: 1; }
|
|
432
|
+
.nav-item .badge { font-size: 11px; padding: 2px 8px; border-radius: 10px; background: var(--bg-tertiary); color: var(--text-muted); flex-shrink: 0; font-weight: 500; }
|
|
433
|
+
.nav-item.active .badge { background: rgba(255,255,255,0.2); color: white; }
|
|
434
|
+
.sidebar-footer { padding: 14px 20px; border-top: 1px solid var(--border); font-size: 11px; color: var(--text-muted); }
|
|
435
|
+
.sidebar-footer a { color: var(--accent-blue); text-decoration: none; }
|
|
436
|
+
.main { flex: 1; overflow-y: auto; padding: 32px 40px 60px; }
|
|
437
|
+
.main-inner { max-width: 1100px; margin: 0 auto; }
|
|
438
|
+
.hero { display: grid; grid-template-columns: auto 1fr; gap: 28px; align-items: center; padding: 28px; background: var(--bg-card); border-radius: 14px; margin-bottom: 28px; border: 1px solid var(--border); }
|
|
439
|
+
.hero-score { font-size: 64px; font-weight: 700; color: var(--text-primary); line-height: 1; }
|
|
440
|
+
.hero-score .grade { font-size: 28px; color: var(--accent-blue); display: block; margin-top: 6px; }
|
|
441
|
+
.hero-meta h2 { font-size: 22px; color: var(--text-primary); margin-bottom: 6px; }
|
|
442
|
+
.hero-meta p { color: var(--text-muted); }
|
|
443
|
+
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px; }
|
|
444
|
+
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 24px; }
|
|
445
|
+
.stat-card { background: var(--bg-card); padding: 20px; border-radius: 10px; border: 1px solid var(--border); }
|
|
446
|
+
.stat-card .label { font-size: 11px; text-transform: uppercase; color: var(--text-muted); letter-spacing: 0.8px; margin-bottom: 8px; }
|
|
447
|
+
.stat-card .value { font-size: 28px; color: var(--text-primary); font-weight: 600; }
|
|
448
|
+
.stat-card .sub { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
|
|
449
|
+
.dim-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 24px; }
|
|
450
|
+
.dim-card { background: var(--bg-card); padding: 16px; border-radius: 10px; border: 1px solid var(--border); display: flex; align-items: center; gap: 16px; }
|
|
451
|
+
.dim-name { flex: 1; font-size: 14px; color: var(--text-primary); font-weight: 500; }
|
|
452
|
+
.dim-bar { flex: 0 0 140px; height: 6px; background: var(--bg-tertiary); border-radius: 3px; overflow: hidden; }
|
|
453
|
+
.dim-bar-fill { height: 100%; transition: width 0.3s; }
|
|
454
|
+
.dim-score { font-size: 18px; color: var(--text-primary); font-weight: 600; min-width: 60px; text-align: right; }
|
|
455
|
+
.crit-list { background: var(--bg-card); border-radius: 10px; padding: 4px; border: 1px solid var(--border); }
|
|
456
|
+
.crit-item { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: 8px; }
|
|
457
|
+
.crit-item:hover { background: var(--bg-tertiary); }
|
|
458
|
+
.crit-item:not(:last-child) { border-bottom: 1px solid var(--border); }
|
|
459
|
+
.crit-sev { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
460
|
+
.crit-text { flex: 1; }
|
|
461
|
+
.crit-text .label { font-size: 13px; color: var(--text-primary); }
|
|
462
|
+
.crit-text .detail { font-size: 12px; color: var(--text-muted); margin-top: 2px; font-family: 'SF Mono', 'Fira Code', monospace; }
|
|
463
|
+
.section-title { font-size: 16px; color: var(--text-primary); font-weight: 600; margin-bottom: 14px; margin-top: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
|
|
464
|
+
.main-inner h1 { font-size: 26px; color: var(--text-primary); border-bottom: 1px solid var(--border); padding-bottom: 12px; margin-bottom: 20px; }
|
|
465
|
+
.main-inner h2 { font-size: 20px; color: var(--text-primary); margin-top: 28px; margin-bottom: 12px; padding-bottom: 6px; border-bottom: 1px solid var(--border); }
|
|
466
|
+
.main-inner h3 { font-size: 16px; color: var(--text-primary); margin-top: 22px; margin-bottom: 8px; }
|
|
467
|
+
.main-inner h4 { font-size: 14px; color: var(--text-primary); margin-top: 16px; margin-bottom: 6px; }
|
|
468
|
+
.main-inner p { line-height: 1.7; margin-bottom: 12px; }
|
|
469
|
+
.main-inner a { color: var(--accent-blue); text-decoration: none; }
|
|
470
|
+
.main-inner a:hover { text-decoration: underline; }
|
|
471
|
+
.main-inner strong { color: var(--text-primary); }
|
|
472
|
+
.main-inner em { color: var(--text-muted); }
|
|
473
|
+
.main-inner ul, .main-inner ol { padding-left: 22px; margin-bottom: 14px; }
|
|
474
|
+
.main-inner li { margin-bottom: 4px; line-height: 1.6; }
|
|
475
|
+
.main-inner table { border-collapse: collapse; width: 100%; margin-bottom: 18px; font-size: 13px; }
|
|
476
|
+
.main-inner th { background: var(--bg-secondary); color: var(--text-primary); font-weight: 600; text-align: left; padding: 10px 14px; border: 1px solid var(--border); }
|
|
477
|
+
.main-inner td { padding: 10px 14px; border: 1px solid var(--border); }
|
|
478
|
+
.main-inner tr:hover td { background: rgba(56, 139, 253, 0.04); }
|
|
479
|
+
.main-inner code { background: var(--bg-secondary); padding: 2px 7px; border-radius: 5px; font-size: 12px; font-family: 'SF Mono', 'Fira Code', monospace; color: var(--accent-blue); }
|
|
480
|
+
.main-inner pre { background: var(--bg-secondary); padding: 16px; border-radius: 10px; overflow-x: auto; margin-bottom: 16px; border: 1px solid var(--border); }
|
|
481
|
+
.main-inner pre code { background: none; padding: 0; color: var(--text-secondary); font-size: 12px; }
|
|
482
|
+
.main-inner blockquote { border-left: 3px solid var(--accent-blue); padding: 8px 16px; color: var(--text-muted); margin-bottom: 12px; background: rgba(56, 139, 253, 0.04); border-radius: 0 6px 6px 0; }
|
|
483
|
+
.main-inner hr { border: none; border-top: 1px solid var(--border); margin: 24px 0; }
|
|
484
|
+
.mobile-toggle { display: none; position: fixed; top: 12px; left: 12px; z-index: 100; background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-primary); padding: 8px 12px; border-radius: 8px; cursor: pointer; }
|
|
485
|
+
@media (max-width: 768px) {
|
|
486
|
+
.sidebar { position: fixed; left: -320px; z-index: 50; height: 100vh; transition: left 0.3s ease; }
|
|
487
|
+
.sidebar.open { left: 0; box-shadow: 4px 0 20px rgba(0,0,0,0.5); }
|
|
488
|
+
.mobile-toggle { display: block; }
|
|
489
|
+
.main { padding: 56px 20px 40px; }
|
|
490
|
+
.grid-3, .dim-grid, .grid-2 { grid-template-columns: 1fr; }
|
|
491
|
+
.hero { grid-template-columns: 1fr; text-align: center; }
|
|
492
|
+
}
|
|
493
|
+
@media print {
|
|
494
|
+
body { background: white; color: #1a1a1a; }
|
|
495
|
+
.sidebar, .mobile-toggle { display: none; }
|
|
496
|
+
.main { padding: 20px; }
|
|
497
|
+
.main-inner h1, .main-inner h2, .main-inner h3 { color: #1a1a1a; }
|
|
498
|
+
.main-inner code { background: #f0f0f0; color: #1a1a1a; }
|
|
499
|
+
.stat-card, .hero, .crit-list, .dim-card { background: #f6f8fa; border: 1px solid #ddd; }
|
|
500
|
+
.hero-score, .stat-card .value, .dim-score { color: #1a1a1a; }
|
|
501
|
+
}
|
|
502
|
+
::-webkit-scrollbar { width: 8px; }
|
|
503
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
504
|
+
::-webkit-scrollbar-thumb { background: var(--bg-tertiary); border-radius: 4px; }
|
|
505
|
+
::-webkit-scrollbar-thumb:hover { background: var(--border); }
|
|
506
|
+
.main-inner { animation: fadeIn 0.2s ease; }
|
|
507
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
|
508
|
+
</style>
|
|
509
|
+
</head>
|
|
510
|
+
<body>
|
|
511
|
+
<button class="mobile-toggle" onclick="document.querySelector('.sidebar').classList.toggle('open')">☰</button>
|
|
512
|
+
|
|
513
|
+
<aside class="sidebar">
|
|
514
|
+
<div class="sidebar-header">
|
|
515
|
+
<h1>DXKit Dashboard</h1>
|
|
516
|
+
<div class="project-name">${escapeHtml(a.projectName)}</div>
|
|
517
|
+
<div class="generated">Generated ${a.generationDate}</div>
|
|
518
|
+
</div>
|
|
519
|
+
<div class="sidebar-nav" id="nav">
|
|
520
|
+
<div class="nav-section-label">Overview</div>
|
|
521
|
+
<button class="nav-item active" data-target="overview">
|
|
522
|
+
<span class="icon">📊</span>
|
|
523
|
+
<span class="label">Summary</span>
|
|
524
|
+
${a.overviewBadge ? `<span class="badge">${escapeHtml(a.overviewBadge)}</span>` : ''}
|
|
525
|
+
</button>
|
|
526
|
+
<div class="nav-section-label">Reports</div>
|
|
527
|
+
</div>
|
|
528
|
+
<div class="sidebar-footer">
|
|
529
|
+
Powered by <a href="https://www.npmjs.com/package/@vyuhlabs/dxkit" target="_blank">VyuhLabs DXKit</a>
|
|
530
|
+
</div>
|
|
531
|
+
</aside>
|
|
532
|
+
|
|
533
|
+
<main class="main">
|
|
534
|
+
<div class="main-inner" id="content">
|
|
535
|
+
<div id="overview-content">
|
|
536
|
+
${a.healthScore !== null
|
|
537
|
+
? `
|
|
538
|
+
<div class="hero">
|
|
539
|
+
<div>
|
|
540
|
+
<div class="hero-score">${a.healthScore}<span style="color:var(--text-muted);font-size:36px">/100</span><span class="grade">Rating ${escapeHtml(a.healthGrade ?? '')}</span></div>
|
|
541
|
+
</div>
|
|
542
|
+
<div class="hero-meta">
|
|
543
|
+
<h2>Overall Codebase Health</h2>
|
|
544
|
+
<p>Computed across 6 dimensions: testing, code quality, documentation, security, maintainability, developer experience.</p>
|
|
545
|
+
</div>
|
|
546
|
+
</div>`
|
|
547
|
+
: ''}
|
|
548
|
+
|
|
549
|
+
<div class="section-title">Score Breakdown — 6 Dimensions</div>
|
|
550
|
+
<div class="dim-grid">
|
|
551
|
+
${a.orderedDims
|
|
552
|
+
.map(([name, dim]) => {
|
|
553
|
+
if (!dim)
|
|
554
|
+
return '';
|
|
555
|
+
const score = dim.score ?? 0;
|
|
556
|
+
const color = score >= 70
|
|
557
|
+
? 'var(--accent-green)'
|
|
558
|
+
: score >= 50
|
|
559
|
+
? 'var(--accent-orange)'
|
|
560
|
+
: 'var(--accent-red)';
|
|
561
|
+
return `<div class="dim-card">
|
|
562
|
+
<div class="dim-name">${escapeHtml(name)}</div>
|
|
563
|
+
<div class="dim-bar"><div class="dim-bar-fill" style="width:${score}%;background:${color}"></div></div>
|
|
564
|
+
<div class="dim-score">${score}/100</div>
|
|
565
|
+
</div>`;
|
|
566
|
+
})
|
|
567
|
+
.join('')}
|
|
568
|
+
</div>
|
|
569
|
+
|
|
570
|
+
<div class="section-title">Key Metrics</div>
|
|
571
|
+
<div class="grid-3">
|
|
572
|
+
<div class="stat-card">
|
|
573
|
+
<div class="label">Vulnerabilities</div>
|
|
574
|
+
<div class="value" style="color:${a.vulnFindings.length > 0 ? 'var(--accent-red)' : 'var(--accent-green)'}">${a.vulnFindings.length}</div>
|
|
575
|
+
<div class="sub">${a.vulnBySeverity.critical ?? 0} critical · ${a.vulnBySeverity.high ?? 0} high · ${a.vulnBySeverity.medium ?? 0} medium · ${a.vulnBySeverity.low ?? 0} low</div>
|
|
576
|
+
</div>
|
|
577
|
+
<div class="stat-card">
|
|
578
|
+
<div class="label">Test Gaps</div>
|
|
579
|
+
<div class="value" style="color:${a.gapCount > 0 ? 'var(--accent-orange)' : 'var(--accent-green)'}">${a.gapCount}</div>
|
|
580
|
+
<div class="sub">${a.gapsByRisk.critical ?? 0} critical · ${a.gapsByRisk.high ?? 0} high · ${a.gapsByRisk.medium ?? 0} medium · ${a.gapsByRisk.low ?? 0} low</div>
|
|
581
|
+
</div>
|
|
582
|
+
<div class="stat-card">
|
|
583
|
+
<div class="label">BoM Advisories</div>
|
|
584
|
+
<div class="value" style="color:${a.advisoryCount > 0 ? 'var(--accent-orange)' : 'var(--accent-green)'}">${a.advisoryCount}</div>
|
|
585
|
+
<div class="sub">${a.topPackages} top-level packages of ${a.totalPackages} total</div>
|
|
586
|
+
</div>
|
|
587
|
+
<div class="stat-card">
|
|
588
|
+
<div class="label">Slop Score</div>
|
|
589
|
+
<div class="value" style="color:${a.slopScore !== null && a.slopScore < 50 ? 'var(--accent-orange)' : 'var(--accent-green)'}">${a.slopScore !== null ? `${a.slopScore}/100` : 'n/a'}</div>
|
|
590
|
+
<div class="sub">${a.qualityMetrics.duplication?.percentage !== undefined ? `${a.qualityMetrics.duplication.percentage}% duplication` : ''}${a.qualityMetrics.lintErrors !== undefined ? ` · ${a.qualityMetrics.lintErrors} lint errors` : ''}</div>
|
|
591
|
+
</div>
|
|
592
|
+
<div class="stat-card">
|
|
593
|
+
<div class="label">Licenses</div>
|
|
594
|
+
<div class="value">${a.totalLicensePkgs}</div>
|
|
595
|
+
<div class="sub">${a.unknownLicenses} unknown · ${a.licenseByCount} distinct types</div>
|
|
596
|
+
</div>
|
|
597
|
+
<div class="stat-card">
|
|
598
|
+
<div class="label">Source Files (testable)</div>
|
|
599
|
+
<div class="value">${a.testGapsSummary.sourceFiles ?? 'n/a'}</div>
|
|
600
|
+
<div class="sub">${a.testGapsSummary.activeTestFiles ?? 0} test files · post-exclusion count · coverage source: ${escapeHtml(a.testGapsSummary.coverageSource ?? 'n/a')}</div>
|
|
601
|
+
</div>
|
|
602
|
+
</div>
|
|
603
|
+
|
|
604
|
+
${a.criticalIssues.length > 0
|
|
605
|
+
? `
|
|
606
|
+
<div class="section-title">Critical Issues at a Glance${a.criticalIssuesTotal > a.criticalIssues.length
|
|
607
|
+
? ` <span style="font-weight:400;color:var(--text-muted);font-size:13px">(showing ${a.criticalIssues.length} of ${a.criticalIssuesTotal} — see report tabs for full lists)</span>`
|
|
608
|
+
: ''}</div>
|
|
609
|
+
<div class="crit-list">
|
|
610
|
+
${a.criticalIssues
|
|
611
|
+
.map((i) => `
|
|
612
|
+
<div class="crit-item">
|
|
613
|
+
<div class="crit-sev" style="background:${sevColor(i.severity)}"></div>
|
|
614
|
+
<div class="crit-text">
|
|
615
|
+
<div class="label">${escapeHtml(i.label)}</div>
|
|
616
|
+
${i.detail ? `<div class="detail">${escapeHtml(i.detail)}</div>` : ''}
|
|
617
|
+
</div>
|
|
618
|
+
</div>`)
|
|
619
|
+
.join('')}
|
|
620
|
+
</div>`
|
|
621
|
+
: ''}
|
|
622
|
+
|
|
623
|
+
<p style="margin-top:32px;color:var(--text-muted);font-size:13px">Click any report in the sidebar to see the full breakdown.</p>
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
</main>
|
|
627
|
+
|
|
628
|
+
<script id="reports-data" type="application/json">${reportsJson.replace(/</g, '\\u003c')}</script>
|
|
629
|
+
<script id="nav-data" type="application/json">${navJson.replace(/</g, '\\u003c')}</script>
|
|
630
|
+
<script>
|
|
631
|
+
const reports = JSON.parse(document.getElementById('reports-data').textContent);
|
|
632
|
+
const navEntries = JSON.parse(document.getElementById('nav-data').textContent);
|
|
633
|
+
const nav = document.getElementById('nav');
|
|
634
|
+
const content = document.getElementById('content');
|
|
635
|
+
const overviewHtml = document.getElementById('overview-content').outerHTML;
|
|
636
|
+
|
|
637
|
+
navEntries.forEach((cfg) => {
|
|
638
|
+
const btn = document.createElement('button');
|
|
639
|
+
btn.className = 'nav-item';
|
|
640
|
+
btn.dataset.target = cfg.reportKey;
|
|
641
|
+
btn.innerHTML = '<span class="icon">' + cfg.icon + '</span><span class="label">' + cfg.label + '</span>' + (cfg.badge ? '<span class="badge">' + cfg.badge + '</span>' : '');
|
|
642
|
+
nav.appendChild(btn);
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
function activate(target) {
|
|
646
|
+
document.querySelectorAll('.nav-item').forEach((b) => b.classList.remove('active'));
|
|
647
|
+
const btn = document.querySelector('[data-target="' + target + '"]');
|
|
648
|
+
if (btn) btn.classList.add('active');
|
|
649
|
+
if (target === 'overview') {
|
|
650
|
+
content.innerHTML = overviewHtml;
|
|
651
|
+
} else if (reports[target]) {
|
|
652
|
+
const md = typeof marked !== 'undefined' ? marked.parse(reports[target]) : '<pre>' + reports[target].replace(/</g, '<') + '</pre>';
|
|
653
|
+
content.innerHTML = '<div class="main-inner" style="animation:fadeIn 0.2s ease">' + md + '</div>';
|
|
654
|
+
}
|
|
655
|
+
document.querySelector('.sidebar').classList.remove('open');
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
document.querySelectorAll('.nav-item').forEach((btn) => {
|
|
659
|
+
btn.addEventListener('click', () => activate(btn.dataset.target));
|
|
660
|
+
});
|
|
661
|
+
</script>
|
|
662
|
+
</body>
|
|
663
|
+
</html>
|
|
664
|
+
`;
|
|
665
|
+
}
|
|
666
|
+
//# sourceMappingURL=index.js.map
|