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