oauthlint 0.2.4 → 0.4.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.
@@ -1,25 +1,98 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { RULES_ROOT } from 'oauthlint-rules';
3
3
  import { SemgrepAdapter, SemgrepNotInstalledError, SemgrepOutputError, } from '../adapters/semgrep.js';
4
+ import { BaselineNotFoundError, BaselineParseError, DEFAULT_BASELINE_FILE, loadBaseline, partitionByBaseline, } from '../core/baseline.js';
5
+ import { GitError, resolveDiffFiles, resolveStagedFiles } from '../core/changed-files.js';
4
6
  import { loadConfig } from '../core/config.js';
5
7
  import { Reporter } from '../core/reporter.js';
6
8
  import { toSarif } from '../core/sarif.js';
7
9
  import { exitCodeFor, highestSeverity, meetsThreshold } from '../core/severity.js';
8
10
  import { applySuppressions } from '../core/suppress.js';
9
11
  export async function runScan(opts) {
10
- const target = resolve(opts.path);
11
- const config = await loadConfig(target);
12
+ const cwd = opts.cwd ?? process.cwd();
13
+ const config = await loadConfig(cwd);
12
14
  const rulesDir = opts.rulesDir ?? config.customRulesDir ?? RULES_ROOT;
13
15
  const failOn = opts.failOn ?? config.failOn ?? 'HIGH';
14
16
  const format = opts.format ?? (opts.json ? 'json' : 'pretty');
15
17
  const stream = opts.stream ?? process.stdout;
18
+ const errStream = opts.stream ?? process.stderr;
19
+ // Load the baseline up front so a missing/malformed file fails fast — before
20
+ // we pay for a full scan — with a clear error rather than a silent no-op.
21
+ let baseline = null;
22
+ if (opts.baseline !== undefined && opts.baseline !== false) {
23
+ const baselinePath = resolve(cwd, typeof opts.baseline === 'string' ? opts.baseline : DEFAULT_BASELINE_FILE);
24
+ try {
25
+ baseline = await loadBaseline(baselinePath);
26
+ }
27
+ catch (err) {
28
+ if (err instanceof BaselineNotFoundError || err instanceof BaselineParseError) {
29
+ errStream.write(`${err.message}\n`);
30
+ return 2;
31
+ }
32
+ throw err;
33
+ }
34
+ }
35
+ // Resolve the set of targets to hand Semgrep. Incremental flags (--diff /
36
+ // --staged) win and narrow the scan to changed files only; otherwise we scan
37
+ // the explicit path args (default ['.']).
38
+ let targets;
39
+ let incremental = false;
40
+ try {
41
+ if (opts.diff !== undefined && opts.diff !== false) {
42
+ incremental = true;
43
+ const ref = typeof opts.diff === 'string' ? opts.diff : undefined;
44
+ targets = await resolveDiffFiles(cwd, ref);
45
+ }
46
+ else if (opts.staged) {
47
+ incremental = true;
48
+ targets = await resolveStagedFiles(cwd);
49
+ }
50
+ else {
51
+ const requested = opts.paths?.length
52
+ ? opts.paths
53
+ : opts.path !== undefined
54
+ ? [opts.path]
55
+ : ['.'];
56
+ targets = requested.map((p) => resolve(cwd, p));
57
+ }
58
+ }
59
+ catch (err) {
60
+ if (err instanceof GitError) {
61
+ errStream.write(`${err.message}\n`);
62
+ return 2;
63
+ }
64
+ throw err;
65
+ }
66
+ // Incremental change set is empty (nothing in a supported language changed).
67
+ // That's a success, not an error — pre-commit hooks and editors call this
68
+ // constantly and should exit 0 with a clear note rather than failing.
69
+ if (incremental && targets.length === 0) {
70
+ if (format === 'json') {
71
+ stream.write(`${JSON.stringify({ schemaVersion: 'oauthlint-v1', findings: [] })}\n`);
72
+ }
73
+ else if (format === 'sarif') {
74
+ const empty = await toSarif({
75
+ findings: [],
76
+ scannedFiles: 0,
77
+ durationMs: 0,
78
+ semgrepVersion: null,
79
+ errors: [],
80
+ });
81
+ stream.write(`${JSON.stringify(empty, null, 2)}\n`);
82
+ }
83
+ else {
84
+ stream.write('No changed files to scan.\n');
85
+ }
86
+ return 0;
87
+ }
88
+ const label = incremental ? `${targets.length} changed file(s)` : targets.join(', ');
16
89
  const reporter = new Reporter({ json: format === 'json', stream });
17
90
  if (format === 'pretty')
18
- reporter.reportStart(target, await countRuleFiles(rulesDir));
91
+ reporter.reportStart(label, await countRuleFiles(rulesDir));
19
92
  const adapter = opts.adapter ?? new SemgrepAdapter({ configPath: rulesDir });
20
93
  let result;
21
94
  try {
22
- result = await adapter.scan(target, { applyFixes: opts.fix });
95
+ result = await adapter.scan(targets, { applyFixes: opts.fix });
23
96
  }
24
97
  catch (err) {
25
98
  if (err instanceof SemgrepNotInstalledError) {
@@ -43,6 +116,14 @@ export async function runScan(opts) {
43
116
  if (minSeverity) {
44
117
  findings = findings.filter((f) => meetsThreshold(f.severity, minSeverity));
45
118
  }
119
+ // Baseline suppression: drop findings whose fingerprint is already recorded,
120
+ // so only genuinely NEW findings are reported and gate the exit code.
121
+ let baselinedCount = 0;
122
+ if (baseline) {
123
+ const { newFindings, baselined } = await partitionByBaseline(findings, baseline, cwd);
124
+ findings = newFindings;
125
+ baselinedCount = baselined.length;
126
+ }
46
127
  if (format === 'sarif') {
47
128
  const sarif = await toSarif({ ...result, findings });
48
129
  stream.write(`${JSON.stringify(sarif, null, 2)}\n`);
@@ -56,6 +137,9 @@ export async function runScan(opts) {
56
137
  if (suppressed.length > 0 && format === 'pretty') {
57
138
  stream.write(`\nℹ ${suppressed.length} finding${suppressed.length === 1 ? '' : 's'} suppressed via inline directives.\n`);
58
139
  }
140
+ if (baselinedCount > 0 && format === 'pretty') {
141
+ stream.write(`\nℹ ${baselinedCount} finding${baselinedCount === 1 ? '' : 's'} already in the baseline (reporting NEW findings only).\n`);
142
+ }
59
143
  }
60
144
  if (failOn === 'off')
61
145
  return 0;
@@ -1 +1 @@
1
- {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ import type { Finding } from '../types.js';
2
+ /**
3
+ * Baseline support: capture the CURRENT set of findings so a team can adopt
4
+ * OAuthLint on a large existing codebase and only be alerted on NEW issues.
5
+ *
6
+ * The hard requirement is a fingerprint that is stable across edits that move
7
+ * a finding around without changing it: inserting an import, reformatting a
8
+ * block, or shifting code down N lines must NOT resurface a baselined finding,
9
+ * while genuinely changing the flagged code MUST surface it as new.
10
+ *
11
+ * Fingerprint = sha256 of three parts joined by NULs:
12
+ * 1. the rule identity — `oauthlintRuleId` when present, else `ruleId`
13
+ * 2. the repo/cwd-relative file path (POSIX separators, so it's portable)
14
+ * 3. a NORMALISED snapshot of the matched source (the `startLine..endLine`
15
+ * span, whitespace-collapsed) — explicitly NOT the raw line numbers
16
+ *
17
+ * When the file can't be read (deleted, binary, permission error) we fall back
18
+ * to the trimmed `message` as the snapshot so a fingerprint can still be
19
+ * produced — degraded but deterministic, never a crash.
20
+ *
21
+ * Identical fingerprints within one file (e.g. the same anti-pattern repeated)
22
+ * are disambiguated by a stable per-file occurrence index, assigned in source
23
+ * order, so each physical finding maps to a distinct baseline entry.
24
+ */
25
+ export declare const BASELINE_VERSION: 1;
26
+ export declare const DEFAULT_BASELINE_FILE = ".oauthlint-baseline.json";
27
+ /** A single baselined finding, keyed by its stable fingerprint. */
28
+ export interface BaselineEntry {
29
+ /** Stable fingerprint (see module docs). Survives line shifts. */
30
+ fingerprint: string;
31
+ /** Rule identity used in the hash — for human-diffability of the file. */
32
+ ruleId: string;
33
+ /** Repo/cwd-relative path used in the hash — for human-diffability. */
34
+ filePath: string;
35
+ }
36
+ export interface BaselineFile {
37
+ version: typeof BASELINE_VERSION;
38
+ generatedAt: string;
39
+ /** Sorted, deterministic list of baselined fingerprints. */
40
+ findings: BaselineEntry[];
41
+ }
42
+ /** In-memory baseline: just the set of fingerprints we suppress against. */
43
+ export interface Baseline {
44
+ fingerprints: Set<string>;
45
+ }
46
+ export declare class BaselineNotFoundError extends Error {
47
+ constructor(path: string);
48
+ }
49
+ export declare class BaselineParseError extends Error {
50
+ constructor(path: string, detail: string);
51
+ }
52
+ /**
53
+ * Turn a finding's `filePath` into a portable, repo/cwd-relative path with
54
+ * POSIX separators. Absolute paths are made relative to `cwd`; paths that are
55
+ * already relative are normalised as-is. We never let an absolute path leak
56
+ * into a fingerprint, so a baseline is portable across machines/checkouts.
57
+ */
58
+ export declare function toRelativePath(filePath: string, cwd: string): string;
59
+ /**
60
+ * Compute the stable fingerprint for a finding (without the occurrence index).
61
+ * Two findings of the same rule on the same (relative) file with the same
62
+ * normalised snippet share this value — they're disambiguated by the index
63
+ * added in {@link fingerprintFindings}.
64
+ */
65
+ export declare function baseFingerprint(finding: Finding, cwd: string, cache: Map<string, string[] | null>): Promise<string>;
66
+ /** A finding paired with its final, occurrence-disambiguated fingerprint. */
67
+ export interface FingerprintedFinding {
68
+ finding: Finding;
69
+ fingerprint: string;
70
+ }
71
+ /**
72
+ * Fingerprint a list of findings deterministically. Findings are processed in
73
+ * a stable order, and identical base fingerprints within the run get an
74
+ * occurrence index (`<base>:<n>`) so repeated anti-patterns each get a unique
75
+ * key. The same input list always yields the same fingerprints.
76
+ */
77
+ export declare function fingerprintFindings(findings: Finding[], cwd: string): Promise<FingerprintedFinding[]>;
78
+ /**
79
+ * Build a serialisable baseline file from a set of findings. The entry list is
80
+ * sorted so the JSON is stable and human-diffable across runs.
81
+ */
82
+ export declare function buildBaseline(findings: Finding[], cwd: string): Promise<BaselineFile>;
83
+ /** Serialise a baseline file to pretty JSON (trailing newline for clean diffs). */
84
+ export declare function serialiseBaseline(baseline: BaselineFile): string;
85
+ /**
86
+ * Load a baseline from disk into an in-memory fingerprint set.
87
+ *
88
+ * @throws BaselineNotFoundError when the file is absent — deliberately NOT
89
+ * treated as an empty allow-list, so a misspelt `--baseline path` fails loud
90
+ * instead of silently suppressing nothing (or everything).
91
+ * @throws BaselineParseError when the file is present but malformed.
92
+ */
93
+ export declare function loadBaseline(path: string): Promise<Baseline>;
94
+ /**
95
+ * Split findings into those already present in the baseline (suppressed) and
96
+ * genuinely new ones, by fingerprinting and matching against the baseline set.
97
+ */
98
+ export declare function partitionByBaseline(findings: Finding[], baseline: Baseline, cwd: string): Promise<{
99
+ newFindings: Finding[];
100
+ baselined: Finding[];
101
+ }>;
102
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/core/baseline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,eAAO,MAAM,gBAAgB,EAAG,CAAU,CAAC;AAC3C,eAAO,MAAM,qBAAqB,6BAA6B,CAAC;AAEhE,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,gBAAgB,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,4EAA4E;AAC5E,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC3B;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,IAAI,EAAE,MAAM;CAMzB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAIzC;AAYD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGpE;AAuDD;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,GAClC,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,OAAO,EAAE,EACnB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAuBjC;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAkB3F;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAEhE;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8BlE;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,WAAW,EAAE,OAAO,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC,CAS3D"}
@@ -0,0 +1,223 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { isAbsolute, relative, resolve } from 'node:path';
4
+ /**
5
+ * Baseline support: capture the CURRENT set of findings so a team can adopt
6
+ * OAuthLint on a large existing codebase and only be alerted on NEW issues.
7
+ *
8
+ * The hard requirement is a fingerprint that is stable across edits that move
9
+ * a finding around without changing it: inserting an import, reformatting a
10
+ * block, or shifting code down N lines must NOT resurface a baselined finding,
11
+ * while genuinely changing the flagged code MUST surface it as new.
12
+ *
13
+ * Fingerprint = sha256 of three parts joined by NULs:
14
+ * 1. the rule identity — `oauthlintRuleId` when present, else `ruleId`
15
+ * 2. the repo/cwd-relative file path (POSIX separators, so it's portable)
16
+ * 3. a NORMALISED snapshot of the matched source (the `startLine..endLine`
17
+ * span, whitespace-collapsed) — explicitly NOT the raw line numbers
18
+ *
19
+ * When the file can't be read (deleted, binary, permission error) we fall back
20
+ * to the trimmed `message` as the snapshot so a fingerprint can still be
21
+ * produced — degraded but deterministic, never a crash.
22
+ *
23
+ * Identical fingerprints within one file (e.g. the same anti-pattern repeated)
24
+ * are disambiguated by a stable per-file occurrence index, assigned in source
25
+ * order, so each physical finding maps to a distinct baseline entry.
26
+ */
27
+ export const BASELINE_VERSION = 1;
28
+ export const DEFAULT_BASELINE_FILE = '.oauthlint-baseline.json';
29
+ export class BaselineNotFoundError extends Error {
30
+ constructor(path) {
31
+ super(`Baseline file not found: ${path}\nCreate one first with: oauthlint baseline\n(an absent baseline is treated as an error, not an empty allow-list, so a typo never silently disables suppression).`);
32
+ this.name = 'BaselineNotFoundError';
33
+ }
34
+ }
35
+ export class BaselineParseError extends Error {
36
+ constructor(path, detail) {
37
+ super(`Could not parse baseline file ${path}: ${detail}`);
38
+ this.name = 'BaselineParseError';
39
+ }
40
+ }
41
+ /** Collapse runs of whitespace and trim, so reformatting doesn't churn fingerprints. */
42
+ function normaliseSnippet(text) {
43
+ return text.replace(/\s+/g, ' ').trim();
44
+ }
45
+ /** Rule identity that goes into the hash — prefer the stable OAuthLint id. */
46
+ function ruleIdentity(finding) {
47
+ return finding.oauthlintRuleId ?? finding.ruleId;
48
+ }
49
+ /**
50
+ * Turn a finding's `filePath` into a portable, repo/cwd-relative path with
51
+ * POSIX separators. Absolute paths are made relative to `cwd`; paths that are
52
+ * already relative are normalised as-is. We never let an absolute path leak
53
+ * into a fingerprint, so a baseline is portable across machines/checkouts.
54
+ */
55
+ export function toRelativePath(filePath, cwd) {
56
+ const rel = isAbsolute(filePath) ? relative(cwd, filePath) : filePath;
57
+ return rel.split('\\').join('/');
58
+ }
59
+ /**
60
+ * Read the matched source span for a finding and return it normalised. On any
61
+ * read/decoding problem, fall back to the finding's message so we still get a
62
+ * deterministic value rather than throwing.
63
+ */
64
+ async function snapshotFor(finding, cwd, cache) {
65
+ const abs = isAbsolute(finding.filePath) ? finding.filePath : resolve(cwd, finding.filePath);
66
+ let lines = cache.get(abs);
67
+ if (lines === undefined) {
68
+ lines = await readLinesSafe(abs);
69
+ cache.set(abs, lines);
70
+ }
71
+ if (lines === null) {
72
+ // Unreadable (missing/binary/permission) — degrade to the message text.
73
+ return normaliseSnippet(finding.message);
74
+ }
75
+ // Finding lines are 1-based and inclusive. Clamp into range defensively in
76
+ // case the file changed between scan and fingerprinting.
77
+ const start = Math.max(1, finding.startLine);
78
+ const end = Math.max(start, finding.endLine);
79
+ const span = lines.slice(start - 1, end).join('\n');
80
+ const normalised = normaliseSnippet(span);
81
+ // If the span is empty (out-of-range lines after a shrink), fall back so the
82
+ // fingerprint is never the hash of an empty string for every such finding.
83
+ return normalised || normaliseSnippet(finding.message);
84
+ }
85
+ /** Read a file as lines, returning null when it's missing or looks binary. */
86
+ async function readLinesSafe(absPath) {
87
+ let buf;
88
+ try {
89
+ buf = await readFile(absPath);
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ // Treat NUL bytes as the binary signal (cheap, matches common heuristics).
95
+ if (buf.includes(0))
96
+ return null;
97
+ return buf.toString('utf8').split('\n');
98
+ }
99
+ function hash(parts) {
100
+ const h = createHash('sha256');
101
+ // NUL separator can't appear in any of the parts (path/id are text, the
102
+ // snippet has NULs stripped by the binary check), so there's no collision
103
+ // between e.g. ["ab","c"] and ["a","bc"].
104
+ h.update(parts.join('\0'));
105
+ return h.digest('hex');
106
+ }
107
+ /**
108
+ * Compute the stable fingerprint for a finding (without the occurrence index).
109
+ * Two findings of the same rule on the same (relative) file with the same
110
+ * normalised snippet share this value — they're disambiguated by the index
111
+ * added in {@link fingerprintFindings}.
112
+ */
113
+ export async function baseFingerprint(finding, cwd, cache) {
114
+ const rel = toRelativePath(finding.filePath, cwd);
115
+ const snippet = await snapshotFor(finding, cwd, cache);
116
+ return hash([ruleIdentity(finding), rel, snippet]);
117
+ }
118
+ /**
119
+ * Fingerprint a list of findings deterministically. Findings are processed in
120
+ * a stable order, and identical base fingerprints within the run get an
121
+ * occurrence index (`<base>:<n>`) so repeated anti-patterns each get a unique
122
+ * key. The same input list always yields the same fingerprints.
123
+ */
124
+ export async function fingerprintFindings(findings, cwd) {
125
+ const cache = new Map();
126
+ // Stable processing order so occurrence indices are reproducible regardless
127
+ // of the adapter's emission order: by relative path, then line, then rule.
128
+ const ordered = [...findings]
129
+ .map((finding) => ({ finding, rel: toRelativePath(finding.filePath, cwd) }))
130
+ .sort((a, b) => a.rel.localeCompare(b.rel) ||
131
+ a.finding.startLine - b.finding.startLine ||
132
+ a.finding.endLine - b.finding.endLine ||
133
+ ruleIdentity(a.finding).localeCompare(ruleIdentity(b.finding)));
134
+ const seen = new Map();
135
+ const out = [];
136
+ for (const { finding } of ordered) {
137
+ const base = await baseFingerprint(finding, cwd, cache);
138
+ const n = seen.get(base) ?? 0;
139
+ seen.set(base, n + 1);
140
+ out.push({ finding, fingerprint: `${base}:${n}` });
141
+ }
142
+ return out;
143
+ }
144
+ /**
145
+ * Build a serialisable baseline file from a set of findings. The entry list is
146
+ * sorted so the JSON is stable and human-diffable across runs.
147
+ */
148
+ export async function buildBaseline(findings, cwd) {
149
+ const fingerprinted = await fingerprintFindings(findings, cwd);
150
+ const entries = fingerprinted.map(({ finding, fingerprint }) => ({
151
+ fingerprint,
152
+ ruleId: ruleIdentity(finding),
153
+ filePath: toRelativePath(finding.filePath, cwd),
154
+ }));
155
+ entries.sort((a, b) => a.filePath.localeCompare(b.filePath) ||
156
+ a.ruleId.localeCompare(b.ruleId) ||
157
+ a.fingerprint.localeCompare(b.fingerprint));
158
+ return {
159
+ version: BASELINE_VERSION,
160
+ generatedAt: new Date().toISOString(),
161
+ findings: entries,
162
+ };
163
+ }
164
+ /** Serialise a baseline file to pretty JSON (trailing newline for clean diffs). */
165
+ export function serialiseBaseline(baseline) {
166
+ return `${JSON.stringify(baseline, null, 2)}\n`;
167
+ }
168
+ /**
169
+ * Load a baseline from disk into an in-memory fingerprint set.
170
+ *
171
+ * @throws BaselineNotFoundError when the file is absent — deliberately NOT
172
+ * treated as an empty allow-list, so a misspelt `--baseline path` fails loud
173
+ * instead of silently suppressing nothing (or everything).
174
+ * @throws BaselineParseError when the file is present but malformed.
175
+ */
176
+ export async function loadBaseline(path) {
177
+ let raw;
178
+ try {
179
+ raw = await readFile(path, 'utf8');
180
+ }
181
+ catch (err) {
182
+ if (err.code === 'ENOENT') {
183
+ throw new BaselineNotFoundError(path);
184
+ }
185
+ throw err;
186
+ }
187
+ let parsed;
188
+ try {
189
+ parsed = JSON.parse(raw);
190
+ }
191
+ catch (err) {
192
+ throw new BaselineParseError(path, err instanceof Error ? err.message : String(err));
193
+ }
194
+ if (typeof parsed !== 'object' ||
195
+ parsed === null ||
196
+ !Array.isArray(parsed.findings)) {
197
+ throw new BaselineParseError(path, 'expected an object with a "findings" array');
198
+ }
199
+ const fingerprints = new Set();
200
+ for (const entry of parsed.findings) {
201
+ if (entry && typeof entry.fingerprint === 'string') {
202
+ fingerprints.add(entry.fingerprint);
203
+ }
204
+ }
205
+ return { fingerprints };
206
+ }
207
+ /**
208
+ * Split findings into those already present in the baseline (suppressed) and
209
+ * genuinely new ones, by fingerprinting and matching against the baseline set.
210
+ */
211
+ export async function partitionByBaseline(findings, baseline, cwd) {
212
+ const fingerprinted = await fingerprintFindings(findings, cwd);
213
+ const newFindings = [];
214
+ const baselined = [];
215
+ for (const { finding, fingerprint } of fingerprinted) {
216
+ if (baseline.fingerprints.has(fingerprint))
217
+ baselined.push(finding);
218
+ else
219
+ newFindings.push(finding);
220
+ }
221
+ return { newFindings, baselined };
222
+ }
223
+ //# sourceMappingURL=baseline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../src/core/baseline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Raised when a git operation can't be performed — most commonly because the
3
+ * target directory is not inside a git work tree, or `git` itself is missing.
4
+ * Callers (the scan command) translate this into a clean, non-crashing error
5
+ * message rather than a stack trace.
6
+ */
7
+ export declare class GitError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ /** True when `file` has an extension the rule pack can actually scan. */
11
+ export declare function isScannableSourceFile(file: string): boolean;
12
+ /**
13
+ * Files changed versus `ref` (or the default branch's merge-base when `ref` is
14
+ * undefined), as absolute paths, filtered to scannable source files.
15
+ *
16
+ * Combines four sets so nothing in flight is missed:
17
+ * - committed changes since `ref` (`git diff --name-only <ref>...`)
18
+ * - unstaged work-tree changes (`git diff --name-only`)
19
+ * - staged-but-uncommitted changes (`git diff --name-only --cached`)
20
+ * - new, not-yet-tracked files (`git ls-files --others --exclude-standard`)
21
+ *
22
+ * `--diff-filter=ACMR` keeps Added / Copied / Modified / Renamed and drops
23
+ * Deleted files (we can't scan a file that no longer exists). Untracked files
24
+ * are picked up separately because plain `git diff` only reports tracked paths —
25
+ * a freshly written, un-`add`ed source file would otherwise be missed.
26
+ *
27
+ * @throws GitError when not in a git repo (or git is unavailable).
28
+ */
29
+ export declare function resolveDiffFiles(cwd: string, ref?: string): Promise<string[]>;
30
+ /**
31
+ * Staged files (`git diff --name-only --cached --diff-filter=ACMR`) as absolute
32
+ * paths, filtered to scannable source files. This is the set a pre-commit hook
33
+ * cares about.
34
+ *
35
+ * @throws GitError when not in a git repo (or git is unavailable).
36
+ */
37
+ export declare function resolveStagedFiles(cwd: string): Promise<string[]>;
38
+ //# sourceMappingURL=changed-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changed-files.d.ts","sourceRoot":"","sources":["../../src/core/changed-files.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,qBAAa,QAAS,SAAQ,KAAK;gBACrB,OAAO,EAAE,MAAM;CAI5B;AAiCD,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D;AA0FD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBnF;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAMvE"}