@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
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Content-hash identity — drift-tolerant matching via the actual
3
+ * source code around a finding rather than its line position.
4
+ *
5
+ * The line-bucket identity scheme tolerates ±2 lines of drift; the
6
+ * git-aware matcher tolerates any line shift git can describe.
7
+ * Neither helps when git history is unavailable (shallow clone,
8
+ * force-pushed baseline commit) or when the line-bucket boundary
9
+ * straddles a context-stable region. The content-hash layer is the
10
+ * third fallback: pair findings whose surrounding code is byte-
11
+ * identical, regardless of where in the file they live.
12
+ *
13
+ * Pipeline:
14
+ *
15
+ * 1. The producer (Phase 3 baseline-create) reads each finding's
16
+ * surrounding context lines, normalizes whitespace, and
17
+ * computes a SHA-1[0:16] hash. The hash is stamped on the
18
+ * finding entry in the baseline file.
19
+ * 2. At guardrail-check time, the current scan computes content
20
+ * hashes the same way for its own findings.
21
+ * 3. The matcher's content-hash pass pairs prior + current
22
+ * findings with matching `(canonical-rule, contentHash)` after
23
+ * location-based pairing has exhausted what git can do.
24
+ *
25
+ * Trade-offs vs. line-bucket / git-aware match:
26
+ *
27
+ * - Survives any vertical drift (the line number is irrelevant).
28
+ * - Survives file rename + reformat + cross-file refactor when
29
+ * the immediate context survives.
30
+ * - Fails when the surrounding context changes — even a single
31
+ * adjacent variable rename invalidates the hash.
32
+ * - Vulnerable to collisions when two findings have identical
33
+ * context (rare for code-pattern findings, more likely for
34
+ * hygiene markers in similar boilerplate).
35
+ *
36
+ * The matcher tags content-hash matches with confidence 0.80 — below
37
+ * git-line-fuzz (0.88) so the brownfield policy's per-severity
38
+ * thresholds naturally distinguish them. For low-severity findings
39
+ * (threshold 0.90 by default), a content-hash match demotes to
40
+ * `'uncertain'` rather than silently pairing; for critical findings
41
+ * (threshold 0.75), the same match passes through cleanly.
42
+ */
43
+ /**
44
+ * Width of the context window read on each side of the finding's
45
+ * reported line. Three lines above + three lines below + the line
46
+ * itself = a seven-line window that captures the immediate
47
+ * surrounding code without being so wide that unrelated edits
48
+ * invalidate the hash.
49
+ */
50
+ export declare const CONTENT_HASH_CONTEXT_LINES = 3;
51
+ /**
52
+ * Pure function: compute the content hash for a finding at `line`
53
+ * inside `fileContent`. Whitespace is normalized — trailing
54
+ * whitespace stripped, internal runs collapsed to a single space,
55
+ * empty lines preserved as empty — so reformat-only edits don't
56
+ * churn the hash.
57
+ *
58
+ * `line` is 1-based to match every other dxkit line-number contract.
59
+ * `contextLines` defaults to `CONTENT_HASH_CONTEXT_LINES`. Lines
60
+ * before the start of the file or past the end are clamped (the
61
+ * window is smaller near the file edges; that's fine — hashing a
62
+ * shorter window is still deterministic).
63
+ */
64
+ export declare function computeContentHash(fileContent: string, line: number, contextLines?: number): string;
65
+ /**
66
+ * Read a file's content at a specific commit. Returns null when git
67
+ * can't resolve the path-at-commit pair — file didn't exist at
68
+ * `sha`, file is binary, sha is unreachable. Callers treat null
69
+ * the same as "content-hash unavailable for this path."
70
+ *
71
+ * Uses `git show <sha>:<path>` which does not require checking out
72
+ * the commit — safe to call repeatedly in a tight loop without
73
+ * touching the working tree.
74
+ */
75
+ export declare function readFileFromCommit(cwd: string, sha: string, file: string): string | null;
76
+ /**
77
+ * Combined helper: read the file at a commit and compute its
78
+ * content hash at the given line. Returns null when the file
79
+ * couldn't be read. Used by the producer (Phase 3 baseline-create)
80
+ * to stamp content hashes on baseline entries.
81
+ */
82
+ export declare function computeContentHashFromCommit(cwd: string, sha: string, file: string, line: number, contextLines?: number): string | null;
83
+ //# sourceMappingURL=content-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-hash.d.ts","sourceRoot":"","sources":["../../src/baseline/content-hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAKH;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,MAAmC,GAChD,MAAM,CAOR;AAaD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBxF;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,MAAmC,GAChD,MAAM,GAAG,IAAI,CAIf"}
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ /**
3
+ * Content-hash identity — drift-tolerant matching via the actual
4
+ * source code around a finding rather than its line position.
5
+ *
6
+ * The line-bucket identity scheme tolerates ±2 lines of drift; the
7
+ * git-aware matcher tolerates any line shift git can describe.
8
+ * Neither helps when git history is unavailable (shallow clone,
9
+ * force-pushed baseline commit) or when the line-bucket boundary
10
+ * straddles a context-stable region. The content-hash layer is the
11
+ * third fallback: pair findings whose surrounding code is byte-
12
+ * identical, regardless of where in the file they live.
13
+ *
14
+ * Pipeline:
15
+ *
16
+ * 1. The producer (Phase 3 baseline-create) reads each finding's
17
+ * surrounding context lines, normalizes whitespace, and
18
+ * computes a SHA-1[0:16] hash. The hash is stamped on the
19
+ * finding entry in the baseline file.
20
+ * 2. At guardrail-check time, the current scan computes content
21
+ * hashes the same way for its own findings.
22
+ * 3. The matcher's content-hash pass pairs prior + current
23
+ * findings with matching `(canonical-rule, contentHash)` after
24
+ * location-based pairing has exhausted what git can do.
25
+ *
26
+ * Trade-offs vs. line-bucket / git-aware match:
27
+ *
28
+ * - Survives any vertical drift (the line number is irrelevant).
29
+ * - Survives file rename + reformat + cross-file refactor when
30
+ * the immediate context survives.
31
+ * - Fails when the surrounding context changes — even a single
32
+ * adjacent variable rename invalidates the hash.
33
+ * - Vulnerable to collisions when two findings have identical
34
+ * context (rare for code-pattern findings, more likely for
35
+ * hygiene markers in similar boilerplate).
36
+ *
37
+ * The matcher tags content-hash matches with confidence 0.80 — below
38
+ * git-line-fuzz (0.88) so the brownfield policy's per-severity
39
+ * thresholds naturally distinguish them. For low-severity findings
40
+ * (threshold 0.90 by default), a content-hash match demotes to
41
+ * `'uncertain'` rather than silently pairing; for critical findings
42
+ * (threshold 0.75), the same match passes through cleanly.
43
+ */
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.CONTENT_HASH_CONTEXT_LINES = void 0;
46
+ exports.computeContentHash = computeContentHash;
47
+ exports.readFileFromCommit = readFileFromCommit;
48
+ exports.computeContentHashFromCommit = computeContentHashFromCommit;
49
+ const crypto_1 = require("crypto");
50
+ const child_process_1 = require("child_process");
51
+ /**
52
+ * Width of the context window read on each side of the finding's
53
+ * reported line. Three lines above + three lines below + the line
54
+ * itself = a seven-line window that captures the immediate
55
+ * surrounding code without being so wide that unrelated edits
56
+ * invalidate the hash.
57
+ */
58
+ exports.CONTENT_HASH_CONTEXT_LINES = 3;
59
+ /**
60
+ * Pure function: compute the content hash for a finding at `line`
61
+ * inside `fileContent`. Whitespace is normalized — trailing
62
+ * whitespace stripped, internal runs collapsed to a single space,
63
+ * empty lines preserved as empty — so reformat-only edits don't
64
+ * churn the hash.
65
+ *
66
+ * `line` is 1-based to match every other dxkit line-number contract.
67
+ * `contextLines` defaults to `CONTENT_HASH_CONTEXT_LINES`. Lines
68
+ * before the start of the file or past the end are clamped (the
69
+ * window is smaller near the file edges; that's fine — hashing a
70
+ * shorter window is still deterministic).
71
+ */
72
+ function computeContentHash(fileContent, line, contextLines = exports.CONTENT_HASH_CONTEXT_LINES) {
73
+ const lines = fileContent.split('\n');
74
+ const startIdx = Math.max(0, line - 1 - contextLines);
75
+ const endIdx = Math.min(lines.length, line + contextLines);
76
+ const window = lines.slice(startIdx, endIdx);
77
+ const normalized = window.map(normalizeLine).join('\n');
78
+ return (0, crypto_1.createHash)('sha1').update(normalized).digest('hex').slice(0, 16);
79
+ }
80
+ /**
81
+ * Collapse internal whitespace runs to a single space + strip
82
+ * leading / trailing whitespace. Tab vs spaces, mixed indentation,
83
+ * and trailing whitespace become equivalent. Empty lines pass
84
+ * through as empty (no normalization needed) so they preserve their
85
+ * position-information in the window.
86
+ */
87
+ function normalizeLine(line) {
88
+ return line.replace(/\s+/g, ' ').trim();
89
+ }
90
+ /**
91
+ * Read a file's content at a specific commit. Returns null when git
92
+ * can't resolve the path-at-commit pair — file didn't exist at
93
+ * `sha`, file is binary, sha is unreachable. Callers treat null
94
+ * the same as "content-hash unavailable for this path."
95
+ *
96
+ * Uses `git show <sha>:<path>` which does not require checking out
97
+ * the commit — safe to call repeatedly in a tight loop without
98
+ * touching the working tree.
99
+ */
100
+ function readFileFromCommit(cwd, sha, file) {
101
+ try {
102
+ return (0, child_process_1.execFileSync)('git', ['show', `${sha}:${file}`], {
103
+ cwd,
104
+ encoding: 'utf8',
105
+ // Cap output size — git show on a 100MB committed binary would
106
+ // otherwise blow the default stdio buffer. 10MB is generous for
107
+ // any real source file.
108
+ maxBuffer: 10 * 1024 * 1024,
109
+ // Silence the "fatal: path X does not exist" message git emits
110
+ // to stderr when the file/sha pair doesn't resolve. Callers
111
+ // expect a null return, not a stderr message bleeding through.
112
+ stdio: ['ignore', 'pipe', 'pipe'],
113
+ });
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ /**
120
+ * Combined helper: read the file at a commit and compute its
121
+ * content hash at the given line. Returns null when the file
122
+ * couldn't be read. Used by the producer (Phase 3 baseline-create)
123
+ * to stamp content hashes on baseline entries.
124
+ */
125
+ function computeContentHashFromCommit(cwd, sha, file, line, contextLines = exports.CONTENT_HASH_CONTEXT_LINES) {
126
+ const content = readFileFromCommit(cwd, sha, file);
127
+ if (content === null)
128
+ return null;
129
+ return computeContentHash(content, line, contextLines);
130
+ }
131
+ //# sourceMappingURL=content-hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-hash.js","sourceRoot":"","sources":["../../src/baseline/content-hash.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;;;AA2BH,gDAWC;AAuBD,gDAiBC;AAQD,oEAUC;AA9FD,mCAAoC;AACpC,iDAA6C;AAE7C;;;;;;GAMG;AACU,QAAA,0BAA0B,GAAG,CAAC,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,SAAgB,kBAAkB,CAChC,WAAmB,EACnB,IAAY,EACZ,eAAuB,kCAA0B;IAEjD,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,GAAG,YAAY,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,kBAAkB,CAAC,GAAW,EAAE,GAAW,EAAE,IAAY;IACvE,IAAI,CAAC;QACH,OAAO,IAAA,4BAAY,EAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,EAAE;YACrD,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,+DAA+D;YAC/D,gEAAgE;YAChE,wBAAwB;YACxB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;YAC3B,+DAA+D;YAC/D,4DAA4D;YAC5D,+DAA+D;YAC/D,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAgB,4BAA4B,CAC1C,GAAW,EACX,GAAW,EACX,IAAY,EACZ,IAAY,EACZ,eAAuB,kCAA0B;IAEjD,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACnD,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,kBAAkB,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * `dxkit baseline create` orchestrator.
3
+ *
4
+ * Builds the shared producer context (runs every analyzer once),
5
+ * dispatches through the canonical producer registry (CLAUDE.md
6
+ * Rule 10), captures repo + analysis-environment metadata, and
7
+ * writes the result to `.dxkit/baselines/<name>.json`. The on-disk
8
+ * file is the durable record subsequent `guardrail check` runs diff
9
+ * against.
10
+ *
11
+ * This module is the producer side; the matcher + classifier on the
12
+ * consumer side already exist in `git-aware-match.ts` and
13
+ * `policy.ts`. The two are connected by the file format defined in
14
+ * `baseline-file.ts`.
15
+ *
16
+ * Per-kind producer coverage + deferral rationale live in the
17
+ * registry index (`./producers/index.ts`) — the single discovery
18
+ * surface. Adding a new identity kind or analyzer means
19
+ * registering a producer there, never an edit here.
20
+ */
21
+ import type { BaselineAnalysisMeta, BaselineFile, BaselineRepoState } from './baseline-file';
22
+ import type { ProducerContext } from './producers';
23
+ import type { SaltMode } from './salt';
24
+ import type { BaselineEntry } from './types';
25
+ import type { SecurityAggregate } from '../analyzers/security/aggregator';
26
+ export interface CreateBaselineOptions {
27
+ /** Repo root to baseline. Caller should pass an absolute path. */
28
+ readonly cwd: string;
29
+ /** Baseline name (becomes the filename stem under `.dxkit/baselines/`).
30
+ * Defaults to `'main'`. Different names allow per-branch / per-
31
+ * environment baselines to coexist on disk. */
32
+ readonly name?: string;
33
+ /** When true, overwrite an existing baseline file at the same path.
34
+ * When false (default), an existing file makes `createBaseline`
35
+ * throw — guards against accidentally clobbering a committed
36
+ * baseline with a fresh capture. */
37
+ readonly force?: boolean;
38
+ /** Forwarded to the underlying analyzer for per-tool timing logs. */
39
+ readonly verbose?: boolean;
40
+ }
41
+ export interface CreateBaselineResult {
42
+ readonly path: string;
43
+ readonly file: BaselineFile;
44
+ }
45
+ /**
46
+ * Test seam: clear the version cache between test runs so per-test
47
+ * fixtures don't leak resolutions into one another. Production
48
+ * callers never use this — the cache lives for the entire CLI
49
+ * invocation and dies with the process.
50
+ */
51
+ export declare function clearToolVersionCache(): void;
52
+ /**
53
+ * Snapshot of one analyzer run, in the exact shape the baseline file
54
+ * and the guardrail-check both need. Built by `gatherCurrentScan`
55
+ * once and consumed by either path.
56
+ *
57
+ * Why this is shared: the guardrail check re-runs every analyzer to
58
+ * produce the "current" side of the diff. Without a shared step, the
59
+ * gather + producer-dispatch logic would have to be duplicated in
60
+ * `check.ts` — exactly the class of duplication CLAUDE.md Rule 2
61
+ * forbids for tool invocation, and the same hazard applies here.
62
+ */
63
+ export interface CurrentScan {
64
+ readonly findings: ReadonlyArray<BaselineEntry>;
65
+ readonly aggregate: SecurityAggregate;
66
+ readonly repoState: BaselineRepoState;
67
+ readonly saltMode: SaltMode;
68
+ /** Per-tool name → version map for the run that just completed. */
69
+ readonly tools: Readonly<Record<string, string>>;
70
+ /** Envelope metadata for the run. `toolchainHash` is already
71
+ * resolved from `tools`. */
72
+ readonly analysisMeta: BaselineAnalysisMeta;
73
+ /** Echoed back so the guardrail check can attribute per-pair
74
+ * severity, overlap, and reachable signals without re-gathering. */
75
+ readonly producerCtx: ProducerContext;
76
+ }
77
+ /**
78
+ * Run every analyzer once, dispatch through the producer registry,
79
+ * and return the assembled `CurrentScan`. Used by `createBaseline`
80
+ * to capture today's state and by `runGuardrailCheck` to gather the
81
+ * current side of the cross-run diff.
82
+ *
83
+ * Pure-orchestrator: each step has a single responsibility (analyze
84
+ * → produce entries → resolve envelope metadata).
85
+ */
86
+ export declare function gatherCurrentScan(options: {
87
+ readonly cwd: string;
88
+ readonly verbose?: boolean;
89
+ }): Promise<CurrentScan>;
90
+ /**
91
+ * Run the baseline-create pipeline. Pure-orchestrator: gather the
92
+ * current scan via the shared step, then write it to disk under the
93
+ * given name.
94
+ */
95
+ export declare function createBaseline(options: CreateBaselineOptions): Promise<CreateBaselineResult>;
96
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/baseline/create.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoBH,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAG7F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAE1E,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;oDAEgD;IAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;yCAGqC;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;CAC7B;AA6JD;;;;;GAKG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;IAChD,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD;iCAC6B;IAC7B,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C;yEACqE;IACrE,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC;CACvC;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,OAAO,CAAC,WAAW,CAAC,CA0EvB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA0B/B"}
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ /**
3
+ * `dxkit baseline create` orchestrator.
4
+ *
5
+ * Builds the shared producer context (runs every analyzer once),
6
+ * dispatches through the canonical producer registry (CLAUDE.md
7
+ * Rule 10), captures repo + analysis-environment metadata, and
8
+ * writes the result to `.dxkit/baselines/<name>.json`. The on-disk
9
+ * file is the durable record subsequent `guardrail check` runs diff
10
+ * against.
11
+ *
12
+ * This module is the producer side; the matcher + classifier on the
13
+ * consumer side already exist in `git-aware-match.ts` and
14
+ * `policy.ts`. The two are connected by the file format defined in
15
+ * `baseline-file.ts`.
16
+ *
17
+ * Per-kind producer coverage + deferral rationale live in the
18
+ * registry index (`./producers/index.ts`) — the single discovery
19
+ * surface. Adding a new identity kind or analyzer means
20
+ * registering a producer there, never an edit here.
21
+ */
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.clearToolVersionCache = clearToolVersionCache;
57
+ exports.gatherCurrentScan = gatherCurrentScan;
58
+ exports.createBaseline = createBaseline;
59
+ const child_process_1 = require("child_process");
60
+ const crypto_1 = require("crypto");
61
+ const fs = __importStar(require("fs"));
62
+ const path = __importStar(require("path"));
63
+ const health_1 = require("../analyzers/health");
64
+ const cache_1 = require("../analyzers/cache");
65
+ const gather_1 = require("../analyzers/quality/gather");
66
+ const tests_1 = require("../analyzers/tests");
67
+ const gitleaks_1 = require("../analyzers/tools/gitleaks");
68
+ const tool_registry_1 = require("../analyzers/tools/tool-registry");
69
+ const constants_1 = require("../constants");
70
+ const baseline_file_1 = require("./baseline-file");
71
+ const policy_1 = require("./policy");
72
+ const producers_1 = require("./producers");
73
+ const salt_1 = require("./salt");
74
+ /** Hash used for baseline-envelope metadata fields (policy, ignore,
75
+ * toolchain, config). Distinct concern from finding-identity
76
+ * fingerprints — these never enter the matcher's identity space. */
77
+ function hashContent(content) {
78
+ return (0, crypto_1.createHash)('sha1').update(content).digest('hex').slice(0, 16); // fingerprint-helper-ok: envelope-metadata hash, not finding identity
79
+ }
80
+ /**
81
+ * Read a small file's text content with the canonical "absent → ''"
82
+ * convention. Treating absent files as the empty string keeps the
83
+ * downstream metadata hash stable across runs where the file is
84
+ * still missing.
85
+ */
86
+ function readOptionalFile(filePath) {
87
+ try {
88
+ return fs.readFileSync(filePath, 'utf8');
89
+ }
90
+ catch {
91
+ return '';
92
+ }
93
+ }
94
+ /** Resolve the absolute commit SHA + branch name of the working tree.
95
+ * Empty strings when the directory isn't a git repo — the rest of
96
+ * the orchestrator works fine, only the git-aware matcher loses its
97
+ * diff anchor on a future check. */
98
+ function readRepoState(cwd) {
99
+ const run = (...args) => {
100
+ try {
101
+ return (0, child_process_1.execFileSync)('git', args, {
102
+ cwd,
103
+ encoding: 'utf8',
104
+ stdio: ['ignore', 'pipe', 'pipe'],
105
+ }).trim();
106
+ }
107
+ catch {
108
+ return '';
109
+ }
110
+ };
111
+ return {
112
+ commitSha: run('rev-parse', 'HEAD'),
113
+ branch: run('rev-parse', '--abbrev-ref', 'HEAD'),
114
+ };
115
+ }
116
+ /** Build the analysis-environment hash bundle from the live repo. */
117
+ function buildAnalysisMeta(cwd) {
118
+ const policyHash = hashContent(JSON.stringify(policy_1.DEFAULT_BROWNFIELD_POLICY));
119
+ const ignoreHash = hashContent(readOptionalFile(path.join(cwd, '.dxkit-ignore')));
120
+ const configHash = hashContent(readOptionalFile(path.join(cwd, '.vyuh-dxkit.json')));
121
+ // toolchainHash is filled in by `createBaseline` once the
122
+ // per-tool version map has been resolved (depends on the gather).
123
+ return { dxkitVersion: constants_1.VERSION, policyHash, ignoreHash, toolchainHash: '', configHash };
124
+ }
125
+ /** Build the per-tool name → version map from the security
126
+ * aggregate's provenance. Sparse; only the tools that actually
127
+ * ran appear. Versions come from each tool's registered
128
+ * `versionCheck` invocation via `findTool`, so the resulting
129
+ * `toolchainHash` actually differs when a tool is upgraded —
130
+ * closing the drift-detection gap that placeholder values left
131
+ * open. In-process scanners (no external binary) are tagged with
132
+ * the dxkit version so a dxkit upgrade invalidates the toolchain
133
+ * hash even when no external tool changed — see
134
+ * `IN_PROCESS_TOOLS`.
135
+ *
136
+ * Compound tool names like `'osv-scanner-nuget-direct'` (the
137
+ * per-pack synthetic names the dep-vuln providers emit) are
138
+ * resolved by progressively shortening on `-` boundaries until a
139
+ * matching TOOL_DEFS key is found — so
140
+ * `'osv-scanner-nuget-direct'` → `'osv-scanner-nuget'` →
141
+ * `'osv-scanner'` (the canonical key). */
142
+ function buildToolsMap(toolNames, cwd) {
143
+ const out = {};
144
+ for (const name of toolNames) {
145
+ if (!name)
146
+ continue;
147
+ out[name] = resolveToolVersion(name, cwd);
148
+ }
149
+ return out;
150
+ }
151
+ /**
152
+ * Scanner names that don't correspond to an external binary — their
153
+ * "version" tracks the dxkit version. Adding a new in-process
154
+ * scanner (e.g. a future regex-based dependency checker) means
155
+ * appending its name here, never special-casing inside
156
+ * `resolveToolVersion`.
157
+ *
158
+ * Drives the `provenance.{secrets,codePatterns,...}.tool` values
159
+ * that surface when external tools are unavailable — gitleaks
160
+ * absent → `grep-secrets` runs the in-process fallback; the
161
+ * TLS-bypass registry is always in-process.
162
+ */
163
+ const IN_PROCESS_TOOLS = new Set(['tls-bypass-registry', 'grep-secrets']);
164
+ /**
165
+ * Per-process cache of resolved tool versions, keyed by `${name}::${cwd}`.
166
+ *
167
+ * Why this exists: `findTool` spawns an `execFileSync` subprocess to
168
+ * run each tool's `versionCheck` command. Under heavy concurrent
169
+ * load (parallel vitest workers, large suites running side-by-side),
170
+ * that subprocess can occasionally complete with empty stdout —
171
+ * `resolveToolVersion`'s `if (status.version) return status.version`
172
+ * branch is skipped, the `return 'present'` fallback fires, and the
173
+ * resulting toolchainHash drifts between two back-to-back gathers
174
+ * within the same process. The matcher's `tooling_drift` gate then
175
+ * fires spuriously.
176
+ *
177
+ * Tool versions don't change mid-process — once we've resolved
178
+ * `gitleaks → 8.24.0` for `cwd`, every subsequent ask in the same
179
+ * process should return the same answer. The cache locks the first
180
+ * probe's outcome and skips later subprocess spawns entirely; same
181
+ * answer always, with the side benefit of faster repeated gathers.
182
+ *
183
+ * NOT applied to `findTool` itself: `tools-cli.ts` runs an install
184
+ * command then immediately re-probes (the install just created the
185
+ * binary, we need fresh state). That callsite must keep getting
186
+ * uncached results. The cache stays local to the toolchain-version
187
+ * resolver here.
188
+ */
189
+ const VERSION_CACHE = new Map();
190
+ function resolveToolVersion(name, cwd) {
191
+ const cacheKey = `${name}::${cwd}`;
192
+ const cached = VERSION_CACHE.get(cacheKey);
193
+ if (cached !== undefined)
194
+ return cached;
195
+ const resolved = resolveToolVersionUncached(name, cwd);
196
+ VERSION_CACHE.set(cacheKey, resolved);
197
+ return resolved;
198
+ }
199
+ function resolveToolVersionUncached(name, cwd) {
200
+ if (IN_PROCESS_TOOLS.has(name))
201
+ return `dxkit-${constants_1.VERSION}`;
202
+ const parts = name.split('-');
203
+ for (let i = parts.length; i > 0; i--) {
204
+ const candidate = parts.slice(0, i).join('-');
205
+ const def = tool_registry_1.TOOL_DEFS[candidate];
206
+ if (!def)
207
+ continue;
208
+ // Probe the version a few times — under heavy CPU load (parallel
209
+ // test pools, concurrent scanner runs) the underlying `execSync`
210
+ // subprocess can occasionally return before its `--version`
211
+ // output streams back, leaving us with a bare `'present'` even
212
+ // though the tool itself is fully functional. The per-process
213
+ // VERSION_CACHE then locks that empty result for the lifetime of
214
+ // the run, which is what we want for byte-stable toolchainHashes
215
+ // but is wrong when the empty result was a transient artifact.
216
+ // Three attempts absorb the hiccup without slowing the common
217
+ // path (first probe succeeds → exit immediately).
218
+ for (let attempt = 0; attempt < 3; attempt++) {
219
+ const status = (0, tool_registry_1.findTool)(def, cwd);
220
+ if (status.version)
221
+ return status.version;
222
+ }
223
+ return 'present';
224
+ }
225
+ return 'unknown';
226
+ }
227
+ /**
228
+ * Test seam: clear the version cache between test runs so per-test
229
+ * fixtures don't leak resolutions into one another. Production
230
+ * callers never use this — the cache lives for the entire CLI
231
+ * invocation and dies with the process.
232
+ */
233
+ function clearToolVersionCache() {
234
+ VERSION_CACHE.clear();
235
+ }
236
+ /**
237
+ * Run every analyzer once, dispatch through the producer registry,
238
+ * and return the assembled `CurrentScan`. Used by `createBaseline`
239
+ * to capture today's state and by `runGuardrailCheck` to gather the
240
+ * current side of the cross-run diff.
241
+ *
242
+ * Pure-orchestrator: each step has a single responsibility (analyze
243
+ * → produce entries → resolve envelope metadata).
244
+ */
245
+ async function gatherCurrentScan(options) {
246
+ const cwd = path.resolve(options.cwd);
247
+ const analysisResult = await (0, cache_1.readOrBuildAnalysisResult)({
248
+ cwd,
249
+ build: (innerCwd) => (0, health_1.gatherAnalysisResultBody)(innerCwd, { verbose: !!options.verbose }),
250
+ });
251
+ const aggregate = analysisResult.capabilities.securityAggregate;
252
+ if (!aggregate) {
253
+ throw new Error('baseline scan: cached AnalysisResult missing securityAggregate ' +
254
+ '(expected to be populated by gatherAnalysisResultBody).');
255
+ }
256
+ const repoState = {
257
+ ...readRepoState(cwd),
258
+ root: cwd,
259
+ };
260
+ // Salt resolves once; threaded into every producer that needs to
261
+ // compute HMACs. The mode lands on the baseline file so the
262
+ // matcher can re-derive the same salt at check time (or warn when
263
+ // it can't).
264
+ const { mode: saltMode, salt } = (0, salt_1.resolveSalt)(cwd);
265
+ // Build the producer context once. Every analyzer's gather runs
266
+ // here (or earlier inside readOrBuildAnalysisResult) so producers
267
+ // can be pure or near-pure consumers — adding a new producer
268
+ // means extending this context with one more input, never
269
+ // adding another producer-specific block in this function.
270
+ const testGapsReport = await (0, tests_1.analyzeTestGaps)(cwd, { verbose: !!options.verbose });
271
+ const hygieneMarkers = (0, gather_1.gatherHygieneMarkers)(cwd);
272
+ const gitleaksOutcome = (0, gitleaks_1.gatherGitleaksResult)(cwd);
273
+ const rawSecrets = gitleaksOutcome.kind === 'success' ? gitleaksOutcome.rawSecrets : [];
274
+ const producerCtx = {
275
+ cwd,
276
+ commitSha: repoState.commitSha,
277
+ salt,
278
+ analysisResult,
279
+ testGapsReport,
280
+ hygiene: hygieneMarkers,
281
+ rawSecrets,
282
+ };
283
+ // Dispatch through the canonical producer registry (CLAUDE.md
284
+ // Rule 10). Adding a new identity kind means registering a
285
+ // producer in `src/baseline/producers/index.ts` — never an edit
286
+ // here.
287
+ const findings = (0, producers_1.runProducers)(producerCtx, producers_1.PRODUCERS);
288
+ const toolNames = new Set();
289
+ if (aggregate.provenance.secrets.tool)
290
+ toolNames.add(aggregate.provenance.secrets.tool);
291
+ if (aggregate.provenance.codePatterns.tool)
292
+ toolNames.add(aggregate.provenance.codePatterns.tool);
293
+ if (aggregate.provenance.depVulns.tool)
294
+ toolNames.add(aggregate.provenance.depVulns.tool);
295
+ if (aggregate.provenance.tlsBypass.ran)
296
+ toolNames.add('tls-bypass-registry');
297
+ const tools = buildToolsMap([...toolNames].sort(), cwd);
298
+ const analysisMeta = {
299
+ ...buildAnalysisMeta(cwd),
300
+ toolchainHash: hashContent(JSON.stringify(tools)),
301
+ };
302
+ return {
303
+ findings,
304
+ aggregate,
305
+ repoState,
306
+ saltMode,
307
+ tools,
308
+ analysisMeta,
309
+ producerCtx,
310
+ };
311
+ }
312
+ /**
313
+ * Run the baseline-create pipeline. Pure-orchestrator: gather the
314
+ * current scan via the shared step, then write it to disk under the
315
+ * given name.
316
+ */
317
+ async function createBaseline(options) {
318
+ const cwd = path.resolve(options.cwd);
319
+ const name = options.name ?? baseline_file_1.DEFAULT_BASELINE_NAME;
320
+ const filePath = (0, baseline_file_1.pathForBaseline)(cwd, name);
321
+ if (!options.force && fs.existsSync(filePath)) {
322
+ throw new Error(`baseline already exists at ${filePath}. Pass force: true to overwrite, ` +
323
+ `or use a different --name to keep both.`);
324
+ }
325
+ const scan = await gatherCurrentScan({ cwd, verbose: options.verbose });
326
+ const file = {
327
+ schemaVersion: baseline_file_1.BASELINE_SCHEMA_VERSION,
328
+ name,
329
+ createdAt: new Date().toISOString(),
330
+ repo: scan.repoState,
331
+ analysis: scan.analysisMeta,
332
+ tools: scan.tools,
333
+ saltMode: scan.saltMode,
334
+ findings: scan.findings,
335
+ };
336
+ (0, baseline_file_1.writeBaselineFile)(filePath, file);
337
+ return { path: filePath, file };
338
+ }
339
+ //# sourceMappingURL=create.js.map