@vyuhlabs/dxkit 2.4.6 → 2.4.8
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 +1076 -0
- package/README.md +132 -27
- 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 +667 -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 +21 -9
- package/dist/analyzers/developer/index.js.map +1 -1
- package/dist/analyzers/dispatcher.d.ts +52 -0
- package/dist/analyzers/dispatcher.d.ts.map +1 -1
- package/dist/analyzers/dispatcher.js +92 -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 +282 -34
- 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 +86 -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 +197 -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 +349 -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 +104 -1
- package/dist/analyzers/security/gather.d.ts.map +1 -1
- package/dist/analyzers/security/gather.js +299 -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/deadline.d.ts +67 -0
- package/dist/analyzers/tools/deadline.d.ts.map +1 -0
- package/dist/analyzers/tools/deadline.js +81 -0
- package/dist/analyzers/tools/deadline.js.map +1 -0
- 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/lint-label.d.ts +29 -0
- package/dist/analyzers/tools/lint-label.d.ts.map +1 -0
- package/dist/analyzers/tools/lint-label.js +23 -0
- package/dist/analyzers/tools/lint-label.js.map +1 -0
- 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 +133 -0
- package/dist/analyzers/tools/nuget-package-reference.d.ts.map +1 -0
- package/dist/analyzers/tools/nuget-package-reference.js +177 -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/report-date.d.ts +17 -0
- package/dist/analyzers/tools/report-date.d.ts.map +1 -0
- package/dist/analyzers/tools/report-date.js +26 -0
- package/dist/analyzers/tools/report-date.js.map +1 -0
- 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 +581 -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 +132 -2
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +207 -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
package/dist/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.run = run;
|
|
37
37
|
const node_util_1 = require("node:util");
|
|
38
|
+
const vendored_advisor_1 = require("./analyzers/tools/vendored-advisor");
|
|
38
39
|
const detect_1 = require("./detect");
|
|
39
40
|
const generator_1 = require("./generator");
|
|
40
41
|
const prompts_1 = require("./prompts");
|
|
@@ -43,8 +44,22 @@ const update_1 = require("./update");
|
|
|
43
44
|
const doctor_1 = require("./doctor");
|
|
44
45
|
const constants_1 = require("./constants");
|
|
45
46
|
const logger = __importStar(require("./logger"));
|
|
47
|
+
const scoring_1 = require("./scoring");
|
|
48
|
+
const tools_unavailable_prose_1 = require("./analyzers/tools/tools-unavailable-prose");
|
|
49
|
+
const report_date_1 = require("./analyzers/tools/report-date");
|
|
46
50
|
const fs = __importStar(require("fs"));
|
|
47
51
|
const path = __importStar(require("path"));
|
|
52
|
+
// process.stdout.write returns false when the OS pipe buffer is full
|
|
53
|
+
// (typically 64KB on Linux). Without awaiting 'drain', the process exits
|
|
54
|
+
// and the tail of large payloads is silently lost on POSIX — manifests as
|
|
55
|
+
// 0-byte files when piping `--json` output through `cat > file` or similar.
|
|
56
|
+
// Tracked as D017.
|
|
57
|
+
async function emitJson(payload) {
|
|
58
|
+
const data = JSON.stringify(payload, null, 2) + '\n';
|
|
59
|
+
if (!process.stdout.write(data)) {
|
|
60
|
+
await new Promise((resolve) => process.stdout.once('drain', resolve));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
48
63
|
function printUsage() {
|
|
49
64
|
console.log(`
|
|
50
65
|
${logger.bold('vyuh-dxkit')} v${constants_1.VERSION} — AI-native developer experience toolkit
|
|
@@ -60,6 +75,9 @@ function printUsage() {
|
|
|
60
75
|
vyuh-dxkit dev-report [path] Developer activity analysis
|
|
61
76
|
vyuh-dxkit licenses [path] Dependency license inventory
|
|
62
77
|
vyuh-dxkit bom [path] Bill of Materials (licenses + vulnerabilities joined)
|
|
78
|
+
vyuh-dxkit coverage [path] Run per-pack test-with-coverage (side-effecting; materializes the coverage artifact health/test-gaps read)
|
|
79
|
+
vyuh-dxkit dashboard [path] Render .dxkit/reports/ into a single HTML dashboard
|
|
80
|
+
vyuh-dxkit report [path] Run every analyzer + dashboard in one shot (full audit)
|
|
63
81
|
vyuh-dxkit to-xlsx <json> Convert a dxkit JSON report to 15-col XLSX
|
|
64
82
|
vyuh-dxkit tools [path] Show required analysis tools status
|
|
65
83
|
vyuh-dxkit tools install Interactively install missing tools
|
|
@@ -79,16 +97,16 @@ function printUsage() {
|
|
|
79
97
|
--rescan Re-run codebase analysis
|
|
80
98
|
|
|
81
99
|
${logger.bold('Analyzer options (health, vulnerabilities, test-gaps, quality, dev-report, licenses, bom):')}
|
|
82
|
-
--json
|
|
83
|
-
--verbose
|
|
84
|
-
--no-save
|
|
85
|
-
--detailed
|
|
86
|
-
--xlsx
|
|
87
|
-
--since
|
|
88
|
-
--filter
|
|
89
|
-
|
|
90
|
-
--
|
|
91
|
-
|
|
100
|
+
--json Print report as JSON to stdout
|
|
101
|
+
--verbose Print per-tool timing to stderr
|
|
102
|
+
--no-save Skip writing the markdown report file
|
|
103
|
+
--detailed Also write <name>-detailed.md + .json with evidence + ranked actions
|
|
104
|
+
--xlsx Licenses/bom: also write 15-col BOM XLSX
|
|
105
|
+
--since Dev-report: start date (YYYY-MM-DD)
|
|
106
|
+
--filter Bom: 'all' (default) or 'top-level' (keeps only root manifest deps;
|
|
107
|
+
advisory rollup under byTopLevelDep still reflects transitives)
|
|
108
|
+
--with-coverage Health/test-gaps: materialize coverage artifacts via per-pack
|
|
109
|
+
runTests() before analysis (line-coverage truth vs filename match)
|
|
92
110
|
|
|
93
111
|
${logger.bold('Examples:')}
|
|
94
112
|
npx vyuh-dxkit init # Interactive
|
|
@@ -121,8 +139,14 @@ async function run(argv) {
|
|
|
121
139
|
output: { type: 'string', short: 'o' },
|
|
122
140
|
xlsx: { type: 'boolean', default: false },
|
|
123
141
|
filter: { type: 'string' },
|
|
124
|
-
'no-nested': { type: 'boolean', default: false },
|
|
125
142
|
all: { type: 'boolean', default: false },
|
|
143
|
+
'reports-dir': { type: 'string' },
|
|
144
|
+
'json-dir': { type: 'string' },
|
|
145
|
+
'project-name': { type: 'string' },
|
|
146
|
+
lang: { type: 'string' },
|
|
147
|
+
timeout: { type: 'string' },
|
|
148
|
+
'no-fail-fast': { type: 'boolean', default: false },
|
|
149
|
+
'with-coverage': { type: 'boolean', default: false },
|
|
126
150
|
},
|
|
127
151
|
allowPositionals: true,
|
|
128
152
|
strict: false,
|
|
@@ -242,27 +266,57 @@ async function run(argv) {
|
|
|
242
266
|
}
|
|
243
267
|
case 'health': {
|
|
244
268
|
const targetPath = resolveRepoPath(positionals[1]);
|
|
245
|
-
const {
|
|
269
|
+
const { analyzeHealthWithMetrics } = await Promise.resolve().then(() => __importStar(require('./analyzers/health')));
|
|
246
270
|
logger.header('vyuh-dxkit health');
|
|
247
271
|
logger.info(`Analyzing ${targetPath}...`);
|
|
248
272
|
const startTime = Date.now();
|
|
273
|
+
// D021 (2.4.7): --with-coverage materializes the coverage artifact
|
|
274
|
+
// BEFORE the analyzer runs, so the report reads line-coverage
|
|
275
|
+
// truth (`coverageFidelity: 'line-coverage'`) instead of falling
|
|
276
|
+
// back to the filename-match heuristic. Shares the same per-pack
|
|
277
|
+
// runner the `coverage` command uses; honors --lang to limit
|
|
278
|
+
// scope on polyglot repos.
|
|
279
|
+
if (values['with-coverage']) {
|
|
280
|
+
const { runCoverageAcrossPacks } = await Promise.resolve().then(() => __importStar(require('./analyzers/coverage-runner')));
|
|
281
|
+
const langFilter = values.lang;
|
|
282
|
+
logger.info('Running test-with-coverage across active packs...');
|
|
283
|
+
const { rows } = await runCoverageAcrossPacks(targetPath, {
|
|
284
|
+
langFilter,
|
|
285
|
+
failFast: !values['no-fail-fast'],
|
|
286
|
+
onPackStart: (id) => process.stderr.write(` → ${id}: running tests with coverage...\n`),
|
|
287
|
+
});
|
|
288
|
+
const successes = rows.filter((r) => r.status === 'success').length;
|
|
289
|
+
if (successes > 0) {
|
|
290
|
+
logger.success(`${successes}/${rows.length} packs produced coverage artifacts`);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
logger.warn(`0/${rows.length} packs produced coverage artifacts — falling back to heuristic`);
|
|
294
|
+
}
|
|
295
|
+
console.log(''); // slop-ok
|
|
296
|
+
}
|
|
249
297
|
// Detailed mode needs HealthMetrics for remediation planning; pull both.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
298
|
+
// D032 (2.4.7): always gather the underlying metrics so the
|
|
299
|
+
// `-detailed.json` write below has the data it needs. Pre-fix
|
|
300
|
+
// the metrics-bearing path was gated on `--detailed`, so the
|
|
301
|
+
// dashboard's input JSON was only produced when the user opted
|
|
302
|
+
// into detailed reporting — making the dashboard headline numbers
|
|
303
|
+
// silently stale or zero on a default `dxkit health . && dxkit
|
|
304
|
+
// dashboard .` workflow. Both internal entry points share
|
|
305
|
+
// `analyzeHealthInternal`, so the only cost is keeping a metrics
|
|
306
|
+
// reference live (no extra compute).
|
|
307
|
+
const healthResult = await analyzeHealthWithMetrics(targetPath, {
|
|
308
|
+
verbose: !!values.verbose,
|
|
309
|
+
});
|
|
256
310
|
const report = healthResult.report;
|
|
257
311
|
const healthMetrics = healthResult.metrics;
|
|
258
312
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
259
313
|
if (values.json) {
|
|
260
|
-
|
|
314
|
+
await emitJson(report);
|
|
261
315
|
}
|
|
262
316
|
else {
|
|
263
317
|
// Console output
|
|
264
318
|
console.log('');
|
|
265
|
-
console.log(` ${logger.bold('Overall:')} ${report.summary.overallScore}/100 (
|
|
319
|
+
console.log(` ${logger.bold('Overall:')} ${report.summary.overallScore}/100 (Rating: ${report.summary.rating})`);
|
|
266
320
|
console.log('');
|
|
267
321
|
const dims = report.dimensions;
|
|
268
322
|
const order = [
|
|
@@ -275,7 +329,11 @@ async function run(argv) {
|
|
|
275
329
|
];
|
|
276
330
|
for (const [name, dim] of order) {
|
|
277
331
|
const bar = '█'.repeat(Math.round(dim.score / 5)) + '░'.repeat(20 - Math.round(dim.score / 5));
|
|
278
|
-
console.log(` ${name.padEnd(22)} ${bar} ${dim.score.toString().padStart(3)}/100 ${dim.
|
|
332
|
+
console.log(` ${name.padEnd(22)} ${bar} ${dim.score.toString().padStart(3)}/100 ${dim.rating}`);
|
|
333
|
+
const topAction = (0, scoring_1.formatTopActionLine)(dim);
|
|
334
|
+
if (topAction) {
|
|
335
|
+
logger.dim(` ${' '.repeat(22)} → ${topAction}`);
|
|
336
|
+
}
|
|
279
337
|
}
|
|
280
338
|
console.log('');
|
|
281
339
|
logger.dim('Tools: ' + report.toolsUsed.join(', '));
|
|
@@ -283,27 +341,42 @@ async function run(argv) {
|
|
|
283
341
|
logger.dim('Unavailable: ' + report.toolsUnavailable.join(', '));
|
|
284
342
|
}
|
|
285
343
|
logger.dim(`Completed in ${elapsed}s`);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
344
|
+
}
|
|
345
|
+
// Disk side: orthogonal to --json so consumers don't need separate
|
|
346
|
+
// `--detailed` and `--detailed --json` invocations (closes D018).
|
|
347
|
+
// `logger.success` routes to stderr in --json mode, so it's safe to
|
|
348
|
+
// call unconditionally.
|
|
349
|
+
if (!values['no-save']) {
|
|
350
|
+
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
351
|
+
const date = (0, report_date_1.getReportDate)();
|
|
352
|
+
const reportPath = path.join(reportDir, `health-audit-${date}.md`);
|
|
353
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
354
|
+
fs.writeFileSync(reportPath, formatMarkdownReport(report, elapsed));
|
|
355
|
+
if (!values.json)
|
|
356
|
+
console.log(''); // slop-ok
|
|
357
|
+
logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
|
|
358
|
+
// D032 (2.4.7): always write BOTH `-detailed.json` AND
|
|
359
|
+
// `-detailed.md` so `vyuh-dxkit dashboard` finds fresh inputs
|
|
360
|
+
// on every run. The dashboard reads JSON for tile metrics and
|
|
361
|
+
// embeds the markdown for tab content (Language Breakdown +
|
|
362
|
+
// Plans live only in the detailed.md). Pre-fix gating these
|
|
363
|
+
// on `--detailed` meant a default `health → dashboard` workflow
|
|
364
|
+
// showed stale tile values + stale tab content from whichever
|
|
365
|
+
// run last passed `--detailed`. The `--detailed` flag now only
|
|
366
|
+
// controls the console success-log lines.
|
|
367
|
+
const { buildHealthDetailed, formatHealthDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/health/detailed')));
|
|
368
|
+
const detailed = buildHealthDetailed(report, healthMetrics);
|
|
369
|
+
const detailedJsonPath = path.join(reportDir, `health-audit-${date}-detailed.json`);
|
|
370
|
+
const detailedMdPath = path.join(reportDir, `health-audit-${date}-detailed.md`);
|
|
371
|
+
fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
|
|
372
|
+
fs.writeFileSync(detailedMdPath, formatHealthDetailedMarkdown(detailed, elapsed));
|
|
373
|
+
if (values.detailed) {
|
|
374
|
+
logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
|
|
375
|
+
logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
|
|
305
376
|
}
|
|
306
|
-
|
|
377
|
+
}
|
|
378
|
+
if (!values.json) {
|
|
379
|
+
// Hint about missing tools (exclude project-side config errors).
|
|
307
380
|
const PROJECT_ISSUES = ['config error', 'legacy .eslintrc', 'no eslint config'];
|
|
308
381
|
const trulyMissing = report.toolsUnavailable.filter((t) => !PROJECT_ISSUES.some((p) => t.includes(p)));
|
|
309
382
|
if (trulyMissing.length > 0) {
|
|
@@ -363,7 +436,7 @@ async function run(argv) {
|
|
|
363
436
|
const report = await analyzeSecurity(targetPath, { verbose: !!values.verbose });
|
|
364
437
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
365
438
|
if (values.json) {
|
|
366
|
-
|
|
439
|
+
await emitJson(report);
|
|
367
440
|
}
|
|
368
441
|
else {
|
|
369
442
|
const s = report.summary.findings;
|
|
@@ -381,24 +454,27 @@ async function run(argv) {
|
|
|
381
454
|
logger.dim('Unavailable: ' + report.toolsUnavailable.join(', '));
|
|
382
455
|
}
|
|
383
456
|
logger.dim(`Completed in ${elapsed}s`);
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
457
|
+
}
|
|
458
|
+
// Disk side: orthogonal to --json (closes D018).
|
|
459
|
+
if (!values['no-save']) {
|
|
460
|
+
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
461
|
+
const date = (0, report_date_1.getReportDate)();
|
|
462
|
+
const reportPath = path.join(reportDir, `vulnerability-scan-${date}.md`);
|
|
463
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
464
|
+
fs.writeFileSync(reportPath, formatSecurityReport(report, elapsed));
|
|
465
|
+
if (!values.json)
|
|
466
|
+
console.log(''); // slop-ok
|
|
467
|
+
logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
|
|
468
|
+
// D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
|
|
469
|
+
const { buildSecurityDetailed, formatSecurityDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/security/detailed')));
|
|
470
|
+
const securityDetailed = buildSecurityDetailed(report);
|
|
471
|
+
const securityDetailedJsonPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.json`);
|
|
472
|
+
const securityDetailedMdPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.md`);
|
|
473
|
+
fs.writeFileSync(securityDetailedJsonPath, JSON.stringify(securityDetailed, null, 2));
|
|
474
|
+
fs.writeFileSync(securityDetailedMdPath, formatSecurityDetailedMarkdown(securityDetailed, elapsed));
|
|
475
|
+
if (values.detailed) {
|
|
476
|
+
logger.success(`Detailed report saved to ${path.relative(targetPath, securityDetailedMdPath)}`);
|
|
477
|
+
logger.success(`Detailed JSON saved to ${path.relative(targetPath, securityDetailedJsonPath)}`);
|
|
402
478
|
}
|
|
403
479
|
}
|
|
404
480
|
break;
|
|
@@ -409,10 +485,32 @@ async function run(argv) {
|
|
|
409
485
|
logger.header('vyuh-dxkit test-gaps');
|
|
410
486
|
logger.info(`Analyzing ${targetPath}...`);
|
|
411
487
|
const startTime = Date.now();
|
|
488
|
+
// D021 (2.4.7): --with-coverage materializes the coverage artifact
|
|
489
|
+
// before analysis so the test-gaps report reads line-coverage
|
|
490
|
+
// truth instead of falling back to filename-match. Same runner
|
|
491
|
+
// health --with-coverage uses.
|
|
492
|
+
if (values['with-coverage']) {
|
|
493
|
+
const { runCoverageAcrossPacks } = await Promise.resolve().then(() => __importStar(require('./analyzers/coverage-runner')));
|
|
494
|
+
const langFilter = values.lang;
|
|
495
|
+
logger.info('Running test-with-coverage across active packs...');
|
|
496
|
+
const { rows } = await runCoverageAcrossPacks(targetPath, {
|
|
497
|
+
langFilter,
|
|
498
|
+
failFast: !values['no-fail-fast'],
|
|
499
|
+
onPackStart: (id) => process.stderr.write(` → ${id}: running tests with coverage...\n`),
|
|
500
|
+
});
|
|
501
|
+
const successes = rows.filter((r) => r.status === 'success').length;
|
|
502
|
+
if (successes > 0) {
|
|
503
|
+
logger.success(`${successes}/${rows.length} packs produced coverage artifacts`);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
logger.warn(`0/${rows.length} packs produced coverage artifacts — falling back to heuristic`);
|
|
507
|
+
}
|
|
508
|
+
console.log(''); // slop-ok
|
|
509
|
+
}
|
|
412
510
|
const report = await analyzeTestGaps(targetPath, { verbose: !!values.verbose });
|
|
413
511
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
414
512
|
if (values.json) {
|
|
415
|
-
|
|
513
|
+
await emitJson(report);
|
|
416
514
|
}
|
|
417
515
|
else {
|
|
418
516
|
const s = report.summary;
|
|
@@ -426,24 +524,27 @@ async function run(argv) {
|
|
|
426
524
|
console.log('');
|
|
427
525
|
logger.dim('Tools: ' + report.toolsUsed.join(', '));
|
|
428
526
|
logger.dim(`Completed in ${elapsed}s`);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
527
|
+
}
|
|
528
|
+
// Disk side: orthogonal to --json (closes D018).
|
|
529
|
+
if (!values['no-save']) {
|
|
530
|
+
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
531
|
+
const date = (0, report_date_1.getReportDate)();
|
|
532
|
+
const reportPath = path.join(reportDir, `test-gaps-${date}.md`);
|
|
533
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
534
|
+
fs.writeFileSync(reportPath, formatTestGapsReport(report, elapsed));
|
|
535
|
+
if (!values.json)
|
|
536
|
+
console.log(''); // slop-ok
|
|
537
|
+
logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
|
|
538
|
+
// D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
|
|
539
|
+
const { buildTestGapsDetailed, formatTestGapsDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/tests/detailed')));
|
|
540
|
+
const testGapsDetailed = buildTestGapsDetailed(report);
|
|
541
|
+
const testGapsDetailedJsonPath = path.join(reportDir, `test-gaps-${date}-detailed.json`);
|
|
542
|
+
const testGapsDetailedMdPath = path.join(reportDir, `test-gaps-${date}-detailed.md`);
|
|
543
|
+
fs.writeFileSync(testGapsDetailedJsonPath, JSON.stringify(testGapsDetailed, null, 2));
|
|
544
|
+
fs.writeFileSync(testGapsDetailedMdPath, formatTestGapsDetailedMarkdown(testGapsDetailed, elapsed));
|
|
545
|
+
if (values.detailed) {
|
|
546
|
+
logger.success(`Detailed report saved to ${path.relative(targetPath, testGapsDetailedMdPath)}`);
|
|
547
|
+
logger.success(`Detailed JSON saved to ${path.relative(targetPath, testGapsDetailedJsonPath)}`);
|
|
447
548
|
}
|
|
448
549
|
}
|
|
449
550
|
break;
|
|
@@ -460,7 +561,7 @@ async function run(argv) {
|
|
|
460
561
|
});
|
|
461
562
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
462
563
|
if (values.json) {
|
|
463
|
-
|
|
564
|
+
await emitJson(report);
|
|
464
565
|
}
|
|
465
566
|
else {
|
|
466
567
|
const m = report.metrics;
|
|
@@ -495,24 +596,27 @@ async function run(argv) {
|
|
|
495
596
|
logger.dim('Unavailable: ' + report.toolsUnavailable.join(', '));
|
|
496
597
|
}
|
|
497
598
|
logger.dim(`Completed in ${elapsed}s`);
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
599
|
+
}
|
|
600
|
+
// Disk side: orthogonal to --json (closes D018).
|
|
601
|
+
if (!values['no-save']) {
|
|
602
|
+
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
603
|
+
const date = (0, report_date_1.getReportDate)();
|
|
604
|
+
const reportPath = path.join(reportDir, `quality-review-${date}.md`);
|
|
605
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
606
|
+
fs.writeFileSync(reportPath, formatQualityReport(report, elapsed));
|
|
607
|
+
if (!values.json)
|
|
608
|
+
console.log(''); // slop-ok
|
|
609
|
+
logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
|
|
610
|
+
// D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
|
|
611
|
+
const { buildQualityDetailed, formatQualityDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/quality/detailed')));
|
|
612
|
+
const qualityDetailed = buildQualityDetailed(report);
|
|
613
|
+
const qualityDetailedJsonPath = path.join(reportDir, `quality-review-${date}-detailed.json`);
|
|
614
|
+
const qualityDetailedMdPath = path.join(reportDir, `quality-review-${date}-detailed.md`);
|
|
615
|
+
fs.writeFileSync(qualityDetailedJsonPath, JSON.stringify(qualityDetailed, null, 2));
|
|
616
|
+
fs.writeFileSync(qualityDetailedMdPath, formatQualityDetailedMarkdown(qualityDetailed, elapsed));
|
|
617
|
+
if (values.detailed) {
|
|
618
|
+
logger.success(`Detailed report saved to ${path.relative(targetPath, qualityDetailedMdPath)}`);
|
|
619
|
+
logger.success(`Detailed JSON saved to ${path.relative(targetPath, qualityDetailedJsonPath)}`);
|
|
516
620
|
}
|
|
517
621
|
}
|
|
518
622
|
break;
|
|
@@ -524,10 +628,12 @@ async function run(argv) {
|
|
|
524
628
|
logger.header('vyuh-dxkit dev-report');
|
|
525
629
|
logger.info(`Analyzing ${targetPath}...`);
|
|
526
630
|
const startTime = Date.now();
|
|
527
|
-
const report = analyzeDevActivity(targetPath, sinceFlag, {
|
|
631
|
+
const report = await analyzeDevActivity(targetPath, sinceFlag, {
|
|
632
|
+
verbose: !!values.verbose,
|
|
633
|
+
});
|
|
528
634
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
529
635
|
if (values.json) {
|
|
530
|
-
|
|
636
|
+
await emitJson(report);
|
|
531
637
|
}
|
|
532
638
|
else {
|
|
533
639
|
const s = report.summary;
|
|
@@ -547,28 +653,30 @@ async function run(argv) {
|
|
|
547
653
|
console.log('');
|
|
548
654
|
logger.dim('Tools: ' + report.toolsUsed.join(', '));
|
|
549
655
|
logger.dim(`Completed in ${elapsed}s`);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
656
|
+
}
|
|
657
|
+
// Disk side: orthogonal to --json (closes D018).
|
|
658
|
+
if (!values['no-save']) {
|
|
659
|
+
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
660
|
+
const date = (0, report_date_1.getReportDate)();
|
|
661
|
+
const reportPath = path.join(reportDir, `developer-report-${date}.md`);
|
|
662
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
663
|
+
fs.writeFileSync(reportPath, formatDevReport(report, elapsed));
|
|
664
|
+
if (!values.json)
|
|
665
|
+
console.log(''); // slop-ok
|
|
666
|
+
logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
|
|
667
|
+
// D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
|
|
668
|
+
const { buildDevDetailed, formatDevDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/developer/detailed')));
|
|
669
|
+
const { gatherVagueCommitExamples } = await Promise.resolve().then(() => __importStar(require('./analyzers/developer/gather')));
|
|
670
|
+
const sinceDate = sinceFlag || new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
|
671
|
+
const vague = gatherVagueCommitExamples(targetPath, sinceDate);
|
|
672
|
+
const devDetailed = buildDevDetailed(report, vague);
|
|
673
|
+
const devDetailedJsonPath = path.join(reportDir, `developer-report-${date}-detailed.json`);
|
|
674
|
+
const devDetailedMdPath = path.join(reportDir, `developer-report-${date}-detailed.md`);
|
|
675
|
+
fs.writeFileSync(devDetailedJsonPath, JSON.stringify(devDetailed, null, 2));
|
|
676
|
+
fs.writeFileSync(devDetailedMdPath, formatDevDetailedMarkdown(devDetailed, elapsed));
|
|
677
|
+
if (values.detailed) {
|
|
678
|
+
logger.success(`Detailed report saved to ${path.relative(targetPath, devDetailedMdPath)}`);
|
|
679
|
+
logger.success(`Detailed JSON saved to ${path.relative(targetPath, devDetailedJsonPath)}`);
|
|
572
680
|
}
|
|
573
681
|
}
|
|
574
682
|
break;
|
|
@@ -582,7 +690,7 @@ async function run(argv) {
|
|
|
582
690
|
const report = await analyzeLicenses(targetPath, { verbose: !!values.verbose });
|
|
583
691
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
584
692
|
if (values.json) {
|
|
585
|
-
|
|
693
|
+
await emitJson(report); // slop-ok
|
|
586
694
|
}
|
|
587
695
|
else {
|
|
588
696
|
const s = report.summary;
|
|
@@ -606,35 +714,36 @@ async function run(argv) {
|
|
|
606
714
|
console.log(''); // slop-ok
|
|
607
715
|
logger.dim('Tools: ' + (report.toolsUsed.join(', ') || '(none)'));
|
|
608
716
|
logger.dim(`Completed in ${elapsed}s`);
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
717
|
+
}
|
|
718
|
+
// Disk side: orthogonal to --json (closes D018).
|
|
719
|
+
if (!values['no-save']) {
|
|
720
|
+
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
721
|
+
const date = (0, report_date_1.getReportDate)();
|
|
722
|
+
const reportPath = path.join(reportDir, `licenses-${date}.md`);
|
|
723
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
724
|
+
fs.writeFileSync(reportPath, formatLicensesReport(report, elapsed));
|
|
725
|
+
if (!values.json)
|
|
615
726
|
console.log(''); // slop-ok
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
}
|
|
727
|
+
logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
|
|
728
|
+
// D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
|
|
729
|
+
const { buildLicensesDetailed, formatLicensesDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/licenses/detailed')));
|
|
730
|
+
const licensesDetailed = buildLicensesDetailed(report);
|
|
731
|
+
const licensesDetailedJsonPath = path.join(reportDir, `licenses-${date}-detailed.json`);
|
|
732
|
+
const licensesDetailedMdPath = path.join(reportDir, `licenses-${date}-detailed.md`);
|
|
733
|
+
fs.writeFileSync(licensesDetailedJsonPath, JSON.stringify(licensesDetailed, null, 2));
|
|
734
|
+
fs.writeFileSync(licensesDetailedMdPath, formatLicensesDetailedMarkdown(licensesDetailed, elapsed));
|
|
735
|
+
if (values.detailed) {
|
|
736
|
+
logger.success(`Detailed report saved to ${path.relative(targetPath, licensesDetailedMdPath)}`);
|
|
737
|
+
logger.success(`Detailed JSON saved to ${path.relative(targetPath, licensesDetailedJsonPath)}`);
|
|
738
|
+
}
|
|
739
|
+
if (values.xlsx) {
|
|
740
|
+
const { toLicensesXlsx } = await Promise.resolve().then(() => __importStar(require('./analyzers/xlsx')));
|
|
741
|
+
const xlsxPath = values.output
|
|
742
|
+
? path.resolve(values.output)
|
|
743
|
+
: path.join(reportDir, `licenses-${date}.xlsx`);
|
|
744
|
+
const buf = await toLicensesXlsx(licensesDetailed);
|
|
745
|
+
fs.writeFileSync(xlsxPath, buf);
|
|
746
|
+
logger.success(`XLSX saved to ${path.relative(targetPath, xlsxPath)}`);
|
|
638
747
|
}
|
|
639
748
|
}
|
|
640
749
|
break;
|
|
@@ -650,16 +759,14 @@ async function run(argv) {
|
|
|
650
759
|
process.exit(1);
|
|
651
760
|
}
|
|
652
761
|
const filter = rawFilter;
|
|
653
|
-
const nested = !values['no-nested'];
|
|
654
762
|
const startTime = Date.now();
|
|
655
763
|
const report = await analyzeBom(targetPath, {
|
|
656
764
|
verbose: !!values.verbose,
|
|
657
765
|
filter,
|
|
658
|
-
nested,
|
|
659
766
|
});
|
|
660
767
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
661
768
|
if (values.json) {
|
|
662
|
-
|
|
769
|
+
await emitJson(report); // slop-ok
|
|
663
770
|
}
|
|
664
771
|
else {
|
|
665
772
|
const s = report.summary;
|
|
@@ -696,36 +803,283 @@ async function run(argv) {
|
|
|
696
803
|
console.log(''); // slop-ok
|
|
697
804
|
logger.dim('Tools: ' + (report.toolsUsed.join(', ') || '(none)'));
|
|
698
805
|
logger.dim(`Completed in ${elapsed}s`);
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
806
|
+
}
|
|
807
|
+
// Disk side: orthogonal to --json (closes D018).
|
|
808
|
+
if (!values['no-save']) {
|
|
809
|
+
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
810
|
+
const date = (0, report_date_1.getReportDate)();
|
|
811
|
+
const reportPath = path.join(reportDir, `bom-${date}.md`);
|
|
812
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
813
|
+
fs.writeFileSync(reportPath, formatBomReport(report, elapsed));
|
|
814
|
+
if (!values.json)
|
|
705
815
|
console.log(''); // slop-ok
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
816
|
+
logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
|
|
817
|
+
// D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
|
|
818
|
+
const { buildBomDetailed, formatBomDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/bom/detailed')));
|
|
819
|
+
const bomDetailed = buildBomDetailed(report);
|
|
820
|
+
const bomDetailedJsonPath = path.join(reportDir, `bom-${date}-detailed.json`);
|
|
821
|
+
const bomDetailedMdPath = path.join(reportDir, `bom-${date}-detailed.md`);
|
|
822
|
+
fs.writeFileSync(bomDetailedJsonPath, JSON.stringify(bomDetailed, null, 2));
|
|
823
|
+
fs.writeFileSync(bomDetailedMdPath, formatBomDetailedMarkdown(bomDetailed, elapsed));
|
|
824
|
+
if (values.detailed) {
|
|
825
|
+
logger.success(`Detailed report saved to ${path.relative(targetPath, bomDetailedMdPath)}`);
|
|
826
|
+
logger.success(`Detailed JSON saved to ${path.relative(targetPath, bomDetailedJsonPath)}`);
|
|
827
|
+
}
|
|
828
|
+
if (values.xlsx) {
|
|
829
|
+
const { toBomXlsx } = await Promise.resolve().then(() => __importStar(require('./analyzers/xlsx')));
|
|
830
|
+
const xlsxPath = values.output
|
|
831
|
+
? path.resolve(values.output)
|
|
832
|
+
: path.join(reportDir, `bom-${date}.xlsx`);
|
|
833
|
+
const buf = await toBomXlsx(report);
|
|
834
|
+
fs.writeFileSync(xlsxPath, buf);
|
|
835
|
+
logger.success(`XLSX saved to ${path.relative(targetPath, xlsxPath)}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
case 'dashboard': {
|
|
841
|
+
const targetPath = resolveRepoPath(positionals[1]);
|
|
842
|
+
const { analyzeDashboard } = await Promise.resolve().then(() => __importStar(require('./analyzers/dashboard')));
|
|
843
|
+
logger.header('vyuh-dxkit dashboard');
|
|
844
|
+
const reportsDir = values['reports-dir']
|
|
845
|
+
? path.resolve(values['reports-dir'])
|
|
846
|
+
: path.join(targetPath, '.dxkit', 'reports');
|
|
847
|
+
const jsonDir = values['json-dir'] ? path.resolve(values['json-dir']) : undefined;
|
|
848
|
+
const projectName = values['project-name'] ?? undefined;
|
|
849
|
+
const outputPath = values.output
|
|
850
|
+
? path.resolve(values.output)
|
|
851
|
+
: path.join(reportsDir, 'dashboard.html');
|
|
852
|
+
let result;
|
|
853
|
+
try {
|
|
854
|
+
result = analyzeDashboard(targetPath, { reportsDir, jsonDir, projectName });
|
|
855
|
+
}
|
|
856
|
+
catch (err) {
|
|
857
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
858
|
+
logger.fail(msg);
|
|
859
|
+
process.exit(1);
|
|
860
|
+
}
|
|
861
|
+
if (result.reportCount === 0) {
|
|
862
|
+
logger.fail(`No report markdowns found in ${path.relative(targetPath, reportsDir) || reportsDir}.\n` +
|
|
863
|
+
`Run 'vyuh-dxkit health .' (or any other report command) first to populate the directory.`);
|
|
864
|
+
process.exit(1);
|
|
865
|
+
}
|
|
866
|
+
if (values['no-save']) {
|
|
867
|
+
// Drain-aware HTML emission to stdout. Mirrors emitJson() for
|
|
868
|
+
// payloads that can exceed the 64KB pipe buffer (a dashboard
|
|
869
|
+
// with all reports embedded routinely runs 300-500KB).
|
|
870
|
+
if (!process.stdout.write(result.html)) {
|
|
871
|
+
await new Promise((resolve) => process.stdout.once('drain', resolve));
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
876
|
+
fs.writeFileSync(outputPath, result.html);
|
|
877
|
+
logger.success(`Dashboard written to ${path.relative(targetPath, outputPath) || outputPath}`);
|
|
878
|
+
logger.dim(`${result.reportCount} reports · ${result.summary.healthScore !== null ? `health ${result.summary.healthScore}/100` : 'no health data'} · ` +
|
|
879
|
+
`${result.summary.vulnCount} vulns · ${result.summary.gapCount} test gaps · ` +
|
|
880
|
+
`${result.summary.advisoryCount} BoM advisories · ${result.criticalIssueCount} critical-issue tiles`);
|
|
881
|
+
}
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
884
|
+
case 'coverage': {
|
|
885
|
+
const targetPath = resolveRepoPath(positionals[1]);
|
|
886
|
+
const { runCoverageAcrossPacks } = await Promise.resolve().then(() => __importStar(require('./analyzers/coverage-runner')));
|
|
887
|
+
const { detectActiveLanguages } = await Promise.resolve().then(() => __importStar(require('./languages')));
|
|
888
|
+
logger.header('vyuh-dxkit coverage');
|
|
889
|
+
const active = detectActiveLanguages(targetPath);
|
|
890
|
+
const langFilter = values.lang;
|
|
891
|
+
const failFast = !values['no-fail-fast'];
|
|
892
|
+
const candidates = active.filter((p) => !langFilter || p.id === langFilter);
|
|
893
|
+
if (candidates.length === 0) {
|
|
894
|
+
logger.fail(langFilter
|
|
895
|
+
? `No active language pack matches --lang ${langFilter}. Active packs: ${active.map((p) => p.id).join(', ') || '(none)'}`
|
|
896
|
+
: `No active language packs detected in ${targetPath}. Nothing to run.`);
|
|
897
|
+
process.exit(2);
|
|
898
|
+
}
|
|
899
|
+
logger.info(`Stack: ${candidates.map((p) => p.id).join(', ')}`);
|
|
900
|
+
console.log(''); // slop-ok
|
|
901
|
+
const { rows } = await runCoverageAcrossPacks(targetPath, {
|
|
902
|
+
langFilter,
|
|
903
|
+
failFast,
|
|
904
|
+
onPackStart: (id) => process.stderr.write(` → ${id}: running tests with coverage...\n`),
|
|
905
|
+
});
|
|
906
|
+
// Render summary table via the same drain-aware stdout primitive
|
|
907
|
+
// emitJson uses — wide table rows would otherwise trip the
|
|
908
|
+
// no-bare-console-statements slop gate.
|
|
909
|
+
const writeRow = (s) => {
|
|
910
|
+
process.stdout.write(s + '\n');
|
|
911
|
+
};
|
|
912
|
+
writeRow('');
|
|
913
|
+
writeRow(` ${logger.bold('Pack'.padEnd(12))} ${logger.bold('Status'.padEnd(12))} ${logger.bold('Duration'.padEnd(10))} ${logger.bold('Artifact')}`);
|
|
914
|
+
writeRow(` ${'─'.repeat(12)} ${'─'.repeat(12)} ${'─'.repeat(10)} ${'─'.repeat(40)}`);
|
|
915
|
+
for (const r of rows) {
|
|
916
|
+
const icon = r.status === 'success'
|
|
917
|
+
? '\x1b[32m✓\x1b[0m'
|
|
918
|
+
: r.status === 'unavailable' || r.status === 'skipped'
|
|
919
|
+
? '\x1b[2m·\x1b[0m'
|
|
920
|
+
: '\x1b[31m✗\x1b[0m';
|
|
921
|
+
const duration = r.durationMs > 0 ? `${(r.durationMs / 1000).toFixed(1)}s`.padStart(10) : '—'.padStart(10);
|
|
922
|
+
const right = r.artifact ?? r.reason ?? '';
|
|
923
|
+
writeRow(` ${icon} ${r.pack.padEnd(10)} ${r.status.padEnd(12)} ${duration} ${right}`);
|
|
924
|
+
}
|
|
925
|
+
const successes = rows.filter((r) => r.status === 'success').length;
|
|
926
|
+
const failures = rows.filter((r) => r.status === 'failed').length;
|
|
927
|
+
const unavailable = rows.filter((r) => r.status === 'unavailable' || r.status === 'skipped').length;
|
|
928
|
+
console.log(''); // slop-ok
|
|
929
|
+
if (failures > 0) {
|
|
930
|
+
logger.fail(`${successes}/${rows.length} packs produced coverage. ${failures} failed.`);
|
|
931
|
+
process.exit(1);
|
|
932
|
+
}
|
|
933
|
+
else if (successes === 0) {
|
|
934
|
+
logger.fail(`0/${rows.length} packs produced coverage (${unavailable} unavailable / skipped).`);
|
|
935
|
+
process.exit(2);
|
|
936
|
+
}
|
|
937
|
+
else {
|
|
938
|
+
logger.success(`${successes}/${rows.length} packs produced coverage. ` +
|
|
939
|
+
`Run \`vyuh-dxkit health\` or \`vyuh-dxkit test-gaps\` to consume.`);
|
|
940
|
+
}
|
|
941
|
+
break;
|
|
942
|
+
}
|
|
943
|
+
case 'report': {
|
|
944
|
+
// D021 (2.4.7 sub-piece 3): single orchestrator that runs every
|
|
945
|
+
// analyzer in sequence and produces a fully-populated dashboard.
|
|
946
|
+
// Child-process model rather than direct function calls: each
|
|
947
|
+
// analyzer command already owns its file-write flow (D032 made
|
|
948
|
+
// the detailed JSON + MD unconditional), so spawning preserves
|
|
949
|
+
// every side effect without duplicating code. The ~7 extra Node
|
|
950
|
+
// startups add ~10-15s on top of 5-10 minutes of real analysis —
|
|
951
|
+
// acceptable for a "press one button, get a complete audit"
|
|
952
|
+
// command. Direct function refactoring is recipe-v4 candidate
|
|
953
|
+
// territory (would touch every analyzer's CLI wiring).
|
|
954
|
+
const targetPath = resolveRepoPath(positionals[1]);
|
|
955
|
+
const { spawnSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
956
|
+
logger.header('vyuh-dxkit report');
|
|
957
|
+
logger.info(`Generating full audit for ${targetPath}...`);
|
|
958
|
+
console.log(''); // slop-ok
|
|
959
|
+
// Which analyzers run, in dependency order. `health` runs first
|
|
960
|
+
// so its detailed JSON exists when later commands or the
|
|
961
|
+
// dashboard look for it; `dashboard` runs last so every report
|
|
962
|
+
// it embeds is fresh.
|
|
963
|
+
const analyzerSteps = [
|
|
964
|
+
{ label: 'Health', cmd: 'health', reportPrefix: 'health-audit' },
|
|
965
|
+
{ label: 'Vulnerabilities', cmd: 'vulnerabilities', reportPrefix: 'vulnerability-scan' },
|
|
966
|
+
{ label: 'Test gaps', cmd: 'test-gaps', reportPrefix: 'test-gaps' },
|
|
967
|
+
{ label: 'Code quality', cmd: 'quality', reportPrefix: 'quality-review' },
|
|
968
|
+
{ label: 'Developer report', cmd: 'dev-report', reportPrefix: 'developer-report' },
|
|
969
|
+
{ label: 'BoM', cmd: 'bom', reportPrefix: 'bom' },
|
|
970
|
+
{ label: 'Licenses', cmd: 'licenses', reportPrefix: 'licenses' },
|
|
971
|
+
];
|
|
972
|
+
// Forward common analyzer flags to each child so the orchestrator
|
|
973
|
+
// honors the same options the user would pass to a single command.
|
|
974
|
+
const passthroughFlags = [];
|
|
975
|
+
if (values.detailed)
|
|
976
|
+
passthroughFlags.push('--detailed');
|
|
977
|
+
if (values.xlsx)
|
|
978
|
+
passthroughFlags.push('--xlsx');
|
|
979
|
+
if (values.verbose)
|
|
980
|
+
passthroughFlags.push('--verbose');
|
|
981
|
+
if (values.since)
|
|
982
|
+
passthroughFlags.push('--since', values.since);
|
|
983
|
+
if (values.filter)
|
|
984
|
+
passthroughFlags.push('--filter', values.filter);
|
|
985
|
+
if (values['no-nested'])
|
|
986
|
+
passthroughFlags.push('--no-nested');
|
|
987
|
+
// --with-coverage handled ONCE upfront via `vyuh-dxkit coverage`;
|
|
988
|
+
// health + test-gaps then read the materialized artifact via
|
|
989
|
+
// `loadCoverage()` without re-running the test suite per command.
|
|
990
|
+
// Pre-fix `report --with-coverage` (had it existed) would have
|
|
991
|
+
// double-run tests for health and again for test-gaps.
|
|
992
|
+
const runStartedAt = Date.now();
|
|
993
|
+
const stepDurations = [];
|
|
994
|
+
if (values['with-coverage']) {
|
|
995
|
+
logger.info('[setup] Materializing coverage artifacts (one run, shared)...');
|
|
996
|
+
const t0 = Date.now();
|
|
997
|
+
const rc = spawnSync(process.execPath, [
|
|
998
|
+
process.argv[1],
|
|
999
|
+
'coverage',
|
|
1000
|
+
targetPath,
|
|
1001
|
+
...(values['no-fail-fast'] ? ['--no-fail-fast'] : []),
|
|
1002
|
+
], { stdio: 'inherit' }).status;
|
|
1003
|
+
stepDurations.push({ label: 'Coverage', ms: Date.now() - t0, rc: rc ?? -1 });
|
|
1004
|
+
console.log(''); // slop-ok
|
|
1005
|
+
}
|
|
1006
|
+
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
1007
|
+
// Snapshot the date once at orchestrator startup so every
|
|
1008
|
+
// spawned subcommand writes filenames against the same date —
|
|
1009
|
+
// long runs crossing UTC midnight otherwise produce a mix of
|
|
1010
|
+
// pre- and post-midnight suffixes, and the post-step file-
|
|
1011
|
+
// existence checks below miss the rolled-forward files.
|
|
1012
|
+
const dateStr = (0, report_date_1.getReportDate)();
|
|
1013
|
+
const childEnv = { ...process.env, DXKIT_REPORT_DATE: dateStr };
|
|
1014
|
+
for (const step of analyzerSteps) {
|
|
1015
|
+
logger.info(`[${stepDurations.length + 1}/${analyzerSteps.length + 1}] ${step.label}...`);
|
|
1016
|
+
const t0 = Date.now();
|
|
1017
|
+
const rc = spawnSync(process.execPath, [process.argv[1], step.cmd, targetPath, ...passthroughFlags, ...(step.extraFlags ?? [])], { stdio: 'inherit', env: childEnv }).status;
|
|
1018
|
+
let effectiveRc = rc ?? -1;
|
|
1019
|
+
// Post-step assertion: the child returned rc=0 BUT did the
|
|
1020
|
+
// expected markdown actually land on disk? On heavy polyglot
|
|
1021
|
+
// repos (a JS-heavy customer frontend; 13K+ graphify nodes,
|
|
1022
|
+
// jscpd timeout exhaustion) the health child was observed to silently exit
|
|
1023
|
+
// 0 without writing its markdown — the dashboard then renders
|
|
1024
|
+
// "no <X> data" and the customer never learns their report
|
|
1025
|
+
// is missing. The orchestrator owns the "did the report
|
|
1026
|
+
// actually ship" assertion; analyzer subcommands keep their
|
|
1027
|
+
// own write logic unchanged.
|
|
1028
|
+
if (effectiveRc === 0) {
|
|
1029
|
+
const expectedReport = path.join(reportDir, `${step.reportPrefix}-${dateStr}.md`);
|
|
1030
|
+
if (!fs.existsSync(expectedReport)) {
|
|
1031
|
+
logger.warn(`${step.label} returned exit 0 but did NOT write ${path.relative(targetPath, expectedReport)}. ` +
|
|
1032
|
+
`Treating as failure so the final summary surfaces it.`);
|
|
1033
|
+
effectiveRc = -1;
|
|
727
1034
|
}
|
|
728
1035
|
}
|
|
1036
|
+
stepDurations.push({ label: step.label, ms: Date.now() - t0, rc: effectiveRc });
|
|
1037
|
+
// When the FIRST step (Health) fails, the AnalysisResult cache
|
|
1038
|
+
// didn't get built — every downstream step then re-runs the
|
|
1039
|
+
// full detect + Layer 0 + Layer 2 gather from scratch. On a
|
|
1040
|
+
// heavy polyglot frontend this added ~86 s of redundant Layer
|
|
1041
|
+
// 2 work to Step 2 (Vulnerabilities) alone, and ~10× that
|
|
1042
|
+
// across the remaining 6 steps. Surface the cascade so the
|
|
1043
|
+
// user understands why subsequent steps feel slower; the
|
|
1044
|
+
// alternative path (build the cache directly from the failed
|
|
1045
|
+
// gather) is a structural fix tracked for a later release.
|
|
1046
|
+
if (step.cmd === 'health' && effectiveRc !== 0) {
|
|
1047
|
+
logger.warn('Health failed before the analysis cache could be built. ' +
|
|
1048
|
+
'The remaining steps will re-detect the stack and re-gather ' +
|
|
1049
|
+
'shared metrics from scratch (expect each to be measurably ' +
|
|
1050
|
+
'slower than usual). Their reports will still be written ' +
|
|
1051
|
+
'when they succeed individually.');
|
|
1052
|
+
}
|
|
1053
|
+
console.log(''); // slop-ok
|
|
1054
|
+
}
|
|
1055
|
+
logger.info(`[${stepDurations.length + 1}/${analyzerSteps.length + 1}] Dashboard...`);
|
|
1056
|
+
const dashT0 = Date.now();
|
|
1057
|
+
const dashRc = spawnSync(process.execPath, [process.argv[1], 'dashboard', targetPath], {
|
|
1058
|
+
stdio: 'inherit',
|
|
1059
|
+
env: childEnv,
|
|
1060
|
+
}).status;
|
|
1061
|
+
stepDurations.push({ label: 'Dashboard', ms: Date.now() - dashT0, rc: dashRc ?? -1 });
|
|
1062
|
+
// Final summary. Always emit it so the user sees the dashboard
|
|
1063
|
+
// location without scrolling through per-step output.
|
|
1064
|
+
const totalElapsed = ((Date.now() - runStartedAt) / 1000).toFixed(1);
|
|
1065
|
+
const failed = stepDurations.filter((s) => s.rc !== 0);
|
|
1066
|
+
console.log(''); // slop-ok
|
|
1067
|
+
logger.dim('─'.repeat(60));
|
|
1068
|
+
for (const s of stepDurations) {
|
|
1069
|
+
const status = s.rc === 0 ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
|
|
1070
|
+
const duration = `${(s.ms / 1000).toFixed(1)}s`.padStart(8);
|
|
1071
|
+
process.stdout.write(` ${status} ${s.label.padEnd(20)} ${duration}\n`);
|
|
1072
|
+
}
|
|
1073
|
+
logger.dim('─'.repeat(60));
|
|
1074
|
+
console.log(''); // slop-ok
|
|
1075
|
+
if (failed.length === 0) {
|
|
1076
|
+
logger.success(`All ${stepDurations.length} steps completed in ${totalElapsed}s. ` +
|
|
1077
|
+
`Open .dxkit/reports/dashboard.html for the full picture.`);
|
|
1078
|
+
}
|
|
1079
|
+
else {
|
|
1080
|
+
logger.warn(`${stepDurations.length - failed.length}/${stepDurations.length} steps completed (${failed.length} failed: ${failed.map((s) => s.label).join(', ')}). ` +
|
|
1081
|
+
`Partial dashboard at .dxkit/reports/dashboard.html.`);
|
|
1082
|
+
process.exit(1);
|
|
729
1083
|
}
|
|
730
1084
|
break;
|
|
731
1085
|
}
|
|
@@ -781,7 +1135,7 @@ function formatMarkdownReport(report, elapsed) {
|
|
|
781
1135
|
lines.push('');
|
|
782
1136
|
lines.push('---');
|
|
783
1137
|
lines.push('');
|
|
784
|
-
lines.push(`## Overall Health Score: ${report.summary.overallScore}/100 (
|
|
1138
|
+
lines.push(`## Overall Health Score: ${report.summary.overallScore}/100 (Rating: ${report.summary.rating})`);
|
|
785
1139
|
lines.push('');
|
|
786
1140
|
lines.push('| Dimension | Score | Status |');
|
|
787
1141
|
lines.push('|---|---|---|');
|
|
@@ -795,7 +1149,7 @@ function formatMarkdownReport(report, elapsed) {
|
|
|
795
1149
|
};
|
|
796
1150
|
for (const [key, dim] of Object.entries(report.dimensions)) {
|
|
797
1151
|
const name = dimNames[key] || key;
|
|
798
|
-
lines.push(`| ${name} | ${dim.score}/100 | ${dim.
|
|
1152
|
+
lines.push(`| ${name} | ${dim.score}/100 | ${dim.rating} |`);
|
|
799
1153
|
}
|
|
800
1154
|
lines.push('');
|
|
801
1155
|
lines.push('---');
|
|
@@ -803,10 +1157,13 @@ function formatMarkdownReport(report, elapsed) {
|
|
|
803
1157
|
// Dimension details
|
|
804
1158
|
for (const [key, dim] of Object.entries(report.dimensions)) {
|
|
805
1159
|
const name = dimNames[key] || key;
|
|
806
|
-
lines.push(`## ${name} (${dim.score}/100) -- ${dim.
|
|
1160
|
+
lines.push(`## ${name} (${dim.score}/100) -- ${dim.rating}`);
|
|
807
1161
|
lines.push('');
|
|
808
1162
|
lines.push(dim.details);
|
|
809
1163
|
lines.push('');
|
|
1164
|
+
const topActions = (0, scoring_1.formatTopActionsBlock)(dim);
|
|
1165
|
+
for (const line of topActions)
|
|
1166
|
+
lines.push(line);
|
|
810
1167
|
lines.push('| Metric | Value |');
|
|
811
1168
|
lines.push('|---|---|');
|
|
812
1169
|
for (const [mk, mv] of Object.entries(dim.metrics)) {
|
|
@@ -818,6 +1175,37 @@ function formatMarkdownReport(report, elapsed) {
|
|
|
818
1175
|
lines.push('---');
|
|
819
1176
|
lines.push('');
|
|
820
1177
|
}
|
|
1178
|
+
// 2.4.7: top-N largest files. Surfaces the file-size distribution
|
|
1179
|
+
// beyond the single "largest" callout in the Code Quality /
|
|
1180
|
+
// Maintainability dimensions. Skipped when the array is empty
|
|
1181
|
+
// (no source files counted or autogen excluded everything).
|
|
1182
|
+
if (report.largestFiles && report.largestFiles.length > 0) {
|
|
1183
|
+
lines.push('## Top Files by Size');
|
|
1184
|
+
lines.push('');
|
|
1185
|
+
lines.push('| Rank | File | Lines |');
|
|
1186
|
+
lines.push('|-----:|------|------:|');
|
|
1187
|
+
report.largestFiles.forEach((f, i) => {
|
|
1188
|
+
lines.push(`| ${i + 1} | \`${f.path}\` | ${f.lines.toLocaleString()} |`);
|
|
1189
|
+
});
|
|
1190
|
+
lines.push('');
|
|
1191
|
+
// Advisory: when largest-files contain paths matching a known
|
|
1192
|
+
// vendored-code convention not already in the customer's
|
|
1193
|
+
// exclusion chain, surface a single tip pointing at the
|
|
1194
|
+
// `.dxkit-ignore` escape hatch. Bundled defaults already cover
|
|
1195
|
+
// `vendor/`, `third_party/`, `playground/`, `lexical-playground/`,
|
|
1196
|
+
// etc.; the remaining cases (most commonly `/libs/`) live in
|
|
1197
|
+
// customer-specific paths that can't be defaulted-away without
|
|
1198
|
+
// false-positives on first-party monorepo layouts.
|
|
1199
|
+
const suspects = (0, vendored_advisor_1.suspectVendoredEntries)(report.largestFiles);
|
|
1200
|
+
if (suspects.length > 0) {
|
|
1201
|
+
lines.push(`> **Tip — possibly vendored:** ${suspects
|
|
1202
|
+
.map((s) => `\`${s.path}\``)
|
|
1203
|
+
.join(', ')} match path conventions for external / vendored code. If these aren't authored by your team, add them (or their parent directory) to \`.dxkit-ignore\` to keep largest-files, Maintainability scoring, and the densest-file metric focused on first-party code.`);
|
|
1204
|
+
lines.push('');
|
|
1205
|
+
}
|
|
1206
|
+
lines.push('---');
|
|
1207
|
+
lines.push('');
|
|
1208
|
+
}
|
|
821
1209
|
// Score calculation table
|
|
822
1210
|
lines.push('## Score Calculation');
|
|
823
1211
|
lines.push('');
|
|
@@ -841,14 +1229,18 @@ function formatMarkdownReport(report, elapsed) {
|
|
|
841
1229
|
// Footer
|
|
842
1230
|
lines.push('---');
|
|
843
1231
|
lines.push('');
|
|
844
|
-
|
|
845
|
-
|
|
1232
|
+
// Drop languages that round to 0% — a single .py file alongside a
|
|
1233
|
+
// 300K-LOC C# codebase shouldn't surface as "Python (0%)" in the
|
|
1234
|
+
// header. Filter at the renderer rather than the detector so the
|
|
1235
|
+
// raw HealthReport.languages still carries everything for
|
|
1236
|
+
// programmatic consumers.
|
|
1237
|
+
const visibleLanguages = report.languages.filter((l) => l.percentage >= 1);
|
|
1238
|
+
if (visibleLanguages.length > 0) {
|
|
1239
|
+
lines.push('**Languages:** ' + visibleLanguages.map((l) => `${l.name} (${l.percentage}%)`).join(', '));
|
|
846
1240
|
lines.push('');
|
|
847
1241
|
}
|
|
848
1242
|
lines.push(`**Tools used:** ${report.toolsUsed.join(', ')}`);
|
|
849
|
-
|
|
850
|
-
lines.push(`**Tools unavailable:** ${report.toolsUnavailable.join(', ')}`);
|
|
851
|
-
}
|
|
1243
|
+
lines.push(...(0, tools_unavailable_prose_1.renderToolsUnavailableLines)(report.toolsUnavailable));
|
|
852
1244
|
lines.push(`**Analysis time:** ${elapsed}s`);
|
|
853
1245
|
lines.push('');
|
|
854
1246
|
lines.push('*Generated by [VyuhLabs DXKit](https://www.npmjs.com/package/@vyuhlabs/dxkit)*');
|