@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.
Files changed (243) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/README.md +360 -439
  3. package/dist/analyzers/security/aggregator.d.ts.map +1 -1
  4. package/dist/analyzers/security/aggregator.js +4 -46
  5. package/dist/analyzers/security/aggregator.js.map +1 -1
  6. package/dist/analyzers/tools/fingerprint.d.ts +91 -26
  7. package/dist/analyzers/tools/fingerprint.d.ts.map +1 -1
  8. package/dist/analyzers/tools/fingerprint.js +111 -22
  9. package/dist/analyzers/tools/fingerprint.js.map +1 -1
  10. package/dist/analyzers/tools/generic.d.ts.map +1 -1
  11. package/dist/analyzers/tools/generic.js +6 -1
  12. package/dist/analyzers/tools/generic.js.map +1 -1
  13. package/dist/analyzers/tools/gitleaks.d.ts +24 -1
  14. package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
  15. package/dist/analyzers/tools/gitleaks.js +20 -11
  16. package/dist/analyzers/tools/gitleaks.js.map +1 -1
  17. package/dist/analyzers/types.d.ts +6 -4
  18. package/dist/analyzers/types.d.ts.map +1 -1
  19. package/dist/baseline/baseline-file.d.ts +104 -0
  20. package/dist/baseline/baseline-file.d.ts.map +1 -0
  21. package/dist/baseline/baseline-file.js +110 -0
  22. package/dist/baseline/baseline-file.js.map +1 -0
  23. package/dist/baseline/check-renderers.d.ts +108 -0
  24. package/dist/baseline/check-renderers.d.ts.map +1 -0
  25. package/dist/baseline/check-renderers.js +379 -0
  26. package/dist/baseline/check-renderers.js.map +1 -0
  27. package/dist/baseline/check.d.ts +127 -0
  28. package/dist/baseline/check.d.ts.map +1 -0
  29. package/dist/baseline/check.js +462 -0
  30. package/dist/baseline/check.js.map +1 -0
  31. package/dist/baseline/content-hash.d.ts +83 -0
  32. package/dist/baseline/content-hash.d.ts.map +1 -0
  33. package/dist/baseline/content-hash.js +131 -0
  34. package/dist/baseline/content-hash.js.map +1 -0
  35. package/dist/baseline/create.d.ts +96 -0
  36. package/dist/baseline/create.d.ts.map +1 -0
  37. package/dist/baseline/create.js +339 -0
  38. package/dist/baseline/create.js.map +1 -0
  39. package/dist/baseline/entry-to-located.d.ts +35 -0
  40. package/dist/baseline/entry-to-located.d.ts.map +1 -0
  41. package/dist/baseline/entry-to-located.js +72 -0
  42. package/dist/baseline/entry-to-located.js.map +1 -0
  43. package/dist/baseline/finding-identity.d.ts +47 -0
  44. package/dist/baseline/finding-identity.d.ts.map +1 -0
  45. package/dist/baseline/finding-identity.js +292 -0
  46. package/dist/baseline/finding-identity.js.map +1 -0
  47. package/dist/baseline/git-aware-match.d.ts +146 -0
  48. package/dist/baseline/git-aware-match.d.ts.map +1 -0
  49. package/dist/baseline/git-aware-match.js +439 -0
  50. package/dist/baseline/git-aware-match.js.map +1 -0
  51. package/dist/baseline/policy.d.ts +171 -0
  52. package/dist/baseline/policy.d.ts.map +1 -0
  53. package/dist/baseline/policy.js +206 -0
  54. package/dist/baseline/policy.js.map +1 -0
  55. package/dist/baseline/producers/health.d.ts +30 -0
  56. package/dist/baseline/producers/health.d.ts.map +1 -0
  57. package/dist/baseline/producers/health.js +42 -0
  58. package/dist/baseline/producers/health.js.map +1 -0
  59. package/dist/baseline/producers/index.d.ts +164 -0
  60. package/dist/baseline/producers/index.d.ts.map +1 -0
  61. package/dist/baseline/producers/index.js +200 -0
  62. package/dist/baseline/producers/index.js.map +1 -0
  63. package/dist/baseline/producers/licenses.d.ts +23 -0
  64. package/dist/baseline/producers/licenses.d.ts.map +1 -0
  65. package/dist/baseline/producers/licenses.js +46 -0
  66. package/dist/baseline/producers/licenses.js.map +1 -0
  67. package/dist/baseline/producers/quality.d.ts +39 -0
  68. package/dist/baseline/producers/quality.d.ts.map +1 -0
  69. package/dist/baseline/producers/quality.js +84 -0
  70. package/dist/baseline/producers/quality.js.map +1 -0
  71. package/dist/baseline/producers/secret-hmac.d.ts +45 -0
  72. package/dist/baseline/producers/secret-hmac.d.ts.map +1 -0
  73. package/dist/baseline/producers/secret-hmac.js +70 -0
  74. package/dist/baseline/producers/secret-hmac.js.map +1 -0
  75. package/dist/baseline/producers/security.d.ts +59 -0
  76. package/dist/baseline/producers/security.d.ts.map +1 -0
  77. package/dist/baseline/producers/security.js +135 -0
  78. package/dist/baseline/producers/security.js.map +1 -0
  79. package/dist/baseline/producers/tests.d.ts +36 -0
  80. package/dist/baseline/producers/tests.d.ts.map +1 -0
  81. package/dist/baseline/producers/tests.js +69 -0
  82. package/dist/baseline/producers/tests.js.map +1 -0
  83. package/dist/baseline/salt.d.ts +45 -0
  84. package/dist/baseline/salt.d.ts.map +1 -0
  85. package/dist/baseline/salt.js +113 -0
  86. package/dist/baseline/salt.js.map +1 -0
  87. package/dist/baseline/show.d.ts +79 -0
  88. package/dist/baseline/show.d.ts.map +1 -0
  89. package/dist/baseline/show.js +233 -0
  90. package/dist/baseline/show.js.map +1 -0
  91. package/dist/baseline/types.d.ts +482 -0
  92. package/dist/baseline/types.d.ts.map +1 -0
  93. package/dist/baseline/types.js +53 -0
  94. package/dist/baseline/types.js.map +1 -0
  95. package/dist/cli.d.ts.map +1 -1
  96. package/dist/cli.js +360 -81
  97. package/dist/cli.js.map +1 -1
  98. package/dist/codebase-scanner.d.ts.map +1 -1
  99. package/dist/codebase-scanner.js +0 -1
  100. package/dist/codebase-scanner.js.map +1 -1
  101. package/dist/constants.d.ts.map +1 -1
  102. package/dist/constants.js +0 -4
  103. package/dist/constants.js.map +1 -1
  104. package/dist/doctor.d.ts.map +1 -1
  105. package/dist/doctor.js +22 -25
  106. package/dist/doctor.js.map +1 -1
  107. package/dist/fail-on.d.ts +84 -0
  108. package/dist/fail-on.d.ts.map +1 -0
  109. package/dist/fail-on.js +128 -0
  110. package/dist/fail-on.js.map +1 -0
  111. package/dist/generator.d.ts.map +1 -1
  112. package/dist/generator.js +2 -141
  113. package/dist/generator.js.map +1 -1
  114. package/dist/languages/csharp.d.ts.map +1 -1
  115. package/dist/languages/csharp.js +0 -9
  116. package/dist/languages/csharp.js.map +1 -1
  117. package/dist/languages/go.d.ts.map +1 -1
  118. package/dist/languages/go.js +0 -15
  119. package/dist/languages/go.js.map +1 -1
  120. package/dist/languages/index.d.ts +1 -1
  121. package/dist/languages/index.d.ts.map +1 -1
  122. package/dist/languages/index.js.map +1 -1
  123. package/dist/languages/java.d.ts.map +1 -1
  124. package/dist/languages/java.js +0 -6
  125. package/dist/languages/java.js.map +1 -1
  126. package/dist/languages/kotlin.d.ts.map +1 -1
  127. package/dist/languages/kotlin.js +0 -11
  128. package/dist/languages/kotlin.js.map +1 -1
  129. package/dist/languages/python.d.ts.map +1 -1
  130. package/dist/languages/python.js +0 -15
  131. package/dist/languages/python.js.map +1 -1
  132. package/dist/languages/ruby.d.ts.map +1 -1
  133. package/dist/languages/ruby.js +0 -6
  134. package/dist/languages/ruby.js.map +1 -1
  135. package/dist/languages/rust.d.ts.map +1 -1
  136. package/dist/languages/rust.js +0 -4
  137. package/dist/languages/rust.js.map +1 -1
  138. package/dist/languages/types.d.ts +2 -28
  139. package/dist/languages/types.d.ts.map +1 -1
  140. package/dist/languages/typescript.d.ts.map +1 -1
  141. package/dist/languages/typescript.js +26 -4
  142. package/dist/languages/typescript.js.map +1 -1
  143. package/dist/lib.d.ts +2 -3
  144. package/dist/lib.d.ts.map +1 -1
  145. package/dist/lib.js +3 -6
  146. package/dist/lib.js.map +1 -1
  147. package/dist/prompts.d.ts.map +1 -1
  148. package/dist/prompts.js +0 -10
  149. package/dist/prompts.js.map +1 -1
  150. package/dist/report-schema.d.ts +42 -0
  151. package/dist/report-schema.d.ts.map +1 -0
  152. package/dist/report-schema.js +54 -0
  153. package/dist/report-schema.js.map +1 -0
  154. package/dist/ship-installers.d.ts +106 -0
  155. package/dist/ship-installers.d.ts.map +1 -0
  156. package/dist/ship-installers.js +415 -0
  157. package/dist/ship-installers.js.map +1 -0
  158. package/dist/types.d.ts +0 -4
  159. package/dist/types.d.ts.map +1 -1
  160. package/dist/update.d.ts.map +1 -1
  161. package/dist/update.js +0 -4
  162. package/dist/update.js.map +1 -1
  163. package/package.json +17 -11
  164. package/templates/.claude/agents/onboarding.md +5 -4
  165. package/templates/.claude/agents-available/codebase-explorer.md +1 -1
  166. package/templates/.claude/agents-available/debugger.md +2 -2
  167. package/templates/.claude/agents-available/health-auditor.md +2 -2
  168. package/templates/.claude/commands/doctor.md +20 -12
  169. package/templates/.claude/skills/build/SKILL.md.template +22 -30
  170. package/templates/.claude/skills/deploy/SKILL.md.template +5 -25
  171. package/templates/.claude/skills/doctor/SKILL.md +24 -47
  172. package/templates/.claude/skills/gcloud/SKILL.md +5 -5
  173. package/templates/.claude/skills/learned/SKILL.md +1 -1
  174. package/templates/.claude/skills/pulumi/SKILL.md +2 -2
  175. package/templates/.claude/skills/quality/SKILL.md.template +4 -23
  176. package/templates/.claude/skills/review/SKILL.md.template +4 -3
  177. package/templates/.claude/skills/scaffold/SKILL.md.template +5 -15
  178. package/templates/.claude/skills/secrets/SKILL.md +20 -21
  179. package/templates/.claude/skills/session/SKILL.md +20 -31
  180. package/templates/.claude/skills/test/SKILL.md.template +1 -7
  181. package/templates/.devcontainer/devcontainer.json +81 -0
  182. package/templates/.devcontainer/install-agent-clis.sh +42 -0
  183. package/templates/.devcontainer/post-create.sh +67 -0
  184. package/templates/.githooks/pre-commit +55 -0
  185. package/templates/.githooks/pre-push +63 -0
  186. package/templates/.github/workflows/dxkit-baseline-refresh.yml +78 -0
  187. package/templates/.github/workflows/dxkit-guardrails.yml +98 -0
  188. package/templates/CLAUDE.md.template +62 -196
  189. package/dist/project-yaml.d.ts +0 -13
  190. package/dist/project-yaml.d.ts.map +0 -1
  191. package/dist/project-yaml.js +0 -188
  192. package/dist/project-yaml.js.map +0 -1
  193. package/templates/.ai/README.md +0 -117
  194. package/templates/.ai/prompts/execution-prompt.md +0 -9
  195. package/templates/.ai/prompts/planning-prompt.md +0 -18
  196. package/templates/.ai/prompts/session-end-template.md +0 -182
  197. package/templates/.ai/prompts/session-end.md +0 -132
  198. package/templates/.ai/prompts/session-start.md +0 -109
  199. package/templates/.ai/prompts/step-by-step.md +0 -113
  200. package/templates/.ai/sessions/.gitkeep +0 -0
  201. package/templates/.claude/commands/setup-pr-review.md +0 -72
  202. package/templates/.devcontainer/Dockerfile.dev.template +0 -89
  203. package/templates/.devcontainer/devcontainer.json.template +0 -184
  204. package/templates/.devcontainer/docker-compose.yml.template +0 -105
  205. package/templates/.devcontainer/init-scripts/01-init.sql.template +0 -12
  206. package/templates/.devcontainer/post-create.sh.template +0 -298
  207. package/templates/.github/workflows/ci.yml.template +0 -399
  208. package/templates/.github/workflows/quality.yml.template +0 -376
  209. package/templates/.pre-commit-config.yaml.template +0 -106
  210. package/templates/.project/config/edit_config.py +0 -275
  211. package/templates/.project/config/project_config.py +0 -894
  212. package/templates/.project/scripts/codegen/generate-all.sh +0 -20
  213. package/templates/.project/scripts/codegen/validate-all.sh +0 -17
  214. package/templates/.project/scripts/docs/generate-all.sh +0 -30
  215. package/templates/.project/scripts/docs/serve.sh +0 -20
  216. package/templates/.project/scripts/quality/fix-all.sh +0 -138
  217. package/templates/.project/scripts/quality/lint-go.sh +0 -34
  218. package/templates/.project/scripts/quality/lint-python.sh +0 -54
  219. package/templates/.project/scripts/quality/run-all.sh +0 -497
  220. package/templates/.project/scripts/session/commit.sh +0 -70
  221. package/templates/.project/scripts/session/create-pr.sh +0 -165
  222. package/templates/.project/scripts/session/end.sh +0 -207
  223. package/templates/.project/scripts/session/start.sh +0 -233
  224. package/templates/.project/scripts/setup/doctor.sh +0 -404
  225. package/templates/.project/scripts/setup/interactive-setup.sh +0 -585
  226. package/templates/.project/scripts/sync/sync-template.sh +0 -328
  227. package/templates/.project/scripts/test/run-all.sh +0 -179
  228. package/templates/.project/scripts/test/run-quick.sh +0 -25
  229. package/templates/Makefile +0 -514
  230. package/templates/config/versions.yaml +0 -57
  231. package/templates/configs/go/.golangci.yml.template +0 -172
  232. package/templates/configs/go/go.mod.template +0 -15
  233. package/templates/configs/java/README.md +0 -6
  234. package/templates/configs/kotlin/README.md +0 -6
  235. package/templates/configs/node/package.json.template +0 -67
  236. package/templates/configs/node/tsconfig.json.template +0 -53
  237. package/templates/configs/python/pyproject.toml.template +0 -92
  238. package/templates/configs/python/pytest.ini.template +0 -64
  239. package/templates/configs/python/ruff.toml.template +0 -79
  240. package/templates/configs/ruby/README.md +0 -6
  241. package/templates/configs/rust/Cargo.toml.template +0 -51
  242. package/templates/configs/shared/.editorconfig +0 -67
  243. 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] Initialize Claude Code DX in this repo
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 Just .claude/ + CLAUDE.md (default)
87
- --full Everything: DX + quality + hooks + CI
88
- --detect Auto-detect stack, minimal prompts
89
- --yes Accept all defaults, no prompts
90
- --force Overwrite existing files (except evolved)
91
- --stealth Gitignore generated files (local-only, not committed)
92
- --name <n> Override project name
93
- --no-scan Skip codebase analysis
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
- let config;
178
- let finalMode = values.full ? 'full' : 'dx-only';
179
- // If .project.yaml exists (written by create-devstack), try using it as config source
180
- if ((0, project_yaml_1.hasProjectYaml)(cwd)) {
181
- const yamlConfig = (0, project_yaml_1.readProjectYaml)(cwd);
182
- if (yamlConfig) {
183
- logger.info('Found .project.yaml using as config source.');
184
- config = yamlConfig;
185
- const langs = Object.entries(config.languages)
186
- .filter(([, v]) => v)
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
- if (!config) {
204
- // No .project.yaml — detect stack and prompt as before
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
- report.largestFiles.forEach((f, i) => {
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
- const suspects = (0, vendored_advisor_1.suspectVendoredEntries)(report.largestFiles);
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}\``)