@vyuhlabs/dxkit 2.4.7 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +456 -30
- package/README.md +360 -439
- package/dist/analyzers/bom/gather.d.ts +3 -3
- package/dist/analyzers/bom/gather.js +3 -3
- package/dist/analyzers/bom/index.js +2 -2
- package/dist/analyzers/bom/index.js.map +1 -1
- package/dist/analyzers/dashboard/index.d.ts.map +1 -1
- package/dist/analyzers/dashboard/index.js +4 -3
- package/dist/analyzers/dashboard/index.js.map +1 -1
- package/dist/analyzers/developer/index.d.ts.map +1 -1
- package/dist/analyzers/developer/index.js +2 -1
- package/dist/analyzers/developer/index.js.map +1 -1
- package/dist/analyzers/dispatcher.d.ts +15 -0
- package/dist/analyzers/dispatcher.d.ts.map +1 -1
- package/dist/analyzers/dispatcher.js +42 -6
- package/dist/analyzers/dispatcher.js.map +1 -1
- package/dist/analyzers/health.d.ts.map +1 -1
- package/dist/analyzers/health.js +11 -1
- package/dist/analyzers/health.js.map +1 -1
- package/dist/analyzers/licenses/gather.d.ts +1 -1
- package/dist/analyzers/licenses/gather.d.ts.map +1 -1
- package/dist/analyzers/licenses/gather.js +18 -2
- package/dist/analyzers/licenses/gather.js.map +1 -1
- package/dist/analyzers/quality/index.d.ts.map +1 -1
- package/dist/analyzers/quality/index.js +10 -2
- package/dist/analyzers/quality/index.js.map +1 -1
- package/dist/analyzers/security/aggregator.d.ts.map +1 -1
- package/dist/analyzers/security/aggregator.js +8 -48
- package/dist/analyzers/security/aggregator.js.map +1 -1
- package/dist/analyzers/security/gather.d.ts +4 -3
- package/dist/analyzers/security/gather.d.ts.map +1 -1
- package/dist/analyzers/security/gather.js +23 -5
- package/dist/analyzers/security/gather.js.map +1 -1
- package/dist/analyzers/security/index.d.ts +1 -1
- package/dist/analyzers/security/index.js +2 -2
- package/dist/analyzers/security/index.js.map +1 -1
- package/dist/analyzers/tools/autogen-header.js +1 -1
- package/dist/analyzers/tools/cloc.js +3 -3
- 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/exclusions.d.ts +6 -6
- package/dist/analyzers/tools/exclusions.js +6 -6
- package/dist/analyzers/tools/fingerprint.d.ts +91 -26
- package/dist/analyzers/tools/fingerprint.d.ts.map +1 -1
- package/dist/analyzers/tools/fingerprint.js +111 -22
- package/dist/analyzers/tools/fingerprint.js.map +1 -1
- package/dist/analyzers/tools/generic.d.ts.map +1 -1
- package/dist/analyzers/tools/generic.js +7 -2
- package/dist/analyzers/tools/generic.js.map +1 -1
- package/dist/analyzers/tools/gitleaks.d.ts +24 -1
- package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
- package/dist/analyzers/tools/gitleaks.js +21 -12
- package/dist/analyzers/tools/gitleaks.js.map +1 -1
- package/dist/analyzers/tools/graphify.js +1 -1
- package/dist/analyzers/tools/jscpd.js +1 -1
- 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/nuget-package-reference.d.ts +6 -4
- package/dist/analyzers/tools/nuget-package-reference.d.ts.map +1 -1
- package/dist/analyzers/tools/nuget-package-reference.js +7 -5
- package/dist/analyzers/tools/nuget-package-reference.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/runner.js +3 -3
- package/dist/analyzers/tools/runner.js.map +1 -1
- package/dist/analyzers/tools/vendored-advisor.js +1 -1
- package/dist/analyzers/tools/walk-paths.d.ts +1 -1
- package/dist/analyzers/tools/walk-paths.js +1 -1
- package/dist/analyzers/tools/walk-source-files.js +1 -1
- package/dist/analyzers/types.d.ts +6 -4
- package/dist/analyzers/types.d.ts.map +1 -1
- package/dist/baseline/baseline-file.d.ts +104 -0
- package/dist/baseline/baseline-file.d.ts.map +1 -0
- package/dist/baseline/baseline-file.js +110 -0
- package/dist/baseline/baseline-file.js.map +1 -0
- package/dist/baseline/check-renderers.d.ts +108 -0
- package/dist/baseline/check-renderers.d.ts.map +1 -0
- package/dist/baseline/check-renderers.js +379 -0
- package/dist/baseline/check-renderers.js.map +1 -0
- package/dist/baseline/check.d.ts +127 -0
- package/dist/baseline/check.d.ts.map +1 -0
- package/dist/baseline/check.js +462 -0
- package/dist/baseline/check.js.map +1 -0
- package/dist/baseline/content-hash.d.ts +83 -0
- package/dist/baseline/content-hash.d.ts.map +1 -0
- package/dist/baseline/content-hash.js +131 -0
- package/dist/baseline/content-hash.js.map +1 -0
- package/dist/baseline/create.d.ts +96 -0
- package/dist/baseline/create.d.ts.map +1 -0
- package/dist/baseline/create.js +339 -0
- package/dist/baseline/create.js.map +1 -0
- package/dist/baseline/entry-to-located.d.ts +35 -0
- package/dist/baseline/entry-to-located.d.ts.map +1 -0
- package/dist/baseline/entry-to-located.js +72 -0
- package/dist/baseline/entry-to-located.js.map +1 -0
- package/dist/baseline/finding-identity.d.ts +47 -0
- package/dist/baseline/finding-identity.d.ts.map +1 -0
- package/dist/baseline/finding-identity.js +292 -0
- package/dist/baseline/finding-identity.js.map +1 -0
- package/dist/baseline/git-aware-match.d.ts +146 -0
- package/dist/baseline/git-aware-match.d.ts.map +1 -0
- package/dist/baseline/git-aware-match.js +439 -0
- package/dist/baseline/git-aware-match.js.map +1 -0
- package/dist/baseline/policy.d.ts +171 -0
- package/dist/baseline/policy.d.ts.map +1 -0
- package/dist/baseline/policy.js +206 -0
- package/dist/baseline/policy.js.map +1 -0
- package/dist/baseline/producers/health.d.ts +30 -0
- package/dist/baseline/producers/health.d.ts.map +1 -0
- package/dist/baseline/producers/health.js +42 -0
- package/dist/baseline/producers/health.js.map +1 -0
- package/dist/baseline/producers/index.d.ts +164 -0
- package/dist/baseline/producers/index.d.ts.map +1 -0
- package/dist/baseline/producers/index.js +200 -0
- package/dist/baseline/producers/index.js.map +1 -0
- package/dist/baseline/producers/licenses.d.ts +23 -0
- package/dist/baseline/producers/licenses.d.ts.map +1 -0
- package/dist/baseline/producers/licenses.js +46 -0
- package/dist/baseline/producers/licenses.js.map +1 -0
- package/dist/baseline/producers/quality.d.ts +39 -0
- package/dist/baseline/producers/quality.d.ts.map +1 -0
- package/dist/baseline/producers/quality.js +84 -0
- package/dist/baseline/producers/quality.js.map +1 -0
- package/dist/baseline/producers/secret-hmac.d.ts +45 -0
- package/dist/baseline/producers/secret-hmac.d.ts.map +1 -0
- package/dist/baseline/producers/secret-hmac.js +70 -0
- package/dist/baseline/producers/secret-hmac.js.map +1 -0
- package/dist/baseline/producers/security.d.ts +59 -0
- package/dist/baseline/producers/security.d.ts.map +1 -0
- package/dist/baseline/producers/security.js +135 -0
- package/dist/baseline/producers/security.js.map +1 -0
- package/dist/baseline/producers/tests.d.ts +36 -0
- package/dist/baseline/producers/tests.d.ts.map +1 -0
- package/dist/baseline/producers/tests.js +69 -0
- package/dist/baseline/producers/tests.js.map +1 -0
- package/dist/baseline/salt.d.ts +45 -0
- package/dist/baseline/salt.d.ts.map +1 -0
- package/dist/baseline/salt.js +113 -0
- package/dist/baseline/salt.js.map +1 -0
- package/dist/baseline/show.d.ts +79 -0
- package/dist/baseline/show.d.ts.map +1 -0
- package/dist/baseline/show.js +233 -0
- package/dist/baseline/show.js.map +1 -0
- package/dist/baseline/types.d.ts +482 -0
- package/dist/baseline/types.d.ts.map +1 -0
- package/dist/baseline/types.js +53 -0
- package/dist/baseline/types.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +395 -92
- package/dist/cli.js.map +1 -1
- package/dist/codebase-scanner.d.ts.map +1 -1
- package/dist/codebase-scanner.js +0 -1
- package/dist/codebase-scanner.js.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +0 -4
- package/dist/constants.js.map +1 -1
- package/dist/detect.js +3 -3
- package/dist/detect.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +22 -25
- package/dist/doctor.js.map +1 -1
- package/dist/fail-on.d.ts +84 -0
- package/dist/fail-on.d.ts.map +1 -0
- package/dist/fail-on.js +128 -0
- package/dist/fail-on.js.map +1 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +2 -141
- package/dist/generator.js.map +1 -1
- package/dist/languages/capabilities/provider.d.ts +4 -4
- package/dist/languages/capabilities/types.d.ts +1 -1
- package/dist/languages/csharp.d.ts.map +1 -1
- package/dist/languages/csharp.js +15 -24
- package/dist/languages/csharp.js.map +1 -1
- package/dist/languages/go.d.ts.map +1 -1
- package/dist/languages/go.js +0 -15
- package/dist/languages/go.js.map +1 -1
- package/dist/languages/index.d.ts +4 -3
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +3 -2
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/java.d.ts.map +1 -1
- package/dist/languages/java.js +0 -6
- package/dist/languages/java.js.map +1 -1
- package/dist/languages/kotlin.d.ts.map +1 -1
- package/dist/languages/kotlin.js +0 -11
- package/dist/languages/kotlin.js.map +1 -1
- package/dist/languages/python.d.ts.map +1 -1
- package/dist/languages/python.js +0 -15
- package/dist/languages/python.js.map +1 -1
- package/dist/languages/ruby.d.ts.map +1 -1
- package/dist/languages/ruby.js +0 -6
- package/dist/languages/ruby.js.map +1 -1
- package/dist/languages/rust.d.ts.map +1 -1
- package/dist/languages/rust.js +0 -4
- package/dist/languages/rust.js.map +1 -1
- package/dist/languages/types.d.ts +9 -35
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript.d.ts.map +1 -1
- package/dist/languages/typescript.js +26 -4
- package/dist/languages/typescript.js.map +1 -1
- package/dist/lib.d.ts +2 -3
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +3 -6
- package/dist/lib.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +0 -10
- package/dist/prompts.js.map +1 -1
- package/dist/report-schema.d.ts +42 -0
- package/dist/report-schema.d.ts.map +1 -0
- package/dist/report-schema.js +54 -0
- package/dist/report-schema.js.map +1 -0
- package/dist/ship-installers.d.ts +106 -0
- package/dist/ship-installers.d.ts.map +1 -0
- package/dist/ship-installers.js +415 -0
- package/dist/ship-installers.js.map +1 -0
- package/dist/types.d.ts +0 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +0 -4
- package/dist/update.js.map +1 -1
- package/package.json +17 -11
- package/templates/.claude/agents/onboarding.md +5 -4
- package/templates/.claude/agents-available/codebase-explorer.md +1 -1
- package/templates/.claude/agents-available/debugger.md +2 -2
- package/templates/.claude/agents-available/health-auditor.md +2 -2
- package/templates/.claude/commands/doctor.md +20 -12
- package/templates/.claude/skills/build/SKILL.md.template +22 -30
- package/templates/.claude/skills/deploy/SKILL.md.template +5 -25
- package/templates/.claude/skills/doctor/SKILL.md +24 -47
- package/templates/.claude/skills/gcloud/SKILL.md +5 -5
- package/templates/.claude/skills/learned/SKILL.md +1 -1
- package/templates/.claude/skills/pulumi/SKILL.md +2 -2
- package/templates/.claude/skills/quality/SKILL.md.template +4 -23
- package/templates/.claude/skills/review/SKILL.md.template +4 -3
- package/templates/.claude/skills/scaffold/SKILL.md.template +5 -15
- package/templates/.claude/skills/secrets/SKILL.md +20 -21
- package/templates/.claude/skills/session/SKILL.md +20 -31
- package/templates/.claude/skills/test/SKILL.md.template +1 -7
- package/templates/.devcontainer/devcontainer.json +81 -0
- package/templates/.devcontainer/install-agent-clis.sh +42 -0
- package/templates/.devcontainer/post-create.sh +67 -0
- package/templates/.githooks/pre-commit +55 -0
- package/templates/.githooks/pre-push +63 -0
- package/templates/.github/workflows/dxkit-baseline-refresh.yml +78 -0
- package/templates/.github/workflows/dxkit-guardrails.yml +98 -0
- package/templates/CLAUDE.md.template +62 -196
- package/dist/project-yaml.d.ts +0 -13
- package/dist/project-yaml.d.ts.map +0 -1
- package/dist/project-yaml.js +0 -188
- package/dist/project-yaml.js.map +0 -1
- package/templates/.ai/README.md +0 -117
- package/templates/.ai/prompts/execution-prompt.md +0 -9
- package/templates/.ai/prompts/planning-prompt.md +0 -18
- package/templates/.ai/prompts/session-end-template.md +0 -182
- package/templates/.ai/prompts/session-end.md +0 -132
- package/templates/.ai/prompts/session-start.md +0 -109
- package/templates/.ai/prompts/step-by-step.md +0 -113
- package/templates/.ai/sessions/.gitkeep +0 -0
- package/templates/.claude/commands/setup-pr-review.md +0 -72
- package/templates/.devcontainer/Dockerfile.dev.template +0 -89
- package/templates/.devcontainer/devcontainer.json.template +0 -184
- package/templates/.devcontainer/docker-compose.yml.template +0 -105
- package/templates/.devcontainer/init-scripts/01-init.sql.template +0 -12
- package/templates/.devcontainer/post-create.sh.template +0 -298
- package/templates/.github/workflows/ci.yml.template +0 -399
- package/templates/.github/workflows/quality.yml.template +0 -376
- package/templates/.pre-commit-config.yaml.template +0 -106
- package/templates/.project/config/edit_config.py +0 -275
- package/templates/.project/config/project_config.py +0 -894
- package/templates/.project/scripts/codegen/generate-all.sh +0 -20
- package/templates/.project/scripts/codegen/validate-all.sh +0 -17
- package/templates/.project/scripts/docs/generate-all.sh +0 -30
- package/templates/.project/scripts/docs/serve.sh +0 -20
- package/templates/.project/scripts/quality/fix-all.sh +0 -138
- package/templates/.project/scripts/quality/lint-go.sh +0 -34
- package/templates/.project/scripts/quality/lint-python.sh +0 -54
- package/templates/.project/scripts/quality/run-all.sh +0 -497
- package/templates/.project/scripts/session/commit.sh +0 -70
- package/templates/.project/scripts/session/create-pr.sh +0 -165
- package/templates/.project/scripts/session/end.sh +0 -207
- package/templates/.project/scripts/session/start.sh +0 -233
- package/templates/.project/scripts/setup/doctor.sh +0 -404
- package/templates/.project/scripts/setup/interactive-setup.sh +0 -585
- package/templates/.project/scripts/sync/sync-template.sh +0 -328
- package/templates/.project/scripts/test/run-all.sh +0 -179
- package/templates/.project/scripts/test/run-quick.sh +0 -25
- package/templates/Makefile +0 -514
- package/templates/config/versions.yaml +0 -57
- package/templates/configs/go/.golangci.yml.template +0 -172
- package/templates/configs/go/go.mod.template +0 -15
- package/templates/configs/java/README.md +0 -6
- package/templates/configs/kotlin/README.md +0 -6
- package/templates/configs/node/package.json.template +0 -67
- package/templates/configs/node/tsconfig.json.template +0 -53
- package/templates/configs/python/pyproject.toml.template +0 -92
- package/templates/configs/python/pytest.ini.template +0 -64
- package/templates/configs/python/ruff.toml.template +0 -79
- package/templates/configs/ruby/README.md +0 -6
- package/templates/configs/rust/Cargo.toml.template +0 -51
- package/templates/configs/shared/.editorconfig +0 -67
- package/templates/scripts/validate-templates.sh +0 -449
package/dist/cli.js
CHANGED
|
@@ -39,13 +39,16 @@ const vendored_advisor_1 = require("./analyzers/tools/vendored-advisor");
|
|
|
39
39
|
const detect_1 = require("./detect");
|
|
40
40
|
const generator_1 = require("./generator");
|
|
41
41
|
const prompts_1 = require("./prompts");
|
|
42
|
-
const project_yaml_1 = require("./project-yaml");
|
|
43
42
|
const update_1 = require("./update");
|
|
44
43
|
const doctor_1 = require("./doctor");
|
|
45
44
|
const constants_1 = require("./constants");
|
|
46
45
|
const logger = __importStar(require("./logger"));
|
|
47
46
|
const scoring_1 = require("./scoring");
|
|
48
47
|
const tools_unavailable_prose_1 = require("./analyzers/tools/tools-unavailable-prose");
|
|
48
|
+
const report_date_1 = require("./analyzers/tools/report-date");
|
|
49
|
+
const fail_on_1 = require("./fail-on");
|
|
50
|
+
const report_schema_1 = require("./report-schema");
|
|
51
|
+
const ship_installers_1 = require("./ship-installers");
|
|
49
52
|
const fs = __importStar(require("fs"));
|
|
50
53
|
const path = __importStar(require("path"));
|
|
51
54
|
// process.stdout.write returns false when the OS pipe buffer is full
|
|
@@ -59,12 +62,51 @@ async function emitJson(payload) {
|
|
|
59
62
|
await new Promise((resolve) => process.stdout.once('drain', resolve));
|
|
60
63
|
}
|
|
61
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Apply `--fail-on-score` to a higher-is-better score. Exits with
|
|
67
|
+
* code 1 + a logged reason when the gate fires. Skips when the user
|
|
68
|
+
* didn't pass the flag. Centralized so every analyzer that supports
|
|
69
|
+
* the flag fires consistent messages.
|
|
70
|
+
*/
|
|
71
|
+
function applyFailOnScore(raw, score, scoreLabel) {
|
|
72
|
+
if (raw === undefined)
|
|
73
|
+
return;
|
|
74
|
+
const threshold = (0, fail_on_1.parseScoreThreshold)(raw);
|
|
75
|
+
if (threshold === null) {
|
|
76
|
+
logger.fail(`--fail-on-score: invalid value "${raw}". Expected a number in [0, 100].`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
const verdict = (0, fail_on_1.checkFailOnScore)(score, threshold);
|
|
80
|
+
if (verdict.fails) {
|
|
81
|
+
logger.fail(`${scoreLabel} ${verdict.reason}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Apply `--fail-on-severity` to a per-severity count map. Exits
|
|
87
|
+
* with code 1 + a logged reason when the gate fires. Skips when
|
|
88
|
+
* the user didn't pass the flag.
|
|
89
|
+
*/
|
|
90
|
+
function applyFailOnSeverity(raw, counts, countsLabel) {
|
|
91
|
+
if (raw === undefined)
|
|
92
|
+
return;
|
|
93
|
+
const tier = (0, fail_on_1.parseSeverityTier)(raw);
|
|
94
|
+
if (tier === null) {
|
|
95
|
+
logger.fail(`--fail-on-severity: invalid tier "${raw}". Expected one of: critical, high, medium, low.`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
const verdict = (0, fail_on_1.checkFailOnSeverity)(counts, tier);
|
|
99
|
+
if (verdict.fails) {
|
|
100
|
+
logger.fail(`${countsLabel}: ${verdict.reason}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
62
104
|
function printUsage() {
|
|
63
105
|
console.log(`
|
|
64
|
-
${logger.bold('vyuh-dxkit')} v${constants_1.VERSION} — AI-native developer experience toolkit
|
|
106
|
+
${logger.bold('vyuh-dxkit')} v${constants_1.VERSION} — AI-native developer experience toolkit for any codebase
|
|
65
107
|
|
|
66
108
|
${logger.bold('Usage:')}
|
|
67
|
-
vyuh-dxkit init [options]
|
|
109
|
+
vyuh-dxkit init [options] Install dxkit agent DX in this repo
|
|
68
110
|
vyuh-dxkit update [options] Re-generate (preserves evolved files)
|
|
69
111
|
vyuh-dxkit doctor Verify setup
|
|
70
112
|
vyuh-dxkit health [path] Run deterministic health analysis
|
|
@@ -80,23 +122,48 @@ function printUsage() {
|
|
|
80
122
|
vyuh-dxkit to-xlsx <json> Convert a dxkit JSON report to 15-col XLSX
|
|
81
123
|
vyuh-dxkit tools [path] Show required analysis tools status
|
|
82
124
|
vyuh-dxkit tools install Interactively install missing tools
|
|
125
|
+
vyuh-dxkit baseline create [path] [--name <name>] [--force]
|
|
126
|
+
Capture per-finding identities to .dxkit/baselines/<name>.json
|
|
127
|
+
(read later by guardrail check to gate new regressions)
|
|
128
|
+
vyuh-dxkit baseline show [path] [--name <n>] [--baseline <path>]
|
|
129
|
+
[--kind <kind>] [--json]
|
|
130
|
+
Pretty-print the on-disk baseline. Default: summary +
|
|
131
|
+
per-kind counts. --kind drills into one kind. --json
|
|
132
|
+
emits a schema-banner-wrapped payload.
|
|
133
|
+
vyuh-dxkit guardrail check [path] [--name <n>] [--baseline <path>]
|
|
134
|
+
[--changed-only] [--policy <path>]
|
|
135
|
+
[--json | --markdown]
|
|
136
|
+
Diff current scan against the named baseline; block on net-new
|
|
137
|
+
regressions per brownfield policy. Exit code 1 when blocked.
|
|
83
138
|
|
|
84
139
|
${logger.bold('Init options:')}
|
|
85
|
-
--dx-only
|
|
86
|
-
--full
|
|
87
|
-
|
|
88
|
-
--
|
|
89
|
-
--
|
|
90
|
-
--
|
|
91
|
-
|
|
92
|
-
--
|
|
140
|
+
--dx-only Just .claude/ + CLAUDE.md (default)
|
|
141
|
+
--full Everything: DX + quality + hooks + devcontainer +
|
|
142
|
+
CI guardrails + baseline-refresh workflow
|
|
143
|
+
--with-hooks Install .githooks/pre-push guardrail hook (pre-commit opt-in)
|
|
144
|
+
--with-precommit-hook Also install .githooks/pre-commit (slow on large repos)
|
|
145
|
+
--with-devcontainer Install .devcontainer/ with pinned toolchains +
|
|
146
|
+
dxkit + Claude Code & Codex CLIs
|
|
147
|
+
--with-ci Install .github/workflows/dxkit-guardrails.yml
|
|
148
|
+
(PR-gate that posts a markdown summary comment)
|
|
149
|
+
--with-baseline-refresh Install .github/workflows/dxkit-baseline-refresh.yml
|
|
150
|
+
--with-pr-review Install .github/workflows/pr-review.yml (AI PR review; opt-in)
|
|
151
|
+
(post-merge auto-regen of .dxkit/baselines/main.json)
|
|
152
|
+
--detect Auto-detect stack, minimal prompts
|
|
153
|
+
--yes Accept all defaults, no prompts
|
|
154
|
+
--force Overwrite existing files (incl. existing hooks/
|
|
155
|
+
devcontainer instead of writing .dxkit sidecars)
|
|
156
|
+
--stealth Gitignore generated files (local-only, not committed)
|
|
157
|
+
--name <n> Override project name
|
|
158
|
+
--no-scan Skip codebase analysis
|
|
93
159
|
|
|
94
160
|
${logger.bold('Update options:')}
|
|
95
161
|
--force Overwrite modified files (except evolved)
|
|
96
162
|
--rescan Re-run codebase analysis
|
|
97
163
|
|
|
98
164
|
${logger.bold('Analyzer options (health, vulnerabilities, test-gaps, quality, dev-report, licenses, bom):')}
|
|
99
|
-
--json Print report as JSON to stdout
|
|
165
|
+
--json Print report as JSON to stdout (top-level 'schema' field
|
|
166
|
+
carries the dxkit.<kind>-report.v1 banner for version-gating)
|
|
100
167
|
--verbose Print per-tool timing to stderr
|
|
101
168
|
--no-save Skip writing the markdown report file
|
|
102
169
|
--detailed Also write <name>-detailed.md + .json with evidence + ranked actions
|
|
@@ -106,6 +173,12 @@ function printUsage() {
|
|
|
106
173
|
advisory rollup under byTopLevelDep still reflects transitives)
|
|
107
174
|
--with-coverage Health/test-gaps: materialize coverage artifacts via per-pack
|
|
108
175
|
runTests() before analysis (line-coverage truth vs filename match)
|
|
176
|
+
--fail-on-score <N> Exit 1 when the analyzer's headline score drops below N.
|
|
177
|
+
Applies to: health (overallScore), test-gaps (effectiveCoverage).
|
|
178
|
+
--fail-on-severity <tier>
|
|
179
|
+
Exit 1 when any finding at <tier> or higher exists.
|
|
180
|
+
tier ∈ critical|high|medium|low.
|
|
181
|
+
Applies to: vulnerabilities, bom.
|
|
109
182
|
|
|
110
183
|
${logger.bold('Examples:')}
|
|
111
184
|
npx vyuh-dxkit init # Interactive
|
|
@@ -146,6 +219,20 @@ async function run(argv) {
|
|
|
146
219
|
timeout: { type: 'string' },
|
|
147
220
|
'no-fail-fast': { type: 'boolean', default: false },
|
|
148
221
|
'with-coverage': { type: 'boolean', default: false },
|
|
222
|
+
'changed-only': { type: 'boolean', default: false },
|
|
223
|
+
baseline: { type: 'string' },
|
|
224
|
+
policy: { type: 'string' },
|
|
225
|
+
markdown: { type: 'boolean', default: false },
|
|
226
|
+
'fail-on-score': { type: 'string' },
|
|
227
|
+
'fail-on-severity': { type: 'string' },
|
|
228
|
+
summary: { type: 'boolean', default: false },
|
|
229
|
+
kind: { type: 'string' },
|
|
230
|
+
'with-hooks': { type: 'boolean', default: false },
|
|
231
|
+
'with-precommit-hook': { type: 'boolean', default: false },
|
|
232
|
+
'with-devcontainer': { type: 'boolean', default: false },
|
|
233
|
+
'with-ci': { type: 'boolean', default: false },
|
|
234
|
+
'with-baseline-refresh': { type: 'boolean', default: false },
|
|
235
|
+
'with-pr-review': { type: 'boolean', default: false },
|
|
149
236
|
},
|
|
150
237
|
allowPositionals: true,
|
|
151
238
|
strict: false,
|
|
@@ -173,66 +260,102 @@ async function run(argv) {
|
|
|
173
260
|
switch (command) {
|
|
174
261
|
case 'init': {
|
|
175
262
|
logger.header('vyuh-dxkit init');
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.map(([k]) => k);
|
|
187
|
-
const tools = Object.entries(config.tools)
|
|
188
|
-
.filter(([, v]) => v)
|
|
189
|
-
.map(([k]) => k);
|
|
190
|
-
if (langs.length)
|
|
191
|
-
logger.success(`Languages: ${langs.join(', ')}`);
|
|
192
|
-
if (tools.length)
|
|
193
|
-
logger.success(`Tools: ${tools.join(', ')}`);
|
|
194
|
-
console.log('');
|
|
195
|
-
// .project.yaml implies full mode (create-devstack handles the wizard)
|
|
196
|
-
finalMode = values['dx-only'] ? 'dx-only' : 'full';
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
logger.warn('Found .project.yaml but it is malformed — falling back to detection.');
|
|
200
|
-
}
|
|
263
|
+
logger.info('Detecting stack...');
|
|
264
|
+
const detected = (0, detect_1.detect)(cwd);
|
|
265
|
+
const langs = Object.entries(detected.languages)
|
|
266
|
+
.filter(([, v]) => v)
|
|
267
|
+
.map(([k]) => k);
|
|
268
|
+
const tools = Object.entries(detected.tools)
|
|
269
|
+
.filter(([, v]) => v)
|
|
270
|
+
.map(([k]) => k);
|
|
271
|
+
if (langs.length === 0) {
|
|
272
|
+
logger.warn('No languages detected. Generating with minimal config.');
|
|
201
273
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
logger.info('Detecting stack...');
|
|
205
|
-
const detected = (0, detect_1.detect)(cwd);
|
|
206
|
-
const langs = Object.entries(detected.languages)
|
|
207
|
-
.filter(([, v]) => v)
|
|
208
|
-
.map(([k]) => k);
|
|
209
|
-
const tools = Object.entries(detected.tools)
|
|
210
|
-
.filter(([, v]) => v)
|
|
211
|
-
.map(([k]) => k);
|
|
212
|
-
if (langs.length === 0) {
|
|
213
|
-
logger.warn('No languages detected. Generating with minimal config.');
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
logger.success(`Languages: ${langs.join(', ')}`);
|
|
217
|
-
}
|
|
218
|
-
if (tools.length)
|
|
219
|
-
logger.success(`Tools: ${tools.join(', ')}`);
|
|
220
|
-
if (detected.framework)
|
|
221
|
-
logger.success(`Framework: ${detected.framework}`);
|
|
222
|
-
if (detected.testRunner)
|
|
223
|
-
logger.success(`Tests: ${detected.testRunner.framework} (${detected.testRunner.command})`);
|
|
224
|
-
console.log('');
|
|
225
|
-
// Resolve config via prompts
|
|
226
|
-
const promptOpts = {
|
|
227
|
-
yes: !!(values.yes || values.detect),
|
|
228
|
-
detect: !!values.detect,
|
|
229
|
-
name: values.name,
|
|
230
|
-
};
|
|
231
|
-
const result = await (0, prompts_1.promptForConfig)(detected, promptOpts);
|
|
232
|
-
config = result.config;
|
|
233
|
-
finalMode = values.full ? 'full' : values['dx-only'] ? 'dx-only' : result.mode;
|
|
274
|
+
else {
|
|
275
|
+
logger.success(`Languages: ${langs.join(', ')}`);
|
|
234
276
|
}
|
|
277
|
+
if (tools.length)
|
|
278
|
+
logger.success(`Tools: ${tools.join(', ')}`);
|
|
279
|
+
if (detected.framework)
|
|
280
|
+
logger.success(`Framework: ${detected.framework}`);
|
|
281
|
+
if (detected.testRunner)
|
|
282
|
+
logger.success(`Tests: ${detected.testRunner.framework} (${detected.testRunner.command})`);
|
|
283
|
+
console.log(''); // slop-ok
|
|
284
|
+
const promptOpts = {
|
|
285
|
+
yes: !!(values.yes || values.detect),
|
|
286
|
+
detect: !!values.detect,
|
|
287
|
+
name: values.name,
|
|
288
|
+
};
|
|
289
|
+
const promptResult = await (0, prompts_1.promptForConfig)(detected, promptOpts);
|
|
290
|
+
const config = promptResult.config;
|
|
291
|
+
const finalMode = values.full
|
|
292
|
+
? 'full'
|
|
293
|
+
: values['dx-only']
|
|
294
|
+
? 'dx-only'
|
|
295
|
+
: promptResult.mode;
|
|
235
296
|
const result = await (0, generator_1.generate)(cwd, config, finalMode, !!values.force, !!values['no-scan']);
|
|
297
|
+
// Phase Ship installers (additive). `--full` implies every flag
|
|
298
|
+
// so a one-command setup gets the full 2.5.0 ship surface.
|
|
299
|
+
const isFull = !!values.full;
|
|
300
|
+
// pre-commit hook stays opt-in even under --full because it
|
|
301
|
+
// re-runs every analyzer on every commit (slow on large
|
|
302
|
+
// codebases until incremental scanning lands). Pre-push +
|
|
303
|
+
// CI catch the same regressions before code leaves the
|
|
304
|
+
// developer's machine.
|
|
305
|
+
const wantPrecommitHook = !!values['with-precommit-hook'];
|
|
306
|
+
// --with-precommit-hook implies --with-hooks (so the
|
|
307
|
+
// installer actually runs to install pre-commit alongside
|
|
308
|
+
// pre-push).
|
|
309
|
+
const wantHooks = isFull || !!values['with-hooks'] || wantPrecommitHook;
|
|
310
|
+
const wantDevcontainer = isFull || !!values['with-devcontainer'];
|
|
311
|
+
const wantCi = isFull || !!values['with-ci'];
|
|
312
|
+
const wantBaselineRefresh = isFull || !!values['with-baseline-refresh'];
|
|
313
|
+
// pr-review is opt-in even under --full because the workflow
|
|
314
|
+
// is inert without `ANTHROPIC_API_KEY` + `ENABLE_AI_REVIEW=true`
|
|
315
|
+
// configured separately. Shipping it by default just clutters
|
|
316
|
+
// the Actions tab on repos that don't intend to enable it.
|
|
317
|
+
const wantPrReview = !!values['with-pr-review'];
|
|
318
|
+
const shipResults = [];
|
|
319
|
+
if (wantHooks) {
|
|
320
|
+
shipResults.push({
|
|
321
|
+
label: 'Git hooks',
|
|
322
|
+
result: (0, ship_installers_1.installHooks)(cwd, {
|
|
323
|
+
force: !!values.force,
|
|
324
|
+
withPrecommit: wantPrecommitHook,
|
|
325
|
+
}),
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
if (wantDevcontainer) {
|
|
329
|
+
shipResults.push({
|
|
330
|
+
label: 'Devcontainer',
|
|
331
|
+
result: (0, ship_installers_1.installDevcontainer)(cwd, { force: !!values.force }),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
if (wantCi) {
|
|
335
|
+
shipResults.push({
|
|
336
|
+
label: 'CI guardrails workflow',
|
|
337
|
+
result: (0, ship_installers_1.installCiGuardrails)(cwd, { force: !!values.force }),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
if (wantBaselineRefresh) {
|
|
341
|
+
shipResults.push({
|
|
342
|
+
label: 'CI baseline-refresh workflow',
|
|
343
|
+
result: (0, ship_installers_1.installCiBaselineRefresh)(cwd, { force: !!values.force }),
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (wantPrReview) {
|
|
347
|
+
shipResults.push({
|
|
348
|
+
label: 'AI PR-review workflow',
|
|
349
|
+
result: (0, ship_installers_1.installPrReview)(cwd, { force: !!values.force }),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
// .gitignore + .dxkit-ignore seeding: default-on, no flag.
|
|
353
|
+
// Additive (existing entries preserved); safe for both fresh
|
|
354
|
+
// installs and re-runs.
|
|
355
|
+
shipResults.push({
|
|
356
|
+
label: 'Ignore files',
|
|
357
|
+
result: (0, ship_installers_1.installIgnoreFiles)(cwd, { force: !!values.force }),
|
|
358
|
+
});
|
|
236
359
|
// Summary
|
|
237
360
|
console.log('');
|
|
238
361
|
logger.header('Summary');
|
|
@@ -242,6 +365,23 @@ async function run(argv) {
|
|
|
242
365
|
logger.warn(`Skipped: ${result.skipped.length} files (already exist)`);
|
|
243
366
|
if (result.overwritten.length)
|
|
244
367
|
logger.info(`Overwritten: ${result.overwritten.length} files`);
|
|
368
|
+
for (const { label, result: r } of shipResults) {
|
|
369
|
+
if (r.installed.length) {
|
|
370
|
+
logger.success(`${label}: installed ${r.installed.length} file(s)`);
|
|
371
|
+
for (const f of r.installed)
|
|
372
|
+
logger.dim(` ${f}`);
|
|
373
|
+
}
|
|
374
|
+
if (r.sidecars.length) {
|
|
375
|
+
logger.warn(`${label}: ${r.sidecars.length} sidecar(s) written (existing files preserved)`);
|
|
376
|
+
for (const f of r.sidecars)
|
|
377
|
+
logger.dim(` ${f}`);
|
|
378
|
+
}
|
|
379
|
+
if (r.skipped.length) {
|
|
380
|
+
logger.dim(`${label}: ${r.skipped.length} file(s) skipped (already present)`);
|
|
381
|
+
}
|
|
382
|
+
for (const note of r.notes)
|
|
383
|
+
logger.info(note);
|
|
384
|
+
}
|
|
245
385
|
console.log('');
|
|
246
386
|
logger.info('Manifest written to .vyuh-dxkit.json');
|
|
247
387
|
// Stealth mode: gitignore only files we just created
|
|
@@ -253,6 +393,9 @@ async function run(argv) {
|
|
|
253
393
|
console.log('');
|
|
254
394
|
logger.dim(' Run `vyuh-dxkit doctor` to verify setup');
|
|
255
395
|
logger.dim(' Run `vyuh-dxkit update` to re-generate after changes');
|
|
396
|
+
if (shipResults.length > 0) {
|
|
397
|
+
logger.dim(" Run `vyuh-dxkit baseline create` to capture today's state");
|
|
398
|
+
}
|
|
256
399
|
break;
|
|
257
400
|
}
|
|
258
401
|
case 'update': {
|
|
@@ -310,7 +453,7 @@ async function run(argv) {
|
|
|
310
453
|
const healthMetrics = healthResult.metrics;
|
|
311
454
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
312
455
|
if (values.json) {
|
|
313
|
-
await emitJson(report);
|
|
456
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'health'));
|
|
314
457
|
}
|
|
315
458
|
else {
|
|
316
459
|
// Console output
|
|
@@ -347,7 +490,7 @@ async function run(argv) {
|
|
|
347
490
|
// call unconditionally.
|
|
348
491
|
if (!values['no-save']) {
|
|
349
492
|
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
350
|
-
const date =
|
|
493
|
+
const date = (0, report_date_1.getReportDate)();
|
|
351
494
|
const reportPath = path.join(reportDir, `health-audit-${date}.md`);
|
|
352
495
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
353
496
|
fs.writeFileSync(reportPath, formatMarkdownReport(report, elapsed));
|
|
@@ -367,13 +510,17 @@ async function run(argv) {
|
|
|
367
510
|
const detailed = buildHealthDetailed(report, healthMetrics);
|
|
368
511
|
const detailedJsonPath = path.join(reportDir, `health-audit-${date}-detailed.json`);
|
|
369
512
|
const detailedMdPath = path.join(reportDir, `health-audit-${date}-detailed.md`);
|
|
370
|
-
fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
|
|
513
|
+
fs.writeFileSync(detailedJsonPath, JSON.stringify((0, report_schema_1.stampSchema)(detailed, 'health-detailed'), null, 2));
|
|
371
514
|
fs.writeFileSync(detailedMdPath, formatHealthDetailedMarkdown(detailed, elapsed));
|
|
372
515
|
if (values.detailed) {
|
|
373
516
|
logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
|
|
374
517
|
logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
|
|
375
518
|
}
|
|
376
519
|
}
|
|
520
|
+
// --fail-on-score: applies to the overall health score. Runs
|
|
521
|
+
// after disk writes so a failure still leaves a complete
|
|
522
|
+
// report behind for inspection.
|
|
523
|
+
applyFailOnScore(values['fail-on-score'], report.summary.overallScore, 'health overallScore');
|
|
377
524
|
if (!values.json) {
|
|
378
525
|
// Hint about missing tools (exclude project-side config errors).
|
|
379
526
|
const PROJECT_ISSUES = ['config error', 'legacy .eslintrc', 'no eslint config'];
|
|
@@ -435,7 +582,7 @@ async function run(argv) {
|
|
|
435
582
|
const report = await analyzeSecurity(targetPath, { verbose: !!values.verbose });
|
|
436
583
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
437
584
|
if (values.json) {
|
|
438
|
-
await emitJson(report);
|
|
585
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'vulnerabilities'));
|
|
439
586
|
}
|
|
440
587
|
else {
|
|
441
588
|
const s = report.summary.findings;
|
|
@@ -457,7 +604,7 @@ async function run(argv) {
|
|
|
457
604
|
// Disk side: orthogonal to --json (closes D018).
|
|
458
605
|
if (!values['no-save']) {
|
|
459
606
|
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
460
|
-
const date =
|
|
607
|
+
const date = (0, report_date_1.getReportDate)();
|
|
461
608
|
const reportPath = path.join(reportDir, `vulnerability-scan-${date}.md`);
|
|
462
609
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
463
610
|
fs.writeFileSync(reportPath, formatSecurityReport(report, elapsed));
|
|
@@ -469,13 +616,20 @@ async function run(argv) {
|
|
|
469
616
|
const securityDetailed = buildSecurityDetailed(report);
|
|
470
617
|
const securityDetailedJsonPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.json`);
|
|
471
618
|
const securityDetailedMdPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.md`);
|
|
472
|
-
fs.writeFileSync(securityDetailedJsonPath, JSON.stringify(securityDetailed, null, 2));
|
|
619
|
+
fs.writeFileSync(securityDetailedJsonPath, JSON.stringify((0, report_schema_1.stampSchema)(securityDetailed, 'vulnerabilities-detailed'), null, 2));
|
|
473
620
|
fs.writeFileSync(securityDetailedMdPath, formatSecurityDetailedMarkdown(securityDetailed, elapsed));
|
|
474
621
|
if (values.detailed) {
|
|
475
622
|
logger.success(`Detailed report saved to ${path.relative(targetPath, securityDetailedMdPath)}`);
|
|
476
623
|
logger.success(`Detailed JSON saved to ${path.relative(targetPath, securityDetailedJsonPath)}`);
|
|
477
624
|
}
|
|
478
625
|
}
|
|
626
|
+
// --fail-on-severity: applies to both code findings and
|
|
627
|
+
// dependency advisories. Code findings fire first because
|
|
628
|
+
// they're typically actionable (a SAST hit you wrote);
|
|
629
|
+
// dependency advisories second (transitive issue you may need
|
|
630
|
+
// to triage).
|
|
631
|
+
applyFailOnSeverity(values['fail-on-severity'], report.summary.findings, 'vulnerabilities (code)');
|
|
632
|
+
applyFailOnSeverity(values['fail-on-severity'], report.summary.dependencies, 'vulnerabilities (dependencies)');
|
|
479
633
|
break;
|
|
480
634
|
}
|
|
481
635
|
case 'test-gaps': {
|
|
@@ -509,7 +663,7 @@ async function run(argv) {
|
|
|
509
663
|
const report = await analyzeTestGaps(targetPath, { verbose: !!values.verbose });
|
|
510
664
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
511
665
|
if (values.json) {
|
|
512
|
-
await emitJson(report);
|
|
666
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'test-gaps'));
|
|
513
667
|
}
|
|
514
668
|
else {
|
|
515
669
|
const s = report.summary;
|
|
@@ -527,7 +681,7 @@ async function run(argv) {
|
|
|
527
681
|
// Disk side: orthogonal to --json (closes D018).
|
|
528
682
|
if (!values['no-save']) {
|
|
529
683
|
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
530
|
-
const date =
|
|
684
|
+
const date = (0, report_date_1.getReportDate)();
|
|
531
685
|
const reportPath = path.join(reportDir, `test-gaps-${date}.md`);
|
|
532
686
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
533
687
|
fs.writeFileSync(reportPath, formatTestGapsReport(report, elapsed));
|
|
@@ -539,13 +693,18 @@ async function run(argv) {
|
|
|
539
693
|
const testGapsDetailed = buildTestGapsDetailed(report);
|
|
540
694
|
const testGapsDetailedJsonPath = path.join(reportDir, `test-gaps-${date}-detailed.json`);
|
|
541
695
|
const testGapsDetailedMdPath = path.join(reportDir, `test-gaps-${date}-detailed.md`);
|
|
542
|
-
fs.writeFileSync(testGapsDetailedJsonPath, JSON.stringify(testGapsDetailed, null, 2));
|
|
696
|
+
fs.writeFileSync(testGapsDetailedJsonPath, JSON.stringify((0, report_schema_1.stampSchema)(testGapsDetailed, 'test-gaps-detailed'), null, 2));
|
|
543
697
|
fs.writeFileSync(testGapsDetailedMdPath, formatTestGapsDetailedMarkdown(testGapsDetailed, elapsed));
|
|
544
698
|
if (values.detailed) {
|
|
545
699
|
logger.success(`Detailed report saved to ${path.relative(targetPath, testGapsDetailedMdPath)}`);
|
|
546
700
|
logger.success(`Detailed JSON saved to ${path.relative(targetPath, testGapsDetailedJsonPath)}`);
|
|
547
701
|
}
|
|
548
702
|
}
|
|
703
|
+
// --fail-on-score: applies to the headline effectiveCoverage
|
|
704
|
+
// percentage. Tests-gap reports use a higher-is-better
|
|
705
|
+
// coverage scale, so the same threshold semantics work as
|
|
706
|
+
// for the health overall score.
|
|
707
|
+
applyFailOnScore(values['fail-on-score'], report.summary.effectiveCoverage, 'test-gaps effectiveCoverage');
|
|
549
708
|
break;
|
|
550
709
|
}
|
|
551
710
|
case 'quality': {
|
|
@@ -560,7 +719,7 @@ async function run(argv) {
|
|
|
560
719
|
});
|
|
561
720
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
562
721
|
if (values.json) {
|
|
563
|
-
await emitJson(report);
|
|
722
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'quality'));
|
|
564
723
|
}
|
|
565
724
|
else {
|
|
566
725
|
const m = report.metrics;
|
|
@@ -599,7 +758,7 @@ async function run(argv) {
|
|
|
599
758
|
// Disk side: orthogonal to --json (closes D018).
|
|
600
759
|
if (!values['no-save']) {
|
|
601
760
|
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
602
|
-
const date =
|
|
761
|
+
const date = (0, report_date_1.getReportDate)();
|
|
603
762
|
const reportPath = path.join(reportDir, `quality-review-${date}.md`);
|
|
604
763
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
605
764
|
fs.writeFileSync(reportPath, formatQualityReport(report, elapsed));
|
|
@@ -632,7 +791,7 @@ async function run(argv) {
|
|
|
632
791
|
});
|
|
633
792
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
634
793
|
if (values.json) {
|
|
635
|
-
await emitJson(report);
|
|
794
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'dev-report'));
|
|
636
795
|
}
|
|
637
796
|
else {
|
|
638
797
|
const s = report.summary;
|
|
@@ -656,7 +815,7 @@ async function run(argv) {
|
|
|
656
815
|
// Disk side: orthogonal to --json (closes D018).
|
|
657
816
|
if (!values['no-save']) {
|
|
658
817
|
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
659
|
-
const date =
|
|
818
|
+
const date = (0, report_date_1.getReportDate)();
|
|
660
819
|
const reportPath = path.join(reportDir, `developer-report-${date}.md`);
|
|
661
820
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
662
821
|
fs.writeFileSync(reportPath, formatDevReport(report, elapsed));
|
|
@@ -689,7 +848,7 @@ async function run(argv) {
|
|
|
689
848
|
const report = await analyzeLicenses(targetPath, { verbose: !!values.verbose });
|
|
690
849
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
691
850
|
if (values.json) {
|
|
692
|
-
await emitJson(report); // slop-ok
|
|
851
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'licenses')); // slop-ok
|
|
693
852
|
}
|
|
694
853
|
else {
|
|
695
854
|
const s = report.summary;
|
|
@@ -717,7 +876,7 @@ async function run(argv) {
|
|
|
717
876
|
// Disk side: orthogonal to --json (closes D018).
|
|
718
877
|
if (!values['no-save']) {
|
|
719
878
|
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
720
|
-
const date =
|
|
879
|
+
const date = (0, report_date_1.getReportDate)();
|
|
721
880
|
const reportPath = path.join(reportDir, `licenses-${date}.md`);
|
|
722
881
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
723
882
|
fs.writeFileSync(reportPath, formatLicensesReport(report, elapsed));
|
|
@@ -765,7 +924,7 @@ async function run(argv) {
|
|
|
765
924
|
});
|
|
766
925
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
767
926
|
if (values.json) {
|
|
768
|
-
await emitJson(report); // slop-ok
|
|
927
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'bom')); // slop-ok
|
|
769
928
|
}
|
|
770
929
|
else {
|
|
771
930
|
const s = report.summary;
|
|
@@ -806,7 +965,7 @@ async function run(argv) {
|
|
|
806
965
|
// Disk side: orthogonal to --json (closes D018).
|
|
807
966
|
if (!values['no-save']) {
|
|
808
967
|
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
809
|
-
const date =
|
|
968
|
+
const date = (0, report_date_1.getReportDate)();
|
|
810
969
|
const reportPath = path.join(reportDir, `bom-${date}.md`);
|
|
811
970
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
812
971
|
fs.writeFileSync(reportPath, formatBomReport(report, elapsed));
|
|
@@ -834,6 +993,12 @@ async function run(argv) {
|
|
|
834
993
|
logger.success(`XLSX saved to ${path.relative(targetPath, xlsxPath)}`);
|
|
835
994
|
}
|
|
836
995
|
}
|
|
996
|
+
// --fail-on-severity: BomReport.summary.bySeverity carries
|
|
997
|
+
// per-package max-severity counts. A package with multiple
|
|
998
|
+
// advisories is counted once at its highest severity, which
|
|
999
|
+
// is what a "block at this tier" gate wants — not double
|
|
1000
|
+
// counting.
|
|
1001
|
+
applyFailOnSeverity(values['fail-on-severity'], report.summary.bySeverity, 'bom severity');
|
|
837
1002
|
break;
|
|
838
1003
|
}
|
|
839
1004
|
case 'dashboard': {
|
|
@@ -1003,16 +1168,22 @@ async function run(argv) {
|
|
|
1003
1168
|
console.log(''); // slop-ok
|
|
1004
1169
|
}
|
|
1005
1170
|
const reportDir = path.join(targetPath, '.dxkit', 'reports');
|
|
1006
|
-
|
|
1171
|
+
// Snapshot the date once at orchestrator startup so every
|
|
1172
|
+
// spawned subcommand writes filenames against the same date —
|
|
1173
|
+
// long runs crossing UTC midnight otherwise produce a mix of
|
|
1174
|
+
// pre- and post-midnight suffixes, and the post-step file-
|
|
1175
|
+
// existence checks below miss the rolled-forward files.
|
|
1176
|
+
const dateStr = (0, report_date_1.getReportDate)();
|
|
1177
|
+
const childEnv = { ...process.env, DXKIT_REPORT_DATE: dateStr };
|
|
1007
1178
|
for (const step of analyzerSteps) {
|
|
1008
1179
|
logger.info(`[${stepDurations.length + 1}/${analyzerSteps.length + 1}] ${step.label}...`);
|
|
1009
1180
|
const t0 = Date.now();
|
|
1010
|
-
const rc = spawnSync(process.execPath, [process.argv[1], step.cmd, targetPath, ...passthroughFlags, ...(step.extraFlags ?? [])], { stdio: 'inherit' }).status;
|
|
1181
|
+
const rc = spawnSync(process.execPath, [process.argv[1], step.cmd, targetPath, ...passthroughFlags, ...(step.extraFlags ?? [])], { stdio: 'inherit', env: childEnv }).status;
|
|
1011
1182
|
let effectiveRc = rc ?? -1;
|
|
1012
1183
|
// Post-step assertion: the child returned rc=0 BUT did the
|
|
1013
1184
|
// expected markdown actually land on disk? On heavy polyglot
|
|
1014
|
-
// repos (
|
|
1015
|
-
// exhaustion) the health child was observed to silently exit
|
|
1185
|
+
// repos (a JS-heavy customer frontend; 13K+ graphify nodes,
|
|
1186
|
+
// jscpd timeout exhaustion) the health child was observed to silently exit
|
|
1016
1187
|
// 0 without writing its markdown — the dashboard then renders
|
|
1017
1188
|
// "no <X> data" and the customer never learns their report
|
|
1018
1189
|
// is missing. The orchestrator owns the "did the report
|
|
@@ -1027,12 +1198,29 @@ async function run(argv) {
|
|
|
1027
1198
|
}
|
|
1028
1199
|
}
|
|
1029
1200
|
stepDurations.push({ label: step.label, ms: Date.now() - t0, rc: effectiveRc });
|
|
1201
|
+
// When the FIRST step (Health) fails, the AnalysisResult cache
|
|
1202
|
+
// didn't get built — every downstream step then re-runs the
|
|
1203
|
+
// full detect + Layer 0 + Layer 2 gather from scratch. On a
|
|
1204
|
+
// heavy polyglot frontend this added ~86 s of redundant Layer
|
|
1205
|
+
// 2 work to Step 2 (Vulnerabilities) alone, and ~10× that
|
|
1206
|
+
// across the remaining 6 steps. Surface the cascade so the
|
|
1207
|
+
// user understands why subsequent steps feel slower; the
|
|
1208
|
+
// alternative path (build the cache directly from the failed
|
|
1209
|
+
// gather) is a structural fix tracked for a later release.
|
|
1210
|
+
if (step.cmd === 'health' && effectiveRc !== 0) {
|
|
1211
|
+
logger.warn('Health failed before the analysis cache could be built. ' +
|
|
1212
|
+
'The remaining steps will re-detect the stack and re-gather ' +
|
|
1213
|
+
'shared metrics from scratch (expect each to be measurably ' +
|
|
1214
|
+
'slower than usual). Their reports will still be written ' +
|
|
1215
|
+
'when they succeed individually.');
|
|
1216
|
+
}
|
|
1030
1217
|
console.log(''); // slop-ok
|
|
1031
1218
|
}
|
|
1032
1219
|
logger.info(`[${stepDurations.length + 1}/${analyzerSteps.length + 1}] Dashboard...`);
|
|
1033
1220
|
const dashT0 = Date.now();
|
|
1034
1221
|
const dashRc = spawnSync(process.execPath, [process.argv[1], 'dashboard', targetPath], {
|
|
1035
1222
|
stdio: 'inherit',
|
|
1223
|
+
env: childEnv,
|
|
1036
1224
|
}).status;
|
|
1037
1225
|
stepDurations.push({ label: 'Dashboard', ms: Date.now() - dashT0, rc: dashRc ?? -1 });
|
|
1038
1226
|
// Final summary. Always emit it so the user sees the dashboard
|
|
@@ -1094,6 +1282,117 @@ async function run(argv) {
|
|
|
1094
1282
|
logger.dim(`Converted in ${elapsed}s · report kind: ${kind}`);
|
|
1095
1283
|
break;
|
|
1096
1284
|
}
|
|
1285
|
+
case 'baseline': {
|
|
1286
|
+
const subCommand = positionals[1];
|
|
1287
|
+
if (subCommand === 'create') {
|
|
1288
|
+
const targetPath = resolveRepoPath(positionals[2]);
|
|
1289
|
+
const { createBaseline } = await Promise.resolve().then(() => __importStar(require('./baseline/create')));
|
|
1290
|
+
logger.header('vyuh-dxkit baseline create');
|
|
1291
|
+
logger.info(`Capturing baseline for ${targetPath}...`);
|
|
1292
|
+
const startTime = Date.now();
|
|
1293
|
+
try {
|
|
1294
|
+
const result = await createBaseline({
|
|
1295
|
+
cwd: targetPath,
|
|
1296
|
+
name: values.name,
|
|
1297
|
+
force: !!values.force,
|
|
1298
|
+
verbose: !!values.verbose,
|
|
1299
|
+
});
|
|
1300
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
1301
|
+
const rel = path.relative(targetPath, result.path);
|
|
1302
|
+
logger.success(`Wrote ${rel} — ${result.file.findings.length} findings, salt: ${result.file.saltMode} (${elapsed}s)`);
|
|
1303
|
+
}
|
|
1304
|
+
catch (err) {
|
|
1305
|
+
logger.fail(err.message);
|
|
1306
|
+
process.exit(1);
|
|
1307
|
+
}
|
|
1308
|
+
break;
|
|
1309
|
+
}
|
|
1310
|
+
if (subCommand === 'show') {
|
|
1311
|
+
const targetPath = resolveRepoPath(positionals[2]);
|
|
1312
|
+
const { DEFAULT_BASELINE_NAME, pathForBaseline, readBaselineFile } = await Promise.resolve().then(() => __importStar(require('./baseline/baseline-file')));
|
|
1313
|
+
const { parseKindFilter, renderSummary, renderKind, renderJson, FILTER_KINDS } = await Promise.resolve().then(() => __importStar(require('./baseline/show')));
|
|
1314
|
+
const name = values.name ?? DEFAULT_BASELINE_NAME;
|
|
1315
|
+
const filePath = values.baseline ?? pathForBaseline(targetPath, name);
|
|
1316
|
+
let file;
|
|
1317
|
+
try {
|
|
1318
|
+
file = readBaselineFile(filePath);
|
|
1319
|
+
}
|
|
1320
|
+
catch (err) {
|
|
1321
|
+
logger.fail(err.message);
|
|
1322
|
+
process.exit(1);
|
|
1323
|
+
}
|
|
1324
|
+
// Optional kind filter. Validated up-front so a typo surfaces
|
|
1325
|
+
// a clear error rather than a silently-empty result.
|
|
1326
|
+
let kindFilter;
|
|
1327
|
+
if (values.kind !== undefined) {
|
|
1328
|
+
const parsed = parseKindFilter(values.kind);
|
|
1329
|
+
if (parsed === null) {
|
|
1330
|
+
logger.fail(`--kind: unknown value "${values.kind}". Expected one of: ${FILTER_KINDS.join(', ')}.`);
|
|
1331
|
+
process.exit(1);
|
|
1332
|
+
}
|
|
1333
|
+
kindFilter = parsed;
|
|
1334
|
+
}
|
|
1335
|
+
if (values.json) {
|
|
1336
|
+
await emitJson(renderJson(file, kindFilter ? { kind: kindFilter } : {}));
|
|
1337
|
+
}
|
|
1338
|
+
else if (kindFilter) {
|
|
1339
|
+
process.stdout.write(renderKind(file, kindFilter) + '\n');
|
|
1340
|
+
}
|
|
1341
|
+
else {
|
|
1342
|
+
process.stdout.write(renderSummary(file) + '\n');
|
|
1343
|
+
}
|
|
1344
|
+
break;
|
|
1345
|
+
}
|
|
1346
|
+
logger.fail(`Unknown baseline subcommand: ${subCommand ?? '(missing)'}. ` +
|
|
1347
|
+
`Available: vyuh-dxkit baseline create [path] [--name <name>] [--force] · ` +
|
|
1348
|
+
`vyuh-dxkit baseline show [path] [--name <name>] [--baseline <path>] [--kind <kind>] [--json]`);
|
|
1349
|
+
process.exit(1);
|
|
1350
|
+
break;
|
|
1351
|
+
}
|
|
1352
|
+
case 'guardrail': {
|
|
1353
|
+
const subCommand = positionals[1];
|
|
1354
|
+
if (subCommand !== 'check') {
|
|
1355
|
+
logger.fail(`Unknown guardrail subcommand: ${subCommand ?? '(missing)'}. ` +
|
|
1356
|
+
`Available: vyuh-dxkit guardrail check [path] [--name <n>] [--baseline <path>] ` +
|
|
1357
|
+
`[--changed-only] [--policy <path>] [--json | --markdown]`);
|
|
1358
|
+
process.exit(1);
|
|
1359
|
+
}
|
|
1360
|
+
const targetPath = resolveRepoPath(positionals[2]);
|
|
1361
|
+
const { runGuardrailCheck } = await Promise.resolve().then(() => __importStar(require('./baseline/check')));
|
|
1362
|
+
const { renderConsole, renderJson, renderMarkdown } = await Promise.resolve().then(() => __importStar(require('./baseline/check-renderers')));
|
|
1363
|
+
if (!values.json)
|
|
1364
|
+
logger.header('vyuh-dxkit guardrail check');
|
|
1365
|
+
if (!values.json)
|
|
1366
|
+
logger.info(`Checking ${targetPath} against baseline...`);
|
|
1367
|
+
const startTime = Date.now();
|
|
1368
|
+
try {
|
|
1369
|
+
const result = await runGuardrailCheck({
|
|
1370
|
+
cwd: targetPath,
|
|
1371
|
+
name: values.name,
|
|
1372
|
+
baselinePath: values.baseline,
|
|
1373
|
+
changedOnly: !!values['changed-only'],
|
|
1374
|
+
policyPath: values.policy,
|
|
1375
|
+
verbose: !!values.verbose,
|
|
1376
|
+
});
|
|
1377
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
1378
|
+
if (values.json) {
|
|
1379
|
+
await emitJson(renderJson(result));
|
|
1380
|
+
}
|
|
1381
|
+
else if (values.markdown) {
|
|
1382
|
+
process.stdout.write(renderMarkdown(result) + '\n');
|
|
1383
|
+
}
|
|
1384
|
+
else {
|
|
1385
|
+
process.stdout.write(renderConsole(result) + '\n');
|
|
1386
|
+
logger.dim(`Completed in ${elapsed}s`);
|
|
1387
|
+
}
|
|
1388
|
+
process.exit(result.blocks ? 1 : 0);
|
|
1389
|
+
}
|
|
1390
|
+
catch (err) {
|
|
1391
|
+
logger.fail(err.message);
|
|
1392
|
+
process.exit(1);
|
|
1393
|
+
}
|
|
1394
|
+
break;
|
|
1395
|
+
}
|
|
1097
1396
|
default:
|
|
1098
1397
|
console.error(`Unknown command: ${command}`);
|
|
1099
1398
|
printUsage();
|
|
@@ -1160,7 +1459,9 @@ function formatMarkdownReport(report, elapsed) {
|
|
|
1160
1459
|
lines.push('');
|
|
1161
1460
|
lines.push('| Rank | File | Lines |');
|
|
1162
1461
|
lines.push('|-----:|------|------:|');
|
|
1163
|
-
|
|
1462
|
+
// Top 10 is the render contract — the underlying metric carries
|
|
1463
|
+
// every file over the threshold (consumed by the baseline producer).
|
|
1464
|
+
report.largestFiles.slice(0, 10).forEach((f, i) => {
|
|
1164
1465
|
lines.push(`| ${i + 1} | \`${f.path}\` | ${f.lines.toLocaleString()} |`);
|
|
1165
1466
|
});
|
|
1166
1467
|
lines.push('');
|
|
@@ -1172,7 +1473,9 @@ function formatMarkdownReport(report, elapsed) {
|
|
|
1172
1473
|
// etc.; the remaining cases (most commonly `/libs/`) live in
|
|
1173
1474
|
// customer-specific paths that can't be defaulted-away without
|
|
1174
1475
|
// false-positives on first-party monorepo layouts.
|
|
1175
|
-
|
|
1476
|
+
// Scope the vendored advisor to the rendered top 10 — the tip
|
|
1477
|
+
// calls out files the user can see in the table above.
|
|
1478
|
+
const suspects = (0, vendored_advisor_1.suspectVendoredEntries)(report.largestFiles.slice(0, 10));
|
|
1176
1479
|
if (suspects.length > 0) {
|
|
1177
1480
|
lines.push(`> **Tip — possibly vendored:** ${suspects
|
|
1178
1481
|
.map((s) => `\`${s.path}\``)
|