@vyuhlabs/dxkit 2.4.8 → 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 +235 -0
- package/README.md +360 -439
- package/dist/analyzers/security/aggregator.d.ts.map +1 -1
- package/dist/analyzers/security/aggregator.js +4 -46
- package/dist/analyzers/security/aggregator.js.map +1 -1
- 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 +6 -1
- 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 +20 -11
- package/dist/analyzers/tools/gitleaks.js.map +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 +360 -81
- 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/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/csharp.d.ts.map +1 -1
- package/dist/languages/csharp.js +0 -9
- 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 +1 -1
- package/dist/languages/index.d.ts.map +1 -1
- 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 +2 -28
- 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,7 +39,6 @@ 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");
|
|
@@ -47,6 +46,9 @@ 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");
|
|
49
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");
|
|
50
52
|
const fs = __importStar(require("fs"));
|
|
51
53
|
const path = __importStar(require("path"));
|
|
52
54
|
// process.stdout.write returns false when the OS pipe buffer is full
|
|
@@ -60,12 +62,51 @@ async function emitJson(payload) {
|
|
|
60
62
|
await new Promise((resolve) => process.stdout.once('drain', resolve));
|
|
61
63
|
}
|
|
62
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
|
+
}
|
|
63
104
|
function printUsage() {
|
|
64
105
|
console.log(`
|
|
65
|
-
${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
|
|
66
107
|
|
|
67
108
|
${logger.bold('Usage:')}
|
|
68
|
-
vyuh-dxkit init [options]
|
|
109
|
+
vyuh-dxkit init [options] Install dxkit agent DX in this repo
|
|
69
110
|
vyuh-dxkit update [options] Re-generate (preserves evolved files)
|
|
70
111
|
vyuh-dxkit doctor Verify setup
|
|
71
112
|
vyuh-dxkit health [path] Run deterministic health analysis
|
|
@@ -81,23 +122,48 @@ function printUsage() {
|
|
|
81
122
|
vyuh-dxkit to-xlsx <json> Convert a dxkit JSON report to 15-col XLSX
|
|
82
123
|
vyuh-dxkit tools [path] Show required analysis tools status
|
|
83
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.
|
|
84
138
|
|
|
85
139
|
${logger.bold('Init options:')}
|
|
86
|
-
--dx-only
|
|
87
|
-
--full
|
|
88
|
-
|
|
89
|
-
--
|
|
90
|
-
--
|
|
91
|
-
--
|
|
92
|
-
|
|
93
|
-
--
|
|
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
|
|
94
159
|
|
|
95
160
|
${logger.bold('Update options:')}
|
|
96
161
|
--force Overwrite modified files (except evolved)
|
|
97
162
|
--rescan Re-run codebase analysis
|
|
98
163
|
|
|
99
164
|
${logger.bold('Analyzer options (health, vulnerabilities, test-gaps, quality, dev-report, licenses, bom):')}
|
|
100
|
-
--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)
|
|
101
167
|
--verbose Print per-tool timing to stderr
|
|
102
168
|
--no-save Skip writing the markdown report file
|
|
103
169
|
--detailed Also write <name>-detailed.md + .json with evidence + ranked actions
|
|
@@ -107,6 +173,12 @@ function printUsage() {
|
|
|
107
173
|
advisory rollup under byTopLevelDep still reflects transitives)
|
|
108
174
|
--with-coverage Health/test-gaps: materialize coverage artifacts via per-pack
|
|
109
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.
|
|
110
182
|
|
|
111
183
|
${logger.bold('Examples:')}
|
|
112
184
|
npx vyuh-dxkit init # Interactive
|
|
@@ -147,6 +219,20 @@ async function run(argv) {
|
|
|
147
219
|
timeout: { type: 'string' },
|
|
148
220
|
'no-fail-fast': { type: 'boolean', default: false },
|
|
149
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 },
|
|
150
236
|
},
|
|
151
237
|
allowPositionals: true,
|
|
152
238
|
strict: false,
|
|
@@ -174,66 +260,102 @@ async function run(argv) {
|
|
|
174
260
|
switch (command) {
|
|
175
261
|
case 'init': {
|
|
176
262
|
logger.header('vyuh-dxkit init');
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
.map(([k]) => k);
|
|
188
|
-
const tools = Object.entries(config.tools)
|
|
189
|
-
.filter(([, v]) => v)
|
|
190
|
-
.map(([k]) => k);
|
|
191
|
-
if (langs.length)
|
|
192
|
-
logger.success(`Languages: ${langs.join(', ')}`);
|
|
193
|
-
if (tools.length)
|
|
194
|
-
logger.success(`Tools: ${tools.join(', ')}`);
|
|
195
|
-
console.log('');
|
|
196
|
-
// .project.yaml implies full mode (create-devstack handles the wizard)
|
|
197
|
-
finalMode = values['dx-only'] ? 'dx-only' : 'full';
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
logger.warn('Found .project.yaml but it is malformed — falling back to detection.');
|
|
201
|
-
}
|
|
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.');
|
|
202
273
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
logger.info('Detecting stack...');
|
|
206
|
-
const detected = (0, detect_1.detect)(cwd);
|
|
207
|
-
const langs = Object.entries(detected.languages)
|
|
208
|
-
.filter(([, v]) => v)
|
|
209
|
-
.map(([k]) => k);
|
|
210
|
-
const tools = Object.entries(detected.tools)
|
|
211
|
-
.filter(([, v]) => v)
|
|
212
|
-
.map(([k]) => k);
|
|
213
|
-
if (langs.length === 0) {
|
|
214
|
-
logger.warn('No languages detected. Generating with minimal config.');
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
logger.success(`Languages: ${langs.join(', ')}`);
|
|
218
|
-
}
|
|
219
|
-
if (tools.length)
|
|
220
|
-
logger.success(`Tools: ${tools.join(', ')}`);
|
|
221
|
-
if (detected.framework)
|
|
222
|
-
logger.success(`Framework: ${detected.framework}`);
|
|
223
|
-
if (detected.testRunner)
|
|
224
|
-
logger.success(`Tests: ${detected.testRunner.framework} (${detected.testRunner.command})`);
|
|
225
|
-
console.log('');
|
|
226
|
-
// Resolve config via prompts
|
|
227
|
-
const promptOpts = {
|
|
228
|
-
yes: !!(values.yes || values.detect),
|
|
229
|
-
detect: !!values.detect,
|
|
230
|
-
name: values.name,
|
|
231
|
-
};
|
|
232
|
-
const result = await (0, prompts_1.promptForConfig)(detected, promptOpts);
|
|
233
|
-
config = result.config;
|
|
234
|
-
finalMode = values.full ? 'full' : values['dx-only'] ? 'dx-only' : result.mode;
|
|
274
|
+
else {
|
|
275
|
+
logger.success(`Languages: ${langs.join(', ')}`);
|
|
235
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;
|
|
236
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
|
+
});
|
|
237
359
|
// Summary
|
|
238
360
|
console.log('');
|
|
239
361
|
logger.header('Summary');
|
|
@@ -243,6 +365,23 @@ async function run(argv) {
|
|
|
243
365
|
logger.warn(`Skipped: ${result.skipped.length} files (already exist)`);
|
|
244
366
|
if (result.overwritten.length)
|
|
245
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
|
+
}
|
|
246
385
|
console.log('');
|
|
247
386
|
logger.info('Manifest written to .vyuh-dxkit.json');
|
|
248
387
|
// Stealth mode: gitignore only files we just created
|
|
@@ -254,6 +393,9 @@ async function run(argv) {
|
|
|
254
393
|
console.log('');
|
|
255
394
|
logger.dim(' Run `vyuh-dxkit doctor` to verify setup');
|
|
256
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
|
+
}
|
|
257
399
|
break;
|
|
258
400
|
}
|
|
259
401
|
case 'update': {
|
|
@@ -311,7 +453,7 @@ async function run(argv) {
|
|
|
311
453
|
const healthMetrics = healthResult.metrics;
|
|
312
454
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
313
455
|
if (values.json) {
|
|
314
|
-
await emitJson(report);
|
|
456
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'health'));
|
|
315
457
|
}
|
|
316
458
|
else {
|
|
317
459
|
// Console output
|
|
@@ -368,13 +510,17 @@ async function run(argv) {
|
|
|
368
510
|
const detailed = buildHealthDetailed(report, healthMetrics);
|
|
369
511
|
const detailedJsonPath = path.join(reportDir, `health-audit-${date}-detailed.json`);
|
|
370
512
|
const detailedMdPath = path.join(reportDir, `health-audit-${date}-detailed.md`);
|
|
371
|
-
fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
|
|
513
|
+
fs.writeFileSync(detailedJsonPath, JSON.stringify((0, report_schema_1.stampSchema)(detailed, 'health-detailed'), null, 2));
|
|
372
514
|
fs.writeFileSync(detailedMdPath, formatHealthDetailedMarkdown(detailed, elapsed));
|
|
373
515
|
if (values.detailed) {
|
|
374
516
|
logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
|
|
375
517
|
logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
|
|
376
518
|
}
|
|
377
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');
|
|
378
524
|
if (!values.json) {
|
|
379
525
|
// Hint about missing tools (exclude project-side config errors).
|
|
380
526
|
const PROJECT_ISSUES = ['config error', 'legacy .eslintrc', 'no eslint config'];
|
|
@@ -436,7 +582,7 @@ async function run(argv) {
|
|
|
436
582
|
const report = await analyzeSecurity(targetPath, { verbose: !!values.verbose });
|
|
437
583
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
438
584
|
if (values.json) {
|
|
439
|
-
await emitJson(report);
|
|
585
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'vulnerabilities'));
|
|
440
586
|
}
|
|
441
587
|
else {
|
|
442
588
|
const s = report.summary.findings;
|
|
@@ -470,13 +616,20 @@ async function run(argv) {
|
|
|
470
616
|
const securityDetailed = buildSecurityDetailed(report);
|
|
471
617
|
const securityDetailedJsonPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.json`);
|
|
472
618
|
const securityDetailedMdPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.md`);
|
|
473
|
-
fs.writeFileSync(securityDetailedJsonPath, JSON.stringify(securityDetailed, null, 2));
|
|
619
|
+
fs.writeFileSync(securityDetailedJsonPath, JSON.stringify((0, report_schema_1.stampSchema)(securityDetailed, 'vulnerabilities-detailed'), null, 2));
|
|
474
620
|
fs.writeFileSync(securityDetailedMdPath, formatSecurityDetailedMarkdown(securityDetailed, elapsed));
|
|
475
621
|
if (values.detailed) {
|
|
476
622
|
logger.success(`Detailed report saved to ${path.relative(targetPath, securityDetailedMdPath)}`);
|
|
477
623
|
logger.success(`Detailed JSON saved to ${path.relative(targetPath, securityDetailedJsonPath)}`);
|
|
478
624
|
}
|
|
479
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)');
|
|
480
633
|
break;
|
|
481
634
|
}
|
|
482
635
|
case 'test-gaps': {
|
|
@@ -510,7 +663,7 @@ async function run(argv) {
|
|
|
510
663
|
const report = await analyzeTestGaps(targetPath, { verbose: !!values.verbose });
|
|
511
664
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
512
665
|
if (values.json) {
|
|
513
|
-
await emitJson(report);
|
|
666
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'test-gaps'));
|
|
514
667
|
}
|
|
515
668
|
else {
|
|
516
669
|
const s = report.summary;
|
|
@@ -540,13 +693,18 @@ async function run(argv) {
|
|
|
540
693
|
const testGapsDetailed = buildTestGapsDetailed(report);
|
|
541
694
|
const testGapsDetailedJsonPath = path.join(reportDir, `test-gaps-${date}-detailed.json`);
|
|
542
695
|
const testGapsDetailedMdPath = path.join(reportDir, `test-gaps-${date}-detailed.md`);
|
|
543
|
-
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));
|
|
544
697
|
fs.writeFileSync(testGapsDetailedMdPath, formatTestGapsDetailedMarkdown(testGapsDetailed, elapsed));
|
|
545
698
|
if (values.detailed) {
|
|
546
699
|
logger.success(`Detailed report saved to ${path.relative(targetPath, testGapsDetailedMdPath)}`);
|
|
547
700
|
logger.success(`Detailed JSON saved to ${path.relative(targetPath, testGapsDetailedJsonPath)}`);
|
|
548
701
|
}
|
|
549
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');
|
|
550
708
|
break;
|
|
551
709
|
}
|
|
552
710
|
case 'quality': {
|
|
@@ -561,7 +719,7 @@ async function run(argv) {
|
|
|
561
719
|
});
|
|
562
720
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
563
721
|
if (values.json) {
|
|
564
|
-
await emitJson(report);
|
|
722
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'quality'));
|
|
565
723
|
}
|
|
566
724
|
else {
|
|
567
725
|
const m = report.metrics;
|
|
@@ -633,7 +791,7 @@ async function run(argv) {
|
|
|
633
791
|
});
|
|
634
792
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
635
793
|
if (values.json) {
|
|
636
|
-
await emitJson(report);
|
|
794
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'dev-report'));
|
|
637
795
|
}
|
|
638
796
|
else {
|
|
639
797
|
const s = report.summary;
|
|
@@ -690,7 +848,7 @@ async function run(argv) {
|
|
|
690
848
|
const report = await analyzeLicenses(targetPath, { verbose: !!values.verbose });
|
|
691
849
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
692
850
|
if (values.json) {
|
|
693
|
-
await emitJson(report); // slop-ok
|
|
851
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'licenses')); // slop-ok
|
|
694
852
|
}
|
|
695
853
|
else {
|
|
696
854
|
const s = report.summary;
|
|
@@ -766,7 +924,7 @@ async function run(argv) {
|
|
|
766
924
|
});
|
|
767
925
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
768
926
|
if (values.json) {
|
|
769
|
-
await emitJson(report); // slop-ok
|
|
927
|
+
await emitJson((0, report_schema_1.stampSchema)(report, 'bom')); // slop-ok
|
|
770
928
|
}
|
|
771
929
|
else {
|
|
772
930
|
const s = report.summary;
|
|
@@ -835,6 +993,12 @@ async function run(argv) {
|
|
|
835
993
|
logger.success(`XLSX saved to ${path.relative(targetPath, xlsxPath)}`);
|
|
836
994
|
}
|
|
837
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');
|
|
838
1002
|
break;
|
|
839
1003
|
}
|
|
840
1004
|
case 'dashboard': {
|
|
@@ -1118,6 +1282,117 @@ async function run(argv) {
|
|
|
1118
1282
|
logger.dim(`Converted in ${elapsed}s · report kind: ${kind}`);
|
|
1119
1283
|
break;
|
|
1120
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
|
+
}
|
|
1121
1396
|
default:
|
|
1122
1397
|
console.error(`Unknown command: ${command}`);
|
|
1123
1398
|
printUsage();
|
|
@@ -1184,7 +1459,9 @@ function formatMarkdownReport(report, elapsed) {
|
|
|
1184
1459
|
lines.push('');
|
|
1185
1460
|
lines.push('| Rank | File | Lines |');
|
|
1186
1461
|
lines.push('|-----:|------|------:|');
|
|
1187
|
-
|
|
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) => {
|
|
1188
1465
|
lines.push(`| ${i + 1} | \`${f.path}\` | ${f.lines.toLocaleString()} |`);
|
|
1189
1466
|
});
|
|
1190
1467
|
lines.push('');
|
|
@@ -1196,7 +1473,9 @@ function formatMarkdownReport(report, elapsed) {
|
|
|
1196
1473
|
// etc.; the remaining cases (most commonly `/libs/`) live in
|
|
1197
1474
|
// customer-specific paths that can't be defaulted-away without
|
|
1198
1475
|
// false-positives on first-party monorepo layouts.
|
|
1199
|
-
|
|
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));
|
|
1200
1479
|
if (suspects.length > 0) {
|
|
1201
1480
|
lines.push(`> **Tip — possibly vendored:** ${suspects
|
|
1202
1481
|
.map((s) => `\`${s.path}\``)
|