@vyuhlabs/dxkit 2.4.8 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/README.md +360 -439
  3. package/dist/analyzers/security/aggregator.d.ts.map +1 -1
  4. package/dist/analyzers/security/aggregator.js +4 -46
  5. package/dist/analyzers/security/aggregator.js.map +1 -1
  6. package/dist/analyzers/tools/fingerprint.d.ts +91 -26
  7. package/dist/analyzers/tools/fingerprint.d.ts.map +1 -1
  8. package/dist/analyzers/tools/fingerprint.js +111 -22
  9. package/dist/analyzers/tools/fingerprint.js.map +1 -1
  10. package/dist/analyzers/tools/generic.d.ts.map +1 -1
  11. package/dist/analyzers/tools/generic.js +6 -1
  12. package/dist/analyzers/tools/generic.js.map +1 -1
  13. package/dist/analyzers/tools/gitleaks.d.ts +24 -1
  14. package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
  15. package/dist/analyzers/tools/gitleaks.js +20 -11
  16. package/dist/analyzers/tools/gitleaks.js.map +1 -1
  17. package/dist/analyzers/types.d.ts +6 -4
  18. package/dist/analyzers/types.d.ts.map +1 -1
  19. package/dist/baseline/baseline-file.d.ts +104 -0
  20. package/dist/baseline/baseline-file.d.ts.map +1 -0
  21. package/dist/baseline/baseline-file.js +110 -0
  22. package/dist/baseline/baseline-file.js.map +1 -0
  23. package/dist/baseline/check-renderers.d.ts +108 -0
  24. package/dist/baseline/check-renderers.d.ts.map +1 -0
  25. package/dist/baseline/check-renderers.js +379 -0
  26. package/dist/baseline/check-renderers.js.map +1 -0
  27. package/dist/baseline/check.d.ts +127 -0
  28. package/dist/baseline/check.d.ts.map +1 -0
  29. package/dist/baseline/check.js +462 -0
  30. package/dist/baseline/check.js.map +1 -0
  31. package/dist/baseline/content-hash.d.ts +83 -0
  32. package/dist/baseline/content-hash.d.ts.map +1 -0
  33. package/dist/baseline/content-hash.js +131 -0
  34. package/dist/baseline/content-hash.js.map +1 -0
  35. package/dist/baseline/create.d.ts +96 -0
  36. package/dist/baseline/create.d.ts.map +1 -0
  37. package/dist/baseline/create.js +339 -0
  38. package/dist/baseline/create.js.map +1 -0
  39. package/dist/baseline/entry-to-located.d.ts +35 -0
  40. package/dist/baseline/entry-to-located.d.ts.map +1 -0
  41. package/dist/baseline/entry-to-located.js +72 -0
  42. package/dist/baseline/entry-to-located.js.map +1 -0
  43. package/dist/baseline/finding-identity.d.ts +47 -0
  44. package/dist/baseline/finding-identity.d.ts.map +1 -0
  45. package/dist/baseline/finding-identity.js +292 -0
  46. package/dist/baseline/finding-identity.js.map +1 -0
  47. package/dist/baseline/git-aware-match.d.ts +146 -0
  48. package/dist/baseline/git-aware-match.d.ts.map +1 -0
  49. package/dist/baseline/git-aware-match.js +439 -0
  50. package/dist/baseline/git-aware-match.js.map +1 -0
  51. package/dist/baseline/policy.d.ts +171 -0
  52. package/dist/baseline/policy.d.ts.map +1 -0
  53. package/dist/baseline/policy.js +206 -0
  54. package/dist/baseline/policy.js.map +1 -0
  55. package/dist/baseline/producers/health.d.ts +30 -0
  56. package/dist/baseline/producers/health.d.ts.map +1 -0
  57. package/dist/baseline/producers/health.js +42 -0
  58. package/dist/baseline/producers/health.js.map +1 -0
  59. package/dist/baseline/producers/index.d.ts +164 -0
  60. package/dist/baseline/producers/index.d.ts.map +1 -0
  61. package/dist/baseline/producers/index.js +200 -0
  62. package/dist/baseline/producers/index.js.map +1 -0
  63. package/dist/baseline/producers/licenses.d.ts +23 -0
  64. package/dist/baseline/producers/licenses.d.ts.map +1 -0
  65. package/dist/baseline/producers/licenses.js +46 -0
  66. package/dist/baseline/producers/licenses.js.map +1 -0
  67. package/dist/baseline/producers/quality.d.ts +39 -0
  68. package/dist/baseline/producers/quality.d.ts.map +1 -0
  69. package/dist/baseline/producers/quality.js +84 -0
  70. package/dist/baseline/producers/quality.js.map +1 -0
  71. package/dist/baseline/producers/secret-hmac.d.ts +45 -0
  72. package/dist/baseline/producers/secret-hmac.d.ts.map +1 -0
  73. package/dist/baseline/producers/secret-hmac.js +70 -0
  74. package/dist/baseline/producers/secret-hmac.js.map +1 -0
  75. package/dist/baseline/producers/security.d.ts +59 -0
  76. package/dist/baseline/producers/security.d.ts.map +1 -0
  77. package/dist/baseline/producers/security.js +135 -0
  78. package/dist/baseline/producers/security.js.map +1 -0
  79. package/dist/baseline/producers/tests.d.ts +36 -0
  80. package/dist/baseline/producers/tests.d.ts.map +1 -0
  81. package/dist/baseline/producers/tests.js +69 -0
  82. package/dist/baseline/producers/tests.js.map +1 -0
  83. package/dist/baseline/salt.d.ts +45 -0
  84. package/dist/baseline/salt.d.ts.map +1 -0
  85. package/dist/baseline/salt.js +113 -0
  86. package/dist/baseline/salt.js.map +1 -0
  87. package/dist/baseline/show.d.ts +79 -0
  88. package/dist/baseline/show.d.ts.map +1 -0
  89. package/dist/baseline/show.js +233 -0
  90. package/dist/baseline/show.js.map +1 -0
  91. package/dist/baseline/types.d.ts +482 -0
  92. package/dist/baseline/types.d.ts.map +1 -0
  93. package/dist/baseline/types.js +53 -0
  94. package/dist/baseline/types.js.map +1 -0
  95. package/dist/cli.d.ts.map +1 -1
  96. package/dist/cli.js +360 -81
  97. package/dist/cli.js.map +1 -1
  98. package/dist/codebase-scanner.d.ts.map +1 -1
  99. package/dist/codebase-scanner.js +0 -1
  100. package/dist/codebase-scanner.js.map +1 -1
  101. package/dist/constants.d.ts.map +1 -1
  102. package/dist/constants.js +0 -4
  103. package/dist/constants.js.map +1 -1
  104. package/dist/doctor.d.ts.map +1 -1
  105. package/dist/doctor.js +22 -25
  106. package/dist/doctor.js.map +1 -1
  107. package/dist/fail-on.d.ts +84 -0
  108. package/dist/fail-on.d.ts.map +1 -0
  109. package/dist/fail-on.js +128 -0
  110. package/dist/fail-on.js.map +1 -0
  111. package/dist/generator.d.ts.map +1 -1
  112. package/dist/generator.js +2 -141
  113. package/dist/generator.js.map +1 -1
  114. package/dist/languages/csharp.d.ts.map +1 -1
  115. package/dist/languages/csharp.js +0 -9
  116. package/dist/languages/csharp.js.map +1 -1
  117. package/dist/languages/go.d.ts.map +1 -1
  118. package/dist/languages/go.js +0 -15
  119. package/dist/languages/go.js.map +1 -1
  120. package/dist/languages/index.d.ts +1 -1
  121. package/dist/languages/index.d.ts.map +1 -1
  122. package/dist/languages/index.js.map +1 -1
  123. package/dist/languages/java.d.ts.map +1 -1
  124. package/dist/languages/java.js +0 -6
  125. package/dist/languages/java.js.map +1 -1
  126. package/dist/languages/kotlin.d.ts.map +1 -1
  127. package/dist/languages/kotlin.js +0 -11
  128. package/dist/languages/kotlin.js.map +1 -1
  129. package/dist/languages/python.d.ts.map +1 -1
  130. package/dist/languages/python.js +0 -15
  131. package/dist/languages/python.js.map +1 -1
  132. package/dist/languages/ruby.d.ts.map +1 -1
  133. package/dist/languages/ruby.js +0 -6
  134. package/dist/languages/ruby.js.map +1 -1
  135. package/dist/languages/rust.d.ts.map +1 -1
  136. package/dist/languages/rust.js +0 -4
  137. package/dist/languages/rust.js.map +1 -1
  138. package/dist/languages/types.d.ts +2 -28
  139. package/dist/languages/types.d.ts.map +1 -1
  140. package/dist/languages/typescript.d.ts.map +1 -1
  141. package/dist/languages/typescript.js +26 -4
  142. package/dist/languages/typescript.js.map +1 -1
  143. package/dist/lib.d.ts +2 -3
  144. package/dist/lib.d.ts.map +1 -1
  145. package/dist/lib.js +3 -6
  146. package/dist/lib.js.map +1 -1
  147. package/dist/prompts.d.ts.map +1 -1
  148. package/dist/prompts.js +0 -10
  149. package/dist/prompts.js.map +1 -1
  150. package/dist/report-schema.d.ts +42 -0
  151. package/dist/report-schema.d.ts.map +1 -0
  152. package/dist/report-schema.js +54 -0
  153. package/dist/report-schema.js.map +1 -0
  154. package/dist/ship-installers.d.ts +106 -0
  155. package/dist/ship-installers.d.ts.map +1 -0
  156. package/dist/ship-installers.js +415 -0
  157. package/dist/ship-installers.js.map +1 -0
  158. package/dist/types.d.ts +0 -4
  159. package/dist/types.d.ts.map +1 -1
  160. package/dist/update.d.ts.map +1 -1
  161. package/dist/update.js +0 -4
  162. package/dist/update.js.map +1 -1
  163. package/package.json +17 -11
  164. package/templates/.claude/agents/onboarding.md +5 -4
  165. package/templates/.claude/agents-available/codebase-explorer.md +1 -1
  166. package/templates/.claude/agents-available/debugger.md +2 -2
  167. package/templates/.claude/agents-available/health-auditor.md +2 -2
  168. package/templates/.claude/commands/doctor.md +20 -12
  169. package/templates/.claude/skills/build/SKILL.md.template +22 -30
  170. package/templates/.claude/skills/deploy/SKILL.md.template +5 -25
  171. package/templates/.claude/skills/doctor/SKILL.md +24 -47
  172. package/templates/.claude/skills/gcloud/SKILL.md +5 -5
  173. package/templates/.claude/skills/learned/SKILL.md +1 -1
  174. package/templates/.claude/skills/pulumi/SKILL.md +2 -2
  175. package/templates/.claude/skills/quality/SKILL.md.template +4 -23
  176. package/templates/.claude/skills/review/SKILL.md.template +4 -3
  177. package/templates/.claude/skills/scaffold/SKILL.md.template +5 -15
  178. package/templates/.claude/skills/secrets/SKILL.md +20 -21
  179. package/templates/.claude/skills/session/SKILL.md +20 -31
  180. package/templates/.claude/skills/test/SKILL.md.template +1 -7
  181. package/templates/.devcontainer/devcontainer.json +81 -0
  182. package/templates/.devcontainer/install-agent-clis.sh +42 -0
  183. package/templates/.devcontainer/post-create.sh +67 -0
  184. package/templates/.githooks/pre-commit +55 -0
  185. package/templates/.githooks/pre-push +63 -0
  186. package/templates/.github/workflows/dxkit-baseline-refresh.yml +78 -0
  187. package/templates/.github/workflows/dxkit-guardrails.yml +98 -0
  188. package/templates/CLAUDE.md.template +62 -196
  189. package/dist/project-yaml.d.ts +0 -13
  190. package/dist/project-yaml.d.ts.map +0 -1
  191. package/dist/project-yaml.js +0 -188
  192. package/dist/project-yaml.js.map +0 -1
  193. package/templates/.ai/README.md +0 -117
  194. package/templates/.ai/prompts/execution-prompt.md +0 -9
  195. package/templates/.ai/prompts/planning-prompt.md +0 -18
  196. package/templates/.ai/prompts/session-end-template.md +0 -182
  197. package/templates/.ai/prompts/session-end.md +0 -132
  198. package/templates/.ai/prompts/session-start.md +0 -109
  199. package/templates/.ai/prompts/step-by-step.md +0 -113
  200. package/templates/.ai/sessions/.gitkeep +0 -0
  201. package/templates/.claude/commands/setup-pr-review.md +0 -72
  202. package/templates/.devcontainer/Dockerfile.dev.template +0 -89
  203. package/templates/.devcontainer/devcontainer.json.template +0 -184
  204. package/templates/.devcontainer/docker-compose.yml.template +0 -105
  205. package/templates/.devcontainer/init-scripts/01-init.sql.template +0 -12
  206. package/templates/.devcontainer/post-create.sh.template +0 -298
  207. package/templates/.github/workflows/ci.yml.template +0 -399
  208. package/templates/.github/workflows/quality.yml.template +0 -376
  209. package/templates/.pre-commit-config.yaml.template +0 -106
  210. package/templates/.project/config/edit_config.py +0 -275
  211. package/templates/.project/config/project_config.py +0 -894
  212. package/templates/.project/scripts/codegen/generate-all.sh +0 -20
  213. package/templates/.project/scripts/codegen/validate-all.sh +0 -17
  214. package/templates/.project/scripts/docs/generate-all.sh +0 -30
  215. package/templates/.project/scripts/docs/serve.sh +0 -20
  216. package/templates/.project/scripts/quality/fix-all.sh +0 -138
  217. package/templates/.project/scripts/quality/lint-go.sh +0 -34
  218. package/templates/.project/scripts/quality/lint-python.sh +0 -54
  219. package/templates/.project/scripts/quality/run-all.sh +0 -497
  220. package/templates/.project/scripts/session/commit.sh +0 -70
  221. package/templates/.project/scripts/session/create-pr.sh +0 -165
  222. package/templates/.project/scripts/session/end.sh +0 -207
  223. package/templates/.project/scripts/session/start.sh +0 -233
  224. package/templates/.project/scripts/setup/doctor.sh +0 -404
  225. package/templates/.project/scripts/setup/interactive-setup.sh +0 -585
  226. package/templates/.project/scripts/sync/sync-template.sh +0 -328
  227. package/templates/.project/scripts/test/run-all.sh +0 -179
  228. package/templates/.project/scripts/test/run-quick.sh +0 -25
  229. package/templates/Makefile +0 -514
  230. package/templates/config/versions.yaml +0 -57
  231. package/templates/configs/go/.golangci.yml.template +0 -172
  232. package/templates/configs/go/go.mod.template +0 -15
  233. package/templates/configs/java/README.md +0 -6
  234. package/templates/configs/kotlin/README.md +0 -6
  235. package/templates/configs/node/package.json.template +0 -67
  236. package/templates/configs/node/tsconfig.json.template +0 -53
  237. package/templates/configs/python/pyproject.toml.template +0 -92
  238. package/templates/configs/python/pytest.ini.template +0 -64
  239. package/templates/configs/python/ruff.toml.template +0 -79
  240. package/templates/configs/ruby/README.md +0 -6
  241. package/templates/configs/rust/Cargo.toml.template +0 -51
  242. package/templates/configs/shared/.editorconfig +0 -67
  243. package/templates/scripts/validate-templates.sh +0 -449
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ /**
3
+ * Convert a stored `BaselineEntry` into the `LocatedIdentity` shape
4
+ * the git-aware matcher consumes. Pure function — no I/O.
5
+ *
6
+ * The matcher's location-pair pass keys off `(file, rule, line)` and
7
+ * its content-hash pass keys off `(rule, contentHash)`. Both passes
8
+ * pair on the rule string verbatim, so the converter MUST normalize
9
+ * the rule across tool boundaries — otherwise a finding reported by
10
+ * tool A in run 1 and tool B in run 2 would silently fail to pair
11
+ * (the identity hashes would agree via the canonical-rule mapping,
12
+ * but the matcher's earlier passes would have already missed them).
13
+ *
14
+ * For hygiene findings the marker acts as the rule discriminator —
15
+ * the identity hash partitions occurrences by marker text, so the
16
+ * location-pair pass must too. The canonical-rule registry doesn't
17
+ * apply to hygiene markers; the marker IS the canonical name.
18
+ *
19
+ * Kinds without file/line locators (dep-vuln, duplication,
20
+ * coverage-gap, license, test-gap, test-file-degradation, god-file,
21
+ * stale-file, large-file, secret-hmac) fall through to the matcher's
22
+ * multiset pass — they're paired by exact identity-hash equality,
23
+ * which the matcher already handles without any locator metadata.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.entryToLocated = entryToLocated;
27
+ exports.entriesToLocated = entriesToLocated;
28
+ const fingerprint_1 = require("../analyzers/tools/fingerprint");
29
+ /**
30
+ * Build a `LocatedIdentity` from one stored entry. The id is the
31
+ * already-computed identity hash; locator fields are populated for
32
+ * the kinds the matcher's location-pair / content-hash passes can
33
+ * use.
34
+ */
35
+ function entryToLocated(entry) {
36
+ switch (entry.kind) {
37
+ case 'secret':
38
+ case 'code':
39
+ case 'config':
40
+ return {
41
+ id: entry.id,
42
+ file: entry.file,
43
+ line: entry.line,
44
+ rule: (0, fingerprint_1.canonicalRuleFor)(entry.tool, entry.rule),
45
+ ...(entry.contentHash !== undefined ? { contentHash: entry.contentHash } : {}),
46
+ };
47
+ case 'hygiene':
48
+ return {
49
+ id: entry.id,
50
+ file: entry.file,
51
+ line: entry.line,
52
+ rule: entry.marker,
53
+ ...(entry.contentHash !== undefined ? { contentHash: entry.contentHash } : {}),
54
+ };
55
+ case 'dep-vuln':
56
+ case 'duplication':
57
+ case 'coverage-gap':
58
+ case 'license':
59
+ case 'test-gap':
60
+ case 'test-file-degradation':
61
+ case 'god-file':
62
+ case 'stale-file':
63
+ case 'large-file':
64
+ case 'secret-hmac':
65
+ return { id: entry.id };
66
+ }
67
+ }
68
+ /** Convenience: map an array of entries through `entryToLocated`. */
69
+ function entriesToLocated(entries) {
70
+ return entries.map(entryToLocated);
71
+ }
72
+ //# sourceMappingURL=entry-to-located.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-to-located.js","sourceRoot":"","sources":["../../src/baseline/entry-to-located.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;AAYH,wCAgCC;AAGD,4CAIC;AAjDD,gEAAkE;AAIlE;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,KAAoB;IACjD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,QAAQ,CAAC;QACd,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ;YACX,OAAO;gBACL,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,IAAA,8BAAgB,EAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC;gBAC9C,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/E,CAAC;QACJ,KAAK,SAAS;YACZ,OAAO;gBACL,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,KAAK,CAAC,MAAM;gBAClB,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/E,CAAC;QACJ,KAAK,UAAU,CAAC;QAChB,KAAK,aAAa,CAAC;QACnB,KAAK,cAAc,CAAC;QACpB,KAAK,SAAS,CAAC;QACf,KAAK,UAAU,CAAC;QAChB,KAAK,uBAAuB,CAAC;QAC7B,KAAK,UAAU,CAAC;QAChB,KAAK,YAAY,CAAC;QAClB,KAAK,YAAY,CAAC;QAClB,KAAK,aAAa;YAChB,OAAO,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,qEAAqE;AACrE,SAAgB,gBAAgB,CAC9B,OAAqC;IAErC,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Per-finding identity dispatch. Pure module — no I/O, deterministic
3
+ * output for deterministic input. Identity is the unit of comparison
4
+ * across runs: two findings with the same identity are "the same
5
+ * finding" for baseline / guardrail purposes.
6
+ *
7
+ * Two of the five identity schemes already live in `tools/fingerprint`
8
+ * (dep-vuln + code/secret/config). This module wraps them in the
9
+ * baseline's discriminated-union shape and adds the two new schemes
10
+ * (duplication, coverage-gap) so callers reach all five through a
11
+ * single dispatch.
12
+ */
13
+ import type { FindingId, IdentityInput, IdentitySchemeVersion, MatchResult } from './types';
14
+ /**
15
+ * Compute the durable identity for a finding. `version` defaults to
16
+ * `'v1'` — explicit so future scheme migrations can co-exist without
17
+ * silent identity drift.
18
+ *
19
+ * Identity is the SAME 16-char hex string format across all kinds, so
20
+ * a baseline can store identities in a single flat set without
21
+ * tracking which kind they came from. Mixing across kinds is safe:
22
+ * the input space for each scheme is disjoint (a dep-vuln tuple of
23
+ * `(package, version, id)` can never collide with a code-finding
24
+ * tuple of `(canonicalRule, file, lineWindow)` at SHA-1 strength).
25
+ */
26
+ export declare function identityFor(input: IdentityInput, version?: IdentitySchemeVersion): FindingId;
27
+ /**
28
+ * Multiset-aware identity diff — the lowest layer of baseline
29
+ * comparison. Pairs identities by occurrence count, not by presence:
30
+ * an identity appearing twice in prior and once in current produces
31
+ * one persisted pair and one removed pair (set-diff would have
32
+ * incorrectly collapsed those to a single persisted).
33
+ *
34
+ * For each shared identity:
35
+ * - the first `min(priorCount, currentCount)` occurrences pair as
36
+ * `persisted` with confidence 1.0 (exact byte equality).
37
+ * - excess occurrences in `current` produce `added` pairs.
38
+ * - excess occurrences in `prior` produce `removed` pairs.
39
+ *
40
+ * Output ordering: pairs grouped by identity, then by status. The
41
+ * flat-array views (`persisted`, `added`, `removed`) preserve
42
+ * occurrence multiplicity — duplicate ids appear N times when N
43
+ * occurrences were classified that way. Callers that want a
44
+ * deduplicated view should run them through a Set themselves.
45
+ */
46
+ export declare function matchAcrossRuns(prior: Iterable<FindingId>, current: Iterable<FindingId>): MatchResult;
47
+ //# sourceMappingURL=finding-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-identity.d.ts","sourceRoot":"","sources":["../../src/baseline/finding-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAcH,OAAO,KAAK,EACV,SAAS,EAET,aAAa,EACb,qBAAqB,EAGrB,WAAW,EAGZ,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,aAAa,EACpB,OAAO,GAAE,qBAA4B,GACpC,SAAS,CA4CX;AA4KD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,EAC1B,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,GAC3B,WAAW,CAyDb"}
@@ -0,0 +1,292 @@
1
+ "use strict";
2
+ /**
3
+ * Per-finding identity dispatch. Pure module — no I/O, deterministic
4
+ * output for deterministic input. Identity is the unit of comparison
5
+ * across runs: two findings with the same identity are "the same
6
+ * finding" for baseline / guardrail purposes.
7
+ *
8
+ * Two of the five identity schemes already live in `tools/fingerprint`
9
+ * (dep-vuln + code/secret/config). This module wraps them in the
10
+ * baseline's discriminated-union shape and adds the two new schemes
11
+ * (duplication, coverage-gap) so callers reach all five through a
12
+ * single dispatch.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.identityFor = identityFor;
16
+ exports.matchAcrossRuns = matchAcrossRuns;
17
+ const crypto_1 = require("crypto");
18
+ const fingerprint_1 = require("../analyzers/tools/fingerprint");
19
+ /**
20
+ * Compute the durable identity for a finding. `version` defaults to
21
+ * `'v1'` — explicit so future scheme migrations can co-exist without
22
+ * silent identity drift.
23
+ *
24
+ * Identity is the SAME 16-char hex string format across all kinds, so
25
+ * a baseline can store identities in a single flat set without
26
+ * tracking which kind they came from. Mixing across kinds is safe:
27
+ * the input space for each scheme is disjoint (a dep-vuln tuple of
28
+ * `(package, version, id)` can never collide with a code-finding
29
+ * tuple of `(canonicalRule, file, lineWindow)` at SHA-1 strength).
30
+ */
31
+ function identityFor(input, version = 'v1') {
32
+ if (version !== 'v1') {
33
+ throw new Error(`Unsupported identity-scheme version: ${version}`);
34
+ }
35
+ switch (input.kind) {
36
+ case 'secret':
37
+ case 'code':
38
+ case 'config': {
39
+ const canonicalRule = (0, fingerprint_1.canonicalRuleFor)(input.tool, input.rule);
40
+ return (0, fingerprint_1.computeCodeFingerprint)(canonicalRule, input.file, input.line);
41
+ }
42
+ case 'dep-vuln':
43
+ return (0, fingerprint_1.computeFingerprint)({
44
+ package: input.package,
45
+ installedVersion: input.installedVersion,
46
+ id: input.id,
47
+ });
48
+ case 'duplication':
49
+ return computeDuplicationIdentity(input.fileA, input.fileB, input.lines, input.startLineA, input.startLineB);
50
+ case 'coverage-gap':
51
+ return computeCoverageGapIdentity(input.file, input.symbol, input.lineRange);
52
+ case 'test-gap':
53
+ return computeTestGapIdentity(input.file, input.risk);
54
+ case 'hygiene':
55
+ return computeHygieneIdentity(input.file, input.line, input.marker);
56
+ case 'license':
57
+ return computeLicenseIdentity(input.package, input.version, input.licenseType);
58
+ case 'test-file-degradation':
59
+ return computeTestFileDegradationIdentity(input.file, input.status);
60
+ case 'god-file':
61
+ return computeGodFileIdentity(input.file);
62
+ case 'stale-file':
63
+ return computeStaleFileIdentity(input.file, input.suffix);
64
+ case 'large-file':
65
+ return computeLargeFileIdentity(input.file);
66
+ case 'secret-hmac':
67
+ return computeSecretHmacIdentity(input.tool, input.rule, input.hmac);
68
+ }
69
+ }
70
+ /**
71
+ * Symmetric-by-construction identity for a duplicate-block pair. The
72
+ * two `(file, startLine)` pairs are sorted lexicographically (first by
73
+ * file path, then by start line) before hashing so a clone reported as
74
+ * `(a:10, b:20)` and one reported as `(b:20, a:10)` produce the same
75
+ * identity.
76
+ *
77
+ * `lines` is included so refactoring one side of the pair (which
78
+ * shrinks or grows the block) reports a fresh identity — the right
79
+ * signal for a guardrail: "the duplicate moved or shrank, which
80
+ * deserves a look." `lines` is preferred over `tokens` because
81
+ * jscpd's JSON reporter does not populate `tokens` in practice
82
+ * (always emits 0), which would silently break the size-sensitivity
83
+ * property.
84
+ *
85
+ * Both start lines participate in identity so intra-file clones
86
+ * (`fileA === fileB`, multiple copies of the same block at different
87
+ * line positions inside one file) produce distinct identities.
88
+ * Without this, three intra-file clones in a single file would all
89
+ * collapse to one identity.
90
+ */
91
+ function computeDuplicationIdentity(fileA, fileB, lines, startLineA, startLineB) {
92
+ const pairs = [
93
+ [fileA, startLineA],
94
+ [fileB, startLineB],
95
+ ];
96
+ pairs.sort((x, y) => (x[0] < y[0] ? -1 : x[0] > y[0] ? 1 : x[1] - y[1]));
97
+ const input = `duplication\0v1\0${pairs[0][0]}\0${pairs[0][1]}\0${pairs[1][0]}\0${pairs[1][1]}\0${lines}`;
98
+ return (0, crypto_1.createHash)('sha1').update(input).digest('hex').slice(0, 16);
99
+ }
100
+ /**
101
+ * Identity for an uncovered-code gap. Prefers `(file, symbol)` when
102
+ * the gap-detection pipeline knew the symbol name — that's stable
103
+ * across refactors that move code within a file. Falls back to
104
+ * `(file, lineRange)` when only a line range is available.
105
+ *
106
+ * Producers MUST supply at least one of `symbol` or `lineRange`;
107
+ * supplying neither throws because the resulting identity would be
108
+ * `(file)` only, which collapses every uncovered region in a file
109
+ * into a single entry.
110
+ */
111
+ function computeCoverageGapIdentity(file, symbol, lineRange) {
112
+ if (!symbol && !lineRange) {
113
+ throw new Error(`coverage-gap identity requires either a symbol or a line range (file: ${file})`);
114
+ }
115
+ const discriminator = symbol ? `sym:${symbol}` : `range:${lineRange[0]}-${lineRange[1]}`;
116
+ const input = `coverage-gap\0v1\0${file}\0${discriminator}`;
117
+ return (0, crypto_1.createHash)('sha1').update(input).digest('hex').slice(0, 16);
118
+ }
119
+ /**
120
+ * Identity for a test-gap source file. Risk tier is part of identity:
121
+ * a file moving between tiers (CRITICAL → HIGH, or vice versa) is
122
+ * semantically a fresh finding — the prior tier's identity disappears,
123
+ * the new tier's identity arrives. Guardrails will fire on the
124
+ * net-new tier, which is the correct signal for regressions.
125
+ */
126
+ function computeTestGapIdentity(file, risk) {
127
+ const input = `test-gap\0v1\0${file}\0${risk}`;
128
+ return (0, crypto_1.createHash)('sha1').update(input).digest('hex').slice(0, 16);
129
+ }
130
+ /**
131
+ * Identity for a single hygiene-marker occurrence (TODO / FIXME /
132
+ * HACK / console-log / any-type). Line number is bucketed via the
133
+ * shared line-window so a reformat that shifts a TODO by one or two
134
+ * lines doesn't churn identity. Marker text is NOT included — the
135
+ * occurrence "is a TODO" regardless of what the comment body says.
136
+ */
137
+ function computeHygieneIdentity(file, line, marker) {
138
+ const input = `hygiene\0v1\0${marker}\0${file}\0${(0, fingerprint_1.lineWindowFor)(line)}`;
139
+ return (0, crypto_1.createHash)('sha1').update(input).digest('hex').slice(0, 16);
140
+ }
141
+ /**
142
+ * Identity for a package license attribution. Includes the license
143
+ * type so a re-licensing event on the same `(package, version)` pin
144
+ * registers as a fresh finding — compliance teams want to be
145
+ * notified when a transitive dep switches from MIT to GPL even
146
+ * without a version bump.
147
+ */
148
+ function computeLicenseIdentity(packageName, version, licenseType) {
149
+ const input = `license\0v1\0${packageName}\0${version}\0${licenseType}`;
150
+ return (0, crypto_1.createHash)('sha1').update(input).digest('hex').slice(0, 16);
151
+ }
152
+ /**
153
+ * Identity for a degraded test file. Degradation status is part of
154
+ * identity so transitions between states register as fresh findings
155
+ * (e.g. an `'empty'` test body that becomes `'commented-out'` later
156
+ * is semantically a different problem worth a guardrail signal).
157
+ */
158
+ function computeTestFileDegradationIdentity(file, status) {
159
+ const input = `test-file-degradation\0v1\0${file}\0${status}`;
160
+ return (0, crypto_1.createHash)('sha1').update(input).digest('hex').slice(0, 16);
161
+ }
162
+ /**
163
+ * Identity for a "god file" complexity offender. The fact that this
164
+ * specific file is a top offender is the durable signal — when a
165
+ * different file becomes the offender, identity changes appropriately
166
+ * because the producer emits a fresh finding pointing at the new
167
+ * path.
168
+ */
169
+ function computeGodFileIdentity(file) {
170
+ const input = `god-file\0v1\0${file}`;
171
+ return (0, crypto_1.createHash)('sha1').update(input).digest('hex').slice(0, 16);
172
+ }
173
+ /**
174
+ * Identity for a stale on-disk artifact tracked in git. Suffix is in
175
+ * identity so a path that's flagged for both a `.swp` and a `.bak`
176
+ * (rare but possible if a developer leaves both kinds of leftovers)
177
+ * surfaces as two distinct findings rather than one collapsed entry.
178
+ * The producer lowercases the suffix and strips the dot before
179
+ * calling.
180
+ */
181
+ function computeStaleFileIdentity(file, suffix) {
182
+ const input = `stale-file\0v1\0${file}\0${suffix}`;
183
+ return (0, crypto_1.createHash)('sha1').update(input).digest('hex').slice(0, 16);
184
+ }
185
+ /**
186
+ * Identity for a source file flagged as exceeding the large-file
187
+ * line-count threshold. Per-file, no further discriminator: the
188
+ * binary "this file is too big" signal is what guardrails act on.
189
+ * Discrete crossings of the threshold add or remove the identity.
190
+ */
191
+ function computeLargeFileIdentity(file) {
192
+ const input = `large-file\0v1\0${file}`;
193
+ return (0, crypto_1.createHash)('sha1').update(input).digest('hex').slice(0, 16);
194
+ }
195
+ /**
196
+ * Identity for a secret keyed on its HMAC rather than its file
197
+ * position. Pairs with `SecretIdentityInput` (the location-based
198
+ * scheme) — the same underlying secret produces both identities,
199
+ * and the matcher can use either to recognize the finding across
200
+ * runs.
201
+ *
202
+ * Canonical-rule mapping applies so two scanners detecting the same
203
+ * secret class (e.g., gitleaks and a hypothetical second tool) emit
204
+ * the same identity bytes when their HMACs match.
205
+ */
206
+ function computeSecretHmacIdentity(tool, rule, hmac) {
207
+ const canonicalRule = (0, fingerprint_1.canonicalRuleFor)(tool, rule);
208
+ const input = `secret-hmac\0v1\0${canonicalRule}\0${hmac}`;
209
+ return (0, crypto_1.createHash)('sha1').update(input).digest('hex').slice(0, 16);
210
+ }
211
+ /**
212
+ * Multiset-aware identity diff — the lowest layer of baseline
213
+ * comparison. Pairs identities by occurrence count, not by presence:
214
+ * an identity appearing twice in prior and once in current produces
215
+ * one persisted pair and one removed pair (set-diff would have
216
+ * incorrectly collapsed those to a single persisted).
217
+ *
218
+ * For each shared identity:
219
+ * - the first `min(priorCount, currentCount)` occurrences pair as
220
+ * `persisted` with confidence 1.0 (exact byte equality).
221
+ * - excess occurrences in `current` produce `added` pairs.
222
+ * - excess occurrences in `prior` produce `removed` pairs.
223
+ *
224
+ * Output ordering: pairs grouped by identity, then by status. The
225
+ * flat-array views (`persisted`, `added`, `removed`) preserve
226
+ * occurrence multiplicity — duplicate ids appear N times when N
227
+ * occurrences were classified that way. Callers that want a
228
+ * deduplicated view should run them through a Set themselves.
229
+ */
230
+ function matchAcrossRuns(prior, current) {
231
+ const priorCounts = countMultiset(prior);
232
+ const currentCounts = countMultiset(current);
233
+ const allIds = new Set([...priorCounts.keys(), ...currentCounts.keys()]);
234
+ const pairs = [];
235
+ const persisted = [];
236
+ const added = [];
237
+ const removed = [];
238
+ const exactReason = {
239
+ code: 'exact-id',
240
+ detail: 'identity fingerprint matched byte-for-byte across runs',
241
+ };
242
+ const newReason = {
243
+ code: 'no-prior-match',
244
+ detail: 'identity fingerprint not present in the baseline',
245
+ };
246
+ const goneReason = {
247
+ code: 'no-current-match',
248
+ detail: 'identity fingerprint not present in the current scan',
249
+ };
250
+ for (const id of allIds) {
251
+ const p = priorCounts.get(id) ?? 0;
252
+ const c = currentCounts.get(id) ?? 0;
253
+ const matched = Math.min(p, c);
254
+ for (let i = 0; i < matched; i++) {
255
+ pairs.push({
256
+ priorId: id,
257
+ currentId: id,
258
+ status: 'persisted',
259
+ confidence: 1.0,
260
+ reasons: [exactReason],
261
+ });
262
+ persisted.push(id);
263
+ }
264
+ for (let i = 0; i < c - matched; i++) {
265
+ pairs.push({
266
+ currentId: id,
267
+ status: 'added',
268
+ confidence: 1.0,
269
+ reasons: [newReason],
270
+ });
271
+ added.push(id);
272
+ }
273
+ for (let i = 0; i < p - matched; i++) {
274
+ pairs.push({
275
+ priorId: id,
276
+ status: 'removed',
277
+ confidence: 1.0,
278
+ reasons: [goneReason],
279
+ });
280
+ removed.push(id);
281
+ }
282
+ }
283
+ return { pairs, persisted, added, removed, gitAware: false };
284
+ }
285
+ function countMultiset(items) {
286
+ const counts = new Map();
287
+ for (const id of items) {
288
+ counts.set(id, (counts.get(id) ?? 0) + 1);
289
+ }
290
+ return counts;
291
+ }
292
+ //# sourceMappingURL=finding-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-identity.js","sourceRoot":"","sources":["../../src/baseline/finding-identity.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;AAsCH,kCA+CC;AA+LD,0CA4DC;AA9UD,mCAAoC;AACpC,gEAKwC;AAkBxC;;;;;;;;;;;GAWG;AACH,SAAgB,WAAW,CACzB,KAAoB,EACpB,UAAiC,IAAI;IAErC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,wCAAwC,OAAO,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,QAAQ,CAAC;QACd,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,aAAa,GAAG,IAAA,8BAAgB,EAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/D,OAAO,IAAA,oCAAsB,EAAC,aAAa,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACvE,CAAC;QACD,KAAK,UAAU;YACb,OAAO,IAAA,gCAAkB,EAAC;gBACxB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;gBACxC,EAAE,EAAE,KAAK,CAAC,EAAE;aACb,CAAC,CAAC;QACL,KAAK,aAAa;YAChB,OAAO,0BAA0B,CAC/B,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,UAAU,CACjB,CAAC;QACJ,KAAK,cAAc;YACjB,OAAO,0BAA0B,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QAC/E,KAAK,UAAU;YACb,OAAO,sBAAsB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACxD,KAAK,SAAS;YACZ,OAAO,sBAAsB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACtE,KAAK,SAAS;YACZ,OAAO,sBAAsB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;QACjF,KAAK,uBAAuB;YAC1B,OAAO,kCAAkC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACtE,KAAK,UAAU;YACb,OAAO,sBAAsB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5C,KAAK,YAAY;YACf,OAAO,wBAAwB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC5D,KAAK,YAAY;YACf,OAAO,wBAAwB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9C,KAAK,aAAa;YAChB,OAAO,yBAAyB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAS,0BAA0B,CACjC,KAAa,EACb,KAAa,EACb,KAAa,EACb,UAAkB,EAClB,UAAkB;IAElB,MAAM,KAAK,GAA4B;QACrC,CAAC,KAAK,EAAE,UAAU,CAAC;QACnB,CAAC,KAAK,EAAE,UAAU,CAAC;KACpB,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,oBAAoB,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;IAC1G,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,0BAA0B,CACjC,IAAY,EACZ,MAA0B,EAC1B,SAAgD;IAEhD,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,yEAAyE,IAAI,GAAG,CACjF,CAAC;IACJ,CAAC;IACD,MAAM,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,SAAU,CAAC,CAAC,CAAC,IAAI,SAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3F,MAAM,KAAK,GAAG,qBAAqB,IAAI,KAAK,aAAa,EAAE,CAAC;IAC5D,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,IAAY,EAAE,IAAiB;IAC7D,MAAM,KAAK,GAAG,iBAAiB,IAAI,KAAK,IAAI,EAAE,CAAC;IAC/C,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,IAAY,EAAE,IAAY,EAAE,MAAqB;IAC/E,MAAM,KAAK,GAAG,gBAAgB,MAAM,KAAK,IAAI,KAAK,IAAA,2BAAa,EAAC,IAAI,CAAC,EAAE,CAAC;IACxE,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAC7B,WAAmB,EACnB,OAAe,EACf,WAAmB;IAEnB,MAAM,KAAK,GAAG,gBAAgB,WAAW,KAAK,OAAO,KAAK,WAAW,EAAE,CAAC;IACxE,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;GAKG;AACH,SAAS,kCAAkC,CACzC,IAAY,EACZ,MAAiC;IAEjC,MAAM,KAAK,GAAG,8BAA8B,IAAI,KAAK,MAAM,EAAE,CAAC;IAC9D,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAAC,IAAY;IAC1C,MAAM,KAAK,GAAG,iBAAiB,IAAI,EAAE,CAAC;IACtC,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,wBAAwB,CAAC,IAAY,EAAE,MAAc;IAC5D,MAAM,KAAK,GAAG,mBAAmB,IAAI,KAAK,MAAM,EAAE,CAAC;IACnD,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;GAKG;AACH,SAAS,wBAAwB,CAAC,IAAY;IAC5C,MAAM,KAAK,GAAG,mBAAmB,IAAI,EAAE,CAAC;IACxC,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,yBAAyB,CAAC,IAAY,EAAE,IAAY,EAAE,IAAY;IACzE,MAAM,aAAa,GAAG,IAAA,8BAAgB,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,oBAAoB,aAAa,KAAK,IAAI,EAAE,CAAC;IAC3D,OAAO,IAAA,mBAAU,EAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,eAAe,CAC7B,KAA0B,EAC1B,OAA4B;IAE5B,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,aAAa,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAY,CAAC,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAEpF,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAgB,EAAE,CAAC;IAClC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAgB,EAAE,CAAC;IAChC,MAAM,WAAW,GAAgB;QAC/B,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,wDAAwD;KACjE,CAAC;IACF,MAAM,SAAS,GAAgB;QAC7B,IAAI,EAAE,gBAAgB;QACtB,MAAM,EAAE,kDAAkD;KAC3D,CAAC;IACF,MAAM,UAAU,GAAgB;QAC9B,IAAI,EAAE,kBAAkB;QACxB,MAAM,EAAE,sDAAsD;KAC/D,CAAC;IAEF,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,CAAC;gBACT,OAAO,EAAE,EAAE;gBACX,SAAS,EAAE,EAAE;gBACb,MAAM,EAAE,WAAW;gBACnB,UAAU,EAAE,GAAG;gBACf,OAAO,EAAE,CAAC,WAAW,CAAC;aACvB,CAAC,CAAC;YACH,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;QACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC;gBACT,SAAS,EAAE,EAAE;gBACb,MAAM,EAAE,OAAO;gBACf,UAAU,EAAE,GAAG;gBACf,OAAO,EAAE,CAAC,SAAS,CAAC;aACrB,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC;gBACT,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,SAAS;gBACjB,UAAU,EAAE,GAAG;gBACf,OAAO,EAAE,CAAC,UAAU,CAAC;aACtB,CAAC,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC/D,CAAC;AAED,SAAS,aAAa,CAAC,KAA0B;IAC/C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC5C,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Git-aware match — pairs prior-run identities with current-run
3
+ * identities through the lens of `git diff baseSha headSha`.
4
+ *
5
+ * The line-bucket identity scheme used by code/secret/config/hygiene
6
+ * findings tolerates ±2 lines of vertical drift. Anything past that
7
+ * appears to a naive set-diff as "removed + added" even though
8
+ * semantically the finding hasn't changed — it just moved with the
9
+ * surrounding code. This module closes the gap.
10
+ *
11
+ * Algorithm:
12
+ *
13
+ * 1. Exact identity match — every finding present in both runs
14
+ * under the same fingerprint is `persisted` immediately.
15
+ *
16
+ * 2. For each finding in the `removed` set that carries a file +
17
+ * line locator: ask git to map its base-line through the diff
18
+ * to the corresponding head-line. If the `added` set contains
19
+ * a finding at the same `(file, rule, mappedLine)`, the two
20
+ * represent the same underlying issue moved by the diff —
21
+ * move both to `persisted`.
22
+ *
23
+ * 3. Whatever remains in `added` and `removed` is genuinely new
24
+ * or genuinely gone.
25
+ *
26
+ * Fallback: when git history is unavailable (no `.git`, baseSha not
27
+ * reachable, file deleted, etc.) the module degrades to plain
28
+ * set-diff matching — the same behavior `matchAcrossRuns` produces
29
+ * on its own. Callers in shallow-clone CI or non-git workflows get
30
+ * a working (if less precise) result.
31
+ *
32
+ * Known limitations (Sprint 0 v1):
33
+ * - File renames are not auto-tracked. A renamed file looks like
34
+ * "removed prior + added current"; future iterations will use
35
+ * `git log --follow` or `git diff -M` rename detection to close
36
+ * this gap.
37
+ * - Cross-file refactors (function extracted to a new file) are
38
+ * reported as removed-and-added.
39
+ * - When the line-bucket mapping fails on context edits (tool
40
+ * reports finding at a slightly different line in head than the
41
+ * diff predicts), we fall back to "unmatched." Sprint 0.x adds
42
+ * a content-hash fallback for this class.
43
+ */
44
+ import type { FindingId, MatchResult } from './types';
45
+ /**
46
+ * Per-finding identity plus the locator info needed to query git.
47
+ * Producers convert `BaselineEntry` (or any equivalent stored form)
48
+ * into this shape before calling `gitAwareMatch`.
49
+ *
50
+ * `file`, `line`, and `rule` are optional only because some finding
51
+ * kinds (dep-vuln, license) have no file-line locator. Those kinds
52
+ * are handled entirely by step-1 exact-identity match and skipped
53
+ * by the step-2 git fallback.
54
+ */
55
+ export interface LocatedIdentity {
56
+ readonly id: FindingId;
57
+ readonly file?: string;
58
+ readonly line?: number;
59
+ readonly rule?: string;
60
+ /** Optional content-hash for the finding's surrounding context.
61
+ * Producer (Phase 3 baseline-create) computes via
62
+ * `computeContentHash` and stamps on the entry. When present on
63
+ * both prior and current sides for a `(canonical-rule, hash)`
64
+ * pair, the matcher's content-hash pass uses it as a fallback
65
+ * after the git-aware location pass exhausts. Absent when the
66
+ * producer can't read the file (binary, deleted, missing). */
67
+ readonly contentHash?: string;
68
+ }
69
+ export interface GitAwareMatchOptions {
70
+ /** Working directory of the repository under check. */
71
+ readonly cwd: string;
72
+ /** Commit SHA the baseline was created against. The matcher
73
+ * requires this SHA to be reachable in `cwd`'s git history. */
74
+ readonly baseSha: string;
75
+ /** Commit SHA (or revision spec) to compare against. Defaults to
76
+ * `'HEAD'` — the current working-tree's last commit. */
77
+ readonly headSha?: string;
78
+ }
79
+ /**
80
+ * Map a 1-based line number in `baseSha`'s version of `file` to its
81
+ * corresponding 1-based line in `headSha`. Returns `null` when the
82
+ * line was deleted, the file was removed, or git couldn't produce a
83
+ * diff for any reason.
84
+ *
85
+ * Implementation runs `git diff --unified=0 baseSha headSha -- file`
86
+ * and walks the resulting `@@ -A,B +C,D @@` hunks. Pure-ish: the
87
+ * only impurity is the git subprocess; the parser is deterministic
88
+ * over its input.
89
+ */
90
+ export declare function mapLineThroughDiff(opts: {
91
+ readonly cwd: string;
92
+ readonly baseSha: string;
93
+ readonly headSha: string;
94
+ /** Path at `baseSha`. May differ from `newFile` if the caller
95
+ * resolved a rename. Pass-through compat: callers that don't
96
+ * track renames can use the same value for both. */
97
+ readonly oldFile?: string;
98
+ /** Path at `headSha`. */
99
+ readonly newFile?: string;
100
+ /** Legacy single-file form. When supplied, both `oldFile` and
101
+ * `newFile` default to this value. Kept for back-compat with
102
+ * call-sites that pre-date rename support. */
103
+ readonly file?: string;
104
+ readonly baseLine: number;
105
+ }): number | null;
106
+ /**
107
+ * Composite matcher. Three passes, decreasing in match strength:
108
+ *
109
+ * 1. Location-aware pairing (when git is available): for each
110
+ * line-anchored prior finding, map its base line to the
111
+ * corresponding head line via `git diff`, then look up a
112
+ * current finding at `(effectivePath, rule, mappedLine)`. The
113
+ * effective path is the prior path translated through the
114
+ * rename map; status is `'relocated'` when the path changed,
115
+ * `'persisted'` when it didn't.
116
+ * Lookups try the exact mapped line first (confidence 0.95),
117
+ * then a ±2 fuzz window (confidence 0.88).
118
+ *
119
+ * 1.5. Content-hash pairing (when both sides carry content
120
+ * hashes): match prior+current by `(canonicalRule,
121
+ * contentHash)`. Runs regardless of git reachability — the
122
+ * hash is file-content-derived and doesn't need git. Catches
123
+ * cases git can't (shallow clone, force-pushed baseline) and
124
+ * cases git misses (line-bucket boundary shifts where the
125
+ * surrounding context survived intact). Confidence 0.80 — the
126
+ * policy's per-severity thresholds naturally tune whether to
127
+ * trust this layer.
128
+ *
129
+ * 2. Multiset exact-identity diff over whatever remains. Catches:
130
+ * - findings without a file-line locator (dep-vuln, license,
131
+ * symbol-based coverage-gap, duplication)
132
+ * - line-anchored findings whose locations didn't survive
133
+ * the diff but whose fingerprints happen to coincide
134
+ * across runs
135
+ * - everything when git history is unreachable (`baseSha`
136
+ * missing) and pass 1 was skipped
137
+ *
138
+ * Why location-first: the line-bucket fingerprint scheme can produce
139
+ * spurious "persisted" matches when two findings of the same rule
140
+ * in the same file naturally shift into each other's buckets. Pass 1
141
+ * pairs them by real diff position, which is what a developer
142
+ * intuitively expects. Pass 1.5 catches the cases where pass 1 isn't
143
+ * available; pass 2 handles content-independent identity kinds.
144
+ */
145
+ export declare function gitAwareMatch(prior: ReadonlyArray<LocatedIdentity>, current: ReadonlyArray<LocatedIdentity>, opts: GitAwareMatchOptions): MatchResult;
146
+ //# sourceMappingURL=git-aware-match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-aware-match.d.ts","sourceRoot":"","sources":["../../src/baseline/git-aware-match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAIH,OAAO,KAAK,EAAE,SAAS,EAA0B,WAAW,EAAE,MAAM,SAAS,CAAC;AAqB9E;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;mEAM+D;IAC/D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;oEACgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;6DACyD;IACzD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;yDAEqD;IACrD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,yBAAyB;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;mDAE+C;IAC/C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAiChB;AAqCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,EACrC,OAAO,EAAE,aAAa,CAAC,eAAe,CAAC,EACvC,IAAI,EAAE,oBAAoB,GACzB,WAAW,CAoLb"}