pr-checkmate 1.23.24 → 2.1.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 (105) hide show
  1. package/README.md +89 -13
  2. package/cspell.json +11 -9
  3. package/dist/cli.js +37 -64
  4. package/dist/config/constants.d.ts +51 -3
  5. package/dist/config/constants.js +73 -11
  6. package/dist/config/pr-checkmate-workflow-docker.yml +79 -0
  7. package/dist/config/pr-checkmate-workflow.yml +65 -326
  8. package/dist/core/checks/dependencies/banned-imports.js +23 -24
  9. package/dist/core/checks/dependencies/circular-deps.js +1 -0
  10. package/dist/core/checks/dependencies/dependency-check.js +1 -0
  11. package/dist/core/checks/dependencies/grype-scan.js +1 -0
  12. package/dist/core/checks/dependencies/npm-audit.js +6 -1
  13. package/dist/core/checks/dependencies/vuln-scan.js +1 -0
  14. package/dist/core/checks/findings.d.ts +198 -19
  15. package/dist/core/checks/findings.js +474 -22
  16. package/dist/core/checks/git/custom-rules.js +21 -16
  17. package/dist/core/checks/git/diff-smell.d.ts +17 -0
  18. package/dist/core/checks/git/diff-smell.js +43 -24
  19. package/dist/core/checks/git/large-files.js +14 -2
  20. package/dist/core/checks/git/leftover-debug.js +23 -14
  21. package/dist/core/checks/git/lockfile-drift.js +2 -1
  22. package/dist/core/checks/git/merge-conflict.js +19 -8
  23. package/dist/core/checks/git/missing-tests.js +22 -6
  24. package/dist/core/checks/git/todo-fixme.js +10 -9
  25. package/dist/core/checks/languages/shellcheck.d.ts +1 -1
  26. package/dist/core/checks/languages/shellcheck.js +1 -1
  27. package/dist/core/checks/languages/typecheck.js +1 -0
  28. package/dist/core/checks/pr/pr-size-check.js +2 -1
  29. package/dist/core/checks/quality/config-validation.d.ts +14 -4
  30. package/dist/core/checks/quality/config-validation.js +119 -17
  31. package/dist/core/checks/quality/dead-code.js +1 -0
  32. package/dist/core/checks/quality/duplicate-check.js +1 -0
  33. package/dist/core/checks/quality/license-header.js +12 -2
  34. package/dist/core/checks/quality/markdown-lint.js +7 -2
  35. package/dist/core/checks/quality/spellcheck.d.ts +50 -0
  36. package/dist/core/checks/quality/spellcheck.js +141 -13
  37. package/dist/core/checks/quality/yaml-lint.js +22 -9
  38. package/dist/core/checks/security/diff-security.js +115 -28
  39. package/dist/core/checks/security/dockerfile-security.js +23 -20
  40. package/dist/core/checks/security/migration-safety.js +19 -17
  41. package/dist/core/checks/security/sensitive-file-guard.js +37 -5
  42. package/dist/core/checks/security/workflow-security.d.ts +10 -0
  43. package/dist/core/checks/security/workflow-security.js +48 -26
  44. package/dist/core/commands.d.ts +27 -0
  45. package/dist/core/commands.js +91 -0
  46. package/dist/core/context.d.ts +12 -3
  47. package/dist/core/context.js +118 -16
  48. package/dist/core/install/cache.d.ts +39 -0
  49. package/dist/core/install/cache.js +99 -0
  50. package/dist/core/install/doctor.d.ts +35 -0
  51. package/dist/core/install/doctor.js +145 -0
  52. package/dist/core/install/ensure.d.ts +60 -0
  53. package/dist/core/install/ensure.js +169 -0
  54. package/dist/core/install/lock.d.ts +33 -0
  55. package/dist/core/install/lock.js +99 -0
  56. package/dist/core/install/profiles.d.ts +64 -0
  57. package/dist/core/install/profiles.js +170 -0
  58. package/dist/core/reporters.d.ts +13 -2
  59. package/dist/core/reporters.js +145 -104
  60. package/dist/core/run-checks.d.ts +25 -1
  61. package/dist/core/run-checks.js +124 -14
  62. package/dist/core/targets.js +26 -4
  63. package/dist/core/types.d.ts +49 -1
  64. package/dist/init.d.ts +29 -14
  65. package/dist/init.js +284 -263
  66. package/dist/interfaces/index.d.ts +120 -0
  67. package/dist/scripts/copy-configs.js +4 -0
  68. package/dist/scripts/generate-install-manifests.d.ts +39 -0
  69. package/dist/scripts/generate-install-manifests.js +121 -0
  70. package/dist/scripts/publish-github.js +18 -4
  71. package/dist/utils/detect-language.d.ts +37 -4
  72. package/dist/utils/detect-language.js +113 -19
  73. package/dist/utils/http.d.ts +59 -0
  74. package/dist/utils/http.js +119 -0
  75. package/dist/utils/logger.d.ts +52 -6
  76. package/dist/utils/logger.js +125 -33
  77. package/dist/utils/pool.d.ts +49 -0
  78. package/dist/utils/pool.js +124 -0
  79. package/dist/utils/update-notifier.d.ts +9 -0
  80. package/dist/utils/update-notifier.js +57 -3
  81. package/eslint-default-rules.json +1 -1
  82. package/eslint.config.mjs +14 -2
  83. package/install-manifests/commitlint/package-lock.json +1028 -0
  84. package/install-manifests/commitlint/package.json +9 -0
  85. package/install-manifests/core/package-lock.json +1335 -0
  86. package/install-manifests/core/package.json +11 -0
  87. package/install-manifests/cpp/package-lock.json +29 -0
  88. package/install-manifests/cpp/package.json +8 -0
  89. package/install-manifests/duplicate/package-lock.json +113 -0
  90. package/install-manifests/duplicate/package.json +8 -0
  91. package/install-manifests/js/package-lock.json +5099 -0
  92. package/install-manifests/js/package.json +26 -0
  93. package/install-manifests/markdown/package-lock.json +1281 -0
  94. package/install-manifests/markdown/package.json +8 -0
  95. package/install-manifests/python/package-lock.json +21 -0
  96. package/install-manifests/python/package.json +8 -0
  97. package/install-manifests/spellcheck/package-lock.json +1047 -0
  98. package/install-manifests/spellcheck/package.json +8 -0
  99. package/package.json +14 -28
  100. package/pr-checkmate.schema.json +1062 -104
  101. package/src/config/cspell.json +11 -9
  102. package/src/config/eslint-default-rules.json +1 -1
  103. package/src/config/pr-checkmate-workflow-docker.yml +79 -0
  104. package/src/config/pr-checkmate-workflow.yml +65 -326
  105. package/src/config/pr-checkmate.schema.json +1062 -104
@@ -5,9 +5,13 @@ import { CheckContext, CheckOutcome } from '../types';
5
5
  * only advises. Every check declares a natural default; the client overrides it
6
6
  * per repo in pr-checkmate.json — promoting an advisory check to a gate, or
7
7
  * downgrading a blocking one, with no code change.
8
+ *
9
+ * Deliberately not exported, here and for {@link SeverityEmitter}: no module
10
+ * names either type, and `tsc` inlines both into `findings.d.ts` anyway, so an
11
+ * `export` would only be API surface the Dead Code check then reports as unused.
8
12
  */
9
- export type Severity = 'error' | 'warn';
10
- export interface SeverityEmitter {
13
+ type Severity = 'error' | 'warn';
14
+ interface SeverityEmitter {
11
15
  /** Log at the resolved level (`logger.error` for error, `logger.warn` for warn). */
12
16
  log: (msg: string) => void;
13
17
  /** Prefix icon matching the level. */
@@ -23,33 +27,156 @@ export interface SeverityEmitter {
23
27
  export declare function severityEmitter(configured: Severity | undefined, fallback: Severity): SeverityEmitter;
24
28
  /** Findings grouped by label → matching sample strings. */
25
29
  export type FindingBucket = Record<string, string[]>;
30
+ /** One scan's findings, split by the level each pattern assigned them. */
31
+ export interface SeverityBuckets {
32
+ errors: FindingBucket;
33
+ warnings: FindingBucket;
34
+ }
35
+ /**
36
+ * Fold one scan's findings into a check's running totals.
37
+ *
38
+ * Checks accumulate across several scans — one per language, or one per file on
39
+ * the full-scan path — so every accumulating check needs this, and each had the
40
+ * same pair of nested loops written out inline.
41
+ */
42
+ export declare function mergeSeverityBuckets(into: SeverityBuckets, from: SeverityBuckets): void;
43
+ /** A fresh pair of empty buckets for a check to accumulate into. */
44
+ export declare function emptyBuckets(): SeverityBuckets;
26
45
  /**
27
46
  * Run `git diff` over the given file globs, returning the raw unified diff
28
47
  * (zero context, added/copied/modified/renamed files) for added-line scanning.
29
48
  */
30
49
  export declare function diffAddedLines(ctx: CheckContext, globs: string[]): Promise<string>;
31
50
  /**
32
- * Strip a trailing line comment (`//` or `#`) from a line, tracking quote state
33
- * so a marker inside a string literal (or a `#` in `"#fff"`, a `//` in a quoted
34
- * URL) is preserved. Heuristic, not a tokenizer: block comments and regex
35
- * literals aren't handled. Used by code-targeting checks so `# print(x)` or
36
- * `// eval(x)` in a comment isn't flagged as real code.
51
+ * Present tracked files as if every line had just been added, so a check written
52
+ * for a diff can review the whole repository unchanged.
53
+ *
54
+ * The patterns are anchored on the diff's `+` marker (`/^\+(?!\+)…/`), which is
55
+ * why `--full` used to skip most of the security surface: no diff, no lines
56
+ * starting with `+`, no findings. Rather than rewrite forty patterns — and lose
57
+ * the guarantee that both modes agree — the file contents are wrapped in the
58
+ * shape the pipeline already understands. Comment stripping, string blanking,
59
+ * `view: 'code'`, grouping and every client knob then work identically.
60
+ *
61
+ * Files are read one at a time and handed to `onFile` rather than concatenated:
62
+ * a monorepo would otherwise be assembled into a single multi-megabyte string
63
+ * before the first pattern ran.
64
+ */
65
+ export declare function scanTrackedFiles(ctx: CheckContext, files: string[], onFile: (asDiff: string) => void): Promise<void>;
66
+ /**
67
+ * Hand a check the lines it should review, from the diff when there is one and
68
+ * from the tracked files otherwise.
69
+ *
70
+ * The single entry point every content check uses, so the choice between the two
71
+ * modes is made once rather than re-implemented per check — and so a check cannot
72
+ * accidentally support only one of them, which is how `--full` ended up with no
73
+ * security coverage at all (ticket 067).
74
+ *
75
+ * `onChunk` is called once per diff in delta mode and once per file in full mode,
76
+ * which keeps a whole monorepo from being concatenated before matching starts.
77
+ */
78
+ export declare function reviewLines(ctx: CheckContext, globs: string[], onChunk: (stdout: string) => void): Promise<void>;
79
+ /**
80
+ * Which comment markers apply to a file, since they are not universal:
81
+ * - `c-like` — `//` only. `#` is NOT a comment in C-family syntax, where it
82
+ * opens a JS private field (`this.#handler`) or a C preprocessor directive.
83
+ * - `hash` — `#` only. `//` in Python or YAML is division or part of a URL.
84
+ * - `both` — unknown extension: strip either, which over-strips rather than
85
+ * under-reports. The conservative default, and the pre-060 behaviour.
86
+ */
87
+ export type CommentSyntax = 'c-like' | 'hash' | 'both';
88
+ /** Comment syntax for a repo-relative path; `both` when the extension is unknown. */
89
+ export declare function commentSyntaxFor(file: string): CommentSyntax;
90
+ /**
91
+ * Strip a trailing line comment from a line, tracking quote state so a marker
92
+ * inside a string literal (a `#` in `"#fff"`, a `//` in a quoted URL) survives.
93
+ *
94
+ * Heuristic, not a tokenizer. `syntax` selects the applicable markers — passing
95
+ * the wrong one is how a valid `this.#handler = eval(payload)` used to be read
96
+ * as a comment and dropped entirely (ticket 059).
97
+ *
98
+ * Block comments are handled by the caller, which alone can track `/* *\/` state
99
+ * across lines; see {@link scanDiffPatterns}.
100
+ */
101
+ export declare function stripInlineComment(line: string, syntax?: CommentSyntax): string;
102
+ /**
103
+ * Blank out the contents of string literals, keeping the quotes and the original
104
+ * length so the rest of the line still reads as code.
105
+ *
106
+ * This is what lets a pattern ask "is a real call being made here" without a
107
+ * lookahead that guesses at quoting. The `eval` rule used to carry
108
+ * `(?![^'"`]*['"`])` — intended to skip `eval` written inside a string, but in
109
+ * practice it disabled the rule on any line containing a quote anywhere, so
110
+ * `eval(userInput + "x")` and ``eval(`${payload}`)`` both went unreported
111
+ * (ticket 058).
112
+ *
113
+ * Only for patterns that match *code*. Rules that look for string *contents* —
114
+ * an SQL keyword inside quotes, a hardcoded URL — must keep reading the raw
115
+ * line, which is what {@link DiffPattern.view} selects.
116
+ */
117
+ export declare function stripStringLiterals(line: string): string;
118
+ /** One regex-matched finding: which added lines to flag, under what label and level. */
119
+ export interface DiffPattern {
120
+ pattern: RegExp;
121
+ label: string;
122
+ severity: Severity;
123
+ /**
124
+ * Which view of the line the pattern reads.
125
+ * - `raw` (default) — the line as written, comments already stripped. Required
126
+ * by rules that inspect string *contents*, e.g. an SQL keyword in quotes.
127
+ * - `code` — string literals blanked by {@link stripStringLiterals}, so a rule
128
+ * matching a *call* is not thrown off by unrelated quoting on the same line.
129
+ */
130
+ view?: 'raw' | 'code';
131
+ }
132
+ /**
133
+ * Bucket added diff lines by the {@link DiffPattern}s they match, split by
134
+ * severity. Patterns are expected to anchor on the leading `+` themselves
135
+ * (`/^\+(?!\+)…/`), which is what keeps `+++` file headers and context lines out.
136
+ *
137
+ * Comment handling follows the file each hunk belongs to, read from the `+++`
138
+ * headers as the diff is walked — a single run reviews files of several
139
+ * languages, so a repo-wide setting would be wrong for most of them.
140
+ *
141
+ * `skipIgnored` re-applies the `pr-checkmate-ignore` opt-out for callers that run
142
+ * `git diff` themselves; a diff obtained from {@link diffAddedLines} is already
143
+ * filtered and should leave it off.
144
+ *
145
+ * `comments: 'off'` scans lines verbatim, which is how a client audits code that
146
+ * was commented out rather than deleted.
37
147
  */
38
- export declare function stripInlineComment(line: string): string;
148
+ export declare function scanDiffPatterns(stdout: string, patterns: readonly DiffPattern[], opts?: {
149
+ skipIgnored?: boolean;
150
+ comments?: 'language' | 'legacy' | 'off';
151
+ /** Globs whose files are skipped entirely (client `<check>.ignorePaths`). */
152
+ ignorePaths?: string[];
153
+ }): SeverityBuckets;
39
154
  /** Total number of findings across all labels in a bucket. */
40
155
  export declare function bucketTotal(bucket: FindingBucket): number;
41
- interface LogOptions {
42
- /** Log prefix, e.g. 'Diff Security'. */
156
+ /**
157
+ * Report a bucket at the check's configured level, summarized as
158
+ * `9× console.*(), 1× debugger statement`.
159
+ *
160
+ * The phrasing every keyword-counting check uses: the label already says what
161
+ * was found, so only the count varies. Written out per check, the four-step tail
162
+ * — resolve the level, log at it, join the counts, emit at the same level — was
163
+ * copied verbatim, and `isError: sev.icon === '❌'` is exactly the kind of link
164
+ * between two of those steps that silently rots when only one side is edited.
165
+ */
166
+ export declare function reportCountedFindings(findings: FindingBucket, ctx: CheckContext, opts: {
167
+ /** Log prefix, e.g. 'TODO/FIXME'. */
43
168
  prefix: string;
44
- /** Use logger.error + ❌ instead of logger.warn + ⚠️. */
45
- isError?: boolean;
46
- /** Append ": N occurrence(s)" after the label (default true). */
47
- showCount?: boolean;
48
- /** Max samples printed per label; omit to print all. */
169
+ /** The client's level for this check, e.g. `ctx.config.todoFixme?.severity`. */
170
+ severity?: Severity;
171
+ /** Level to use when the client configured none. Defaults to `warn`. */
172
+ fallback?: Severity;
173
+ /** Samples printed per label. Defaults to the client's `log.sampleLimit`, else 3. */
49
174
  sampleLimit?: number;
50
- }
51
- /** Log one bucket of findings using the shared check format. */
52
- export declare function logFindings(bucket: FindingBucket, opts: LogOptions): void;
175
+ /** Shorten a label for the summary only, e.g. drop an explanatory tail. */
176
+ summaryLabel?: (label: string) => string;
177
+ /** Appended to the summary, e.g. ` (no ticket reference)`. */
178
+ suffix?: string;
179
+ }): CheckOutcome;
53
180
  interface SeverityReportOptions {
54
181
  /** Log prefix, e.g. 'Diff Security'. */
55
182
  prefix: string;
@@ -67,10 +194,62 @@ interface SeverityReportOptions {
67
194
  * single built-in finding without disabling the whole check.
68
195
  */
69
196
  ignore?: string[];
197
+ /**
198
+ * What a non-empty error bucket means for the run (client `<check>.failOn`):
199
+ * - `error` — block it (`fail`). The default: a finding the check itself rates
200
+ * `error` is meant to gate the merge, which is what a PR gate is for.
201
+ * - `warn` — report but never block, whatever the finding's own severity.
202
+ * - `never` — always `pass`; the findings are still logged.
203
+ *
204
+ * Falls back to `defaults.failOn`, then to `error`.
205
+ */
206
+ failOn?: FailOn;
207
+ /**
208
+ * Per-finding level overrides (client `<check>.severityOverrides`), keyed by a
209
+ * case-insensitive substring of the label. Lets a repo demote one noisy rule,
210
+ * promote one it cares about, or drop a single finding — without turning the
211
+ * whole check off. `off` removes the finding entirely, like `ignore`.
212
+ */
213
+ severityOverrides?: Record<string, 'error' | 'warn' | 'off'>;
214
+ /** Repo-wide fallbacks (client `severityDefaults`), used when the check sets nothing. */
215
+ defaults?: {
216
+ failOn?: FailOn;
217
+ };
218
+ }
219
+ /**
220
+ * What a non-empty error bucket does to the run.
221
+ *
222
+ * Not exported, for the same reason as {@link Severity}: only this module names
223
+ * it, and `tsc` inlines it into `findings.d.ts` regardless.
224
+ */
225
+ type FailOn = 'error' | 'warn' | 'never';
226
+ /** The knobs a check reads straight out of its own block in pr-checkmate.json. */
227
+ interface ClientFindingConfig {
228
+ ignore?: string[];
229
+ failOn?: FailOn;
230
+ severityOverrides?: Record<string, 'error' | 'warn' | 'off'>;
70
231
  }
232
+ /**
233
+ * The half of {@link SeverityReportOptions} every check fills in identically:
234
+ * the client's own `<check>.ignore` / `failOn` / `severityOverrides`, the shared
235
+ * sample limit, and the repo-wide `severityDefaults` fallback.
236
+ *
237
+ * Only the phrasing (`prefix` and the two summaries) is ever check-specific, so
238
+ * spelling the rest out per check meant five lines of copy-paste that had to be
239
+ * kept in step by hand — and a check that forgot one line silently ignored a
240
+ * knob the client had set.
241
+ */
242
+ export declare function clientFindingOptions(ctx: CheckContext, cfg?: ClientFindingConfig): Pick<SeverityReportOptions, 'sampleLimit' | 'ignore' | 'failOn' | 'severityOverrides' | 'defaults'>;
71
243
  /**
72
244
  * Report error/warning finding buckets: log both, then produce the outcome.
73
- * Returns pass() when both buckets are empty, otherwise warn() with a summary.
245
+ *
246
+ * Order is the contract, and each step exists because the one before it is too
247
+ * blunt on its own:
248
+ * 1. `ignore` — drop the finding outright.
249
+ * 2. `severityOverrides` — re-file what survived.
250
+ * 3. `failOn` — decide what a remaining error means for the run.
251
+ * The universal `severity[<check name>]` map is applied later still, by the
252
+ * orchestrator, so a client can always override a check wholesale by name.
74
253
  */
75
254
  export declare function reportSeverityBuckets(errorsIn: FindingBucket, warningsIn: FindingBucket, opts: SeverityReportOptions): CheckOutcome;
76
255
  export {};