@vyuhlabs/dxkit 2.4.8 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +235 -0
- package/README.md +360 -439
- package/dist/analyzers/security/aggregator.d.ts.map +1 -1
- package/dist/analyzers/security/aggregator.js +4 -46
- package/dist/analyzers/security/aggregator.js.map +1 -1
- package/dist/analyzers/tools/fingerprint.d.ts +91 -26
- package/dist/analyzers/tools/fingerprint.d.ts.map +1 -1
- package/dist/analyzers/tools/fingerprint.js +111 -22
- package/dist/analyzers/tools/fingerprint.js.map +1 -1
- package/dist/analyzers/tools/generic.d.ts.map +1 -1
- package/dist/analyzers/tools/generic.js +6 -1
- package/dist/analyzers/tools/generic.js.map +1 -1
- package/dist/analyzers/tools/gitleaks.d.ts +24 -1
- package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
- package/dist/analyzers/tools/gitleaks.js +20 -11
- package/dist/analyzers/tools/gitleaks.js.map +1 -1
- package/dist/analyzers/types.d.ts +6 -4
- package/dist/analyzers/types.d.ts.map +1 -1
- package/dist/baseline/baseline-file.d.ts +104 -0
- package/dist/baseline/baseline-file.d.ts.map +1 -0
- package/dist/baseline/baseline-file.js +110 -0
- package/dist/baseline/baseline-file.js.map +1 -0
- package/dist/baseline/check-renderers.d.ts +108 -0
- package/dist/baseline/check-renderers.d.ts.map +1 -0
- package/dist/baseline/check-renderers.js +379 -0
- package/dist/baseline/check-renderers.js.map +1 -0
- package/dist/baseline/check.d.ts +127 -0
- package/dist/baseline/check.d.ts.map +1 -0
- package/dist/baseline/check.js +462 -0
- package/dist/baseline/check.js.map +1 -0
- package/dist/baseline/content-hash.d.ts +83 -0
- package/dist/baseline/content-hash.d.ts.map +1 -0
- package/dist/baseline/content-hash.js +131 -0
- package/dist/baseline/content-hash.js.map +1 -0
- package/dist/baseline/create.d.ts +96 -0
- package/dist/baseline/create.d.ts.map +1 -0
- package/dist/baseline/create.js +339 -0
- package/dist/baseline/create.js.map +1 -0
- package/dist/baseline/entry-to-located.d.ts +35 -0
- package/dist/baseline/entry-to-located.d.ts.map +1 -0
- package/dist/baseline/entry-to-located.js +72 -0
- package/dist/baseline/entry-to-located.js.map +1 -0
- package/dist/baseline/finding-identity.d.ts +47 -0
- package/dist/baseline/finding-identity.d.ts.map +1 -0
- package/dist/baseline/finding-identity.js +292 -0
- package/dist/baseline/finding-identity.js.map +1 -0
- package/dist/baseline/git-aware-match.d.ts +146 -0
- package/dist/baseline/git-aware-match.d.ts.map +1 -0
- package/dist/baseline/git-aware-match.js +439 -0
- package/dist/baseline/git-aware-match.js.map +1 -0
- package/dist/baseline/policy.d.ts +171 -0
- package/dist/baseline/policy.d.ts.map +1 -0
- package/dist/baseline/policy.js +206 -0
- package/dist/baseline/policy.js.map +1 -0
- package/dist/baseline/producers/health.d.ts +30 -0
- package/dist/baseline/producers/health.d.ts.map +1 -0
- package/dist/baseline/producers/health.js +42 -0
- package/dist/baseline/producers/health.js.map +1 -0
- package/dist/baseline/producers/index.d.ts +164 -0
- package/dist/baseline/producers/index.d.ts.map +1 -0
- package/dist/baseline/producers/index.js +200 -0
- package/dist/baseline/producers/index.js.map +1 -0
- package/dist/baseline/producers/licenses.d.ts +23 -0
- package/dist/baseline/producers/licenses.d.ts.map +1 -0
- package/dist/baseline/producers/licenses.js +46 -0
- package/dist/baseline/producers/licenses.js.map +1 -0
- package/dist/baseline/producers/quality.d.ts +39 -0
- package/dist/baseline/producers/quality.d.ts.map +1 -0
- package/dist/baseline/producers/quality.js +84 -0
- package/dist/baseline/producers/quality.js.map +1 -0
- package/dist/baseline/producers/secret-hmac.d.ts +45 -0
- package/dist/baseline/producers/secret-hmac.d.ts.map +1 -0
- package/dist/baseline/producers/secret-hmac.js +70 -0
- package/dist/baseline/producers/secret-hmac.js.map +1 -0
- package/dist/baseline/producers/security.d.ts +59 -0
- package/dist/baseline/producers/security.d.ts.map +1 -0
- package/dist/baseline/producers/security.js +135 -0
- package/dist/baseline/producers/security.js.map +1 -0
- package/dist/baseline/producers/tests.d.ts +36 -0
- package/dist/baseline/producers/tests.d.ts.map +1 -0
- package/dist/baseline/producers/tests.js +69 -0
- package/dist/baseline/producers/tests.js.map +1 -0
- package/dist/baseline/salt.d.ts +45 -0
- package/dist/baseline/salt.d.ts.map +1 -0
- package/dist/baseline/salt.js +113 -0
- package/dist/baseline/salt.js.map +1 -0
- package/dist/baseline/show.d.ts +79 -0
- package/dist/baseline/show.d.ts.map +1 -0
- package/dist/baseline/show.js +233 -0
- package/dist/baseline/show.js.map +1 -0
- package/dist/baseline/types.d.ts +482 -0
- package/dist/baseline/types.d.ts.map +1 -0
- package/dist/baseline/types.js +53 -0
- package/dist/baseline/types.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +360 -81
- package/dist/cli.js.map +1 -1
- package/dist/codebase-scanner.d.ts.map +1 -1
- package/dist/codebase-scanner.js +0 -1
- package/dist/codebase-scanner.js.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +0 -4
- package/dist/constants.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +22 -25
- package/dist/doctor.js.map +1 -1
- package/dist/fail-on.d.ts +84 -0
- package/dist/fail-on.d.ts.map +1 -0
- package/dist/fail-on.js +128 -0
- package/dist/fail-on.js.map +1 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +2 -141
- package/dist/generator.js.map +1 -1
- package/dist/languages/csharp.d.ts.map +1 -1
- package/dist/languages/csharp.js +0 -9
- package/dist/languages/csharp.js.map +1 -1
- package/dist/languages/go.d.ts.map +1 -1
- package/dist/languages/go.js +0 -15
- package/dist/languages/go.js.map +1 -1
- package/dist/languages/index.d.ts +1 -1
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/java.d.ts.map +1 -1
- package/dist/languages/java.js +0 -6
- package/dist/languages/java.js.map +1 -1
- package/dist/languages/kotlin.d.ts.map +1 -1
- package/dist/languages/kotlin.js +0 -11
- package/dist/languages/kotlin.js.map +1 -1
- package/dist/languages/python.d.ts.map +1 -1
- package/dist/languages/python.js +0 -15
- package/dist/languages/python.js.map +1 -1
- package/dist/languages/ruby.d.ts.map +1 -1
- package/dist/languages/ruby.js +0 -6
- package/dist/languages/ruby.js.map +1 -1
- package/dist/languages/rust.d.ts.map +1 -1
- package/dist/languages/rust.js +0 -4
- package/dist/languages/rust.js.map +1 -1
- package/dist/languages/types.d.ts +2 -28
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript.d.ts.map +1 -1
- package/dist/languages/typescript.js +26 -4
- package/dist/languages/typescript.js.map +1 -1
- package/dist/lib.d.ts +2 -3
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +3 -6
- package/dist/lib.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +0 -10
- package/dist/prompts.js.map +1 -1
- package/dist/report-schema.d.ts +42 -0
- package/dist/report-schema.d.ts.map +1 -0
- package/dist/report-schema.js +54 -0
- package/dist/report-schema.js.map +1 -0
- package/dist/ship-installers.d.ts +106 -0
- package/dist/ship-installers.d.ts.map +1 -0
- package/dist/ship-installers.js +415 -0
- package/dist/ship-installers.js.map +1 -0
- package/dist/types.d.ts +0 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +0 -4
- package/dist/update.js.map +1 -1
- package/package.json +17 -11
- package/templates/.claude/agents/onboarding.md +5 -4
- package/templates/.claude/agents-available/codebase-explorer.md +1 -1
- package/templates/.claude/agents-available/debugger.md +2 -2
- package/templates/.claude/agents-available/health-auditor.md +2 -2
- package/templates/.claude/commands/doctor.md +20 -12
- package/templates/.claude/skills/build/SKILL.md.template +22 -30
- package/templates/.claude/skills/deploy/SKILL.md.template +5 -25
- package/templates/.claude/skills/doctor/SKILL.md +24 -47
- package/templates/.claude/skills/gcloud/SKILL.md +5 -5
- package/templates/.claude/skills/learned/SKILL.md +1 -1
- package/templates/.claude/skills/pulumi/SKILL.md +2 -2
- package/templates/.claude/skills/quality/SKILL.md.template +4 -23
- package/templates/.claude/skills/review/SKILL.md.template +4 -3
- package/templates/.claude/skills/scaffold/SKILL.md.template +5 -15
- package/templates/.claude/skills/secrets/SKILL.md +20 -21
- package/templates/.claude/skills/session/SKILL.md +20 -31
- package/templates/.claude/skills/test/SKILL.md.template +1 -7
- package/templates/.devcontainer/devcontainer.json +81 -0
- package/templates/.devcontainer/install-agent-clis.sh +42 -0
- package/templates/.devcontainer/post-create.sh +67 -0
- package/templates/.githooks/pre-commit +55 -0
- package/templates/.githooks/pre-push +63 -0
- package/templates/.github/workflows/dxkit-baseline-refresh.yml +78 -0
- package/templates/.github/workflows/dxkit-guardrails.yml +98 -0
- package/templates/CLAUDE.md.template +62 -196
- package/dist/project-yaml.d.ts +0 -13
- package/dist/project-yaml.d.ts.map +0 -1
- package/dist/project-yaml.js +0 -188
- package/dist/project-yaml.js.map +0 -1
- package/templates/.ai/README.md +0 -117
- package/templates/.ai/prompts/execution-prompt.md +0 -9
- package/templates/.ai/prompts/planning-prompt.md +0 -18
- package/templates/.ai/prompts/session-end-template.md +0 -182
- package/templates/.ai/prompts/session-end.md +0 -132
- package/templates/.ai/prompts/session-start.md +0 -109
- package/templates/.ai/prompts/step-by-step.md +0 -113
- package/templates/.ai/sessions/.gitkeep +0 -0
- package/templates/.claude/commands/setup-pr-review.md +0 -72
- package/templates/.devcontainer/Dockerfile.dev.template +0 -89
- package/templates/.devcontainer/devcontainer.json.template +0 -184
- package/templates/.devcontainer/docker-compose.yml.template +0 -105
- package/templates/.devcontainer/init-scripts/01-init.sql.template +0 -12
- package/templates/.devcontainer/post-create.sh.template +0 -298
- package/templates/.github/workflows/ci.yml.template +0 -399
- package/templates/.github/workflows/quality.yml.template +0 -376
- package/templates/.pre-commit-config.yaml.template +0 -106
- package/templates/.project/config/edit_config.py +0 -275
- package/templates/.project/config/project_config.py +0 -894
- package/templates/.project/scripts/codegen/generate-all.sh +0 -20
- package/templates/.project/scripts/codegen/validate-all.sh +0 -17
- package/templates/.project/scripts/docs/generate-all.sh +0 -30
- package/templates/.project/scripts/docs/serve.sh +0 -20
- package/templates/.project/scripts/quality/fix-all.sh +0 -138
- package/templates/.project/scripts/quality/lint-go.sh +0 -34
- package/templates/.project/scripts/quality/lint-python.sh +0 -54
- package/templates/.project/scripts/quality/run-all.sh +0 -497
- package/templates/.project/scripts/session/commit.sh +0 -70
- package/templates/.project/scripts/session/create-pr.sh +0 -165
- package/templates/.project/scripts/session/end.sh +0 -207
- package/templates/.project/scripts/session/start.sh +0 -233
- package/templates/.project/scripts/setup/doctor.sh +0 -404
- package/templates/.project/scripts/setup/interactive-setup.sh +0 -585
- package/templates/.project/scripts/sync/sync-template.sh +0 -328
- package/templates/.project/scripts/test/run-all.sh +0 -179
- package/templates/.project/scripts/test/run-quick.sh +0 -25
- package/templates/Makefile +0 -514
- package/templates/config/versions.yaml +0 -57
- package/templates/configs/go/.golangci.yml.template +0 -172
- package/templates/configs/go/go.mod.template +0 -15
- package/templates/configs/java/README.md +0 -6
- package/templates/configs/kotlin/README.md +0 -6
- package/templates/configs/node/package.json.template +0 -67
- package/templates/configs/node/tsconfig.json.template +0 -53
- package/templates/configs/python/pyproject.toml.template +0 -92
- package/templates/configs/python/pytest.ini.template +0 -64
- package/templates/configs/python/ruff.toml.template +0 -79
- package/templates/configs/ruby/README.md +0 -6
- package/templates/configs/rust/Cargo.toml.template +0 -51
- package/templates/configs/shared/.editorconfig +0 -67
- package/templates/scripts/validate-templates.sh +0 -449
|
@@ -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":"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"}
|
|
@@ -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"}
|