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