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
package/README.md CHANGED
@@ -10,6 +10,7 @@
10
10
  ![cspell](https://img.shields.io/badge/cspell-10-4285F4)
11
11
  ![jscpd](https://img.shields.io/badge/jscpd-5-blue)
12
12
  [![API Docs](https://img.shields.io/badge/API_docs-TypeDoc-9600FF?logo=readthedocs&logoColor=white)](https://pr-checkmate-docs.pages.dev)
13
+ [![Container](https://img.shields.io/badge/ghcr.io-pr--checkmate-2496ED?logo=docker&logoColor=white)](https://github.com/users/Mybono/packages/container/package/pr-checkmate)
13
14
 
14
15
  > PR CheckMate is a security-first PR gate: secret scanning, dependency
15
16
  > vulnerability checks, and a signed SBOM + independent VirusTotal scan on
@@ -121,13 +122,38 @@ npx pr-checkmate all --full
121
122
  npx pr-checkmate lint --full
122
123
  ```
123
124
 
124
- The 16 checks that only make sense on a diff (PR Size, Diff Security, Merge Conflict, Custom
125
- Rules, Banned Imports, the git-diff family) drop out of the report on a full scan. The
125
+ Only three checks genuinely need a diff and drop out of a full scan — PR Size, Missing
126
+ Tests and Lockfile Drift. Everything else, the whole security family included, scans the
127
+ working tree when there is no diff to read. The
126
128
  [per-check reference](https://github.com/Mybono/pr_checkmate_public/blob/main/documentation/checks/INDEX.md)
127
129
  spells out the rules under "Review scope".
128
130
 
129
131
  ### Running in CI without installing
130
132
 
133
+ **Use the container.** With no `node_modules` to reuse, a cold `npx` downloads 547 MB and
134
+ takes about five minutes — on every job. The image carries the whole toolchain already:
135
+
136
+ ```bash
137
+ docker run --rm -v "$PWD:/repo" --user "$(id -u):$(id -g)" \
138
+ ghcr.io/mybono/pr-checkmate:latest all
139
+ ```
140
+
141
+ 161 MB pulled once and cached by the runner, and the checks that need a binary on the
142
+ machine — ShellCheck among them — are inside, so they stop silently skipping. Pin the
143
+ version tag (`:2.0.0`) for a reproducible pipeline; `--user` keeps files that formatters
144
+ rewrite owned by you rather than by root.
145
+
146
+ Verify what you pulled — the image carries build provenance signed by the workflow
147
+ that produced it:
148
+
149
+ ```bash
150
+ gh attestation verify oci://ghcr.io/mybono/pr-checkmate:2 --owner Mybono
151
+ ```
152
+
153
+ [All tags →](https://github.com/users/Mybono/packages/container/package/pr-checkmate)
154
+
155
+ The `npx` route still works, and the rest of this section is about making it comfortable.
156
+
131
157
  Plenty of repos never add pr-checkmate to `package.json` and call it on the fly in CI
132
158
  instead (`npx pr-checkmate`). That works, but there is no `node_modules` to generate a
133
159
  config from, so the run silently falls back to the defaults with no file to tune.
@@ -153,8 +179,9 @@ installed version.
153
179
 
154
180
  ## GitHub Actions
155
181
 
156
- Every workflow follows the same shape: checkout, set up Node, install, run. The
157
- TypeScript setup is the baseline:
182
+ `init` generates this workflow; it is reproduced here so nothing about it is a
183
+ surprise. One job runs the whole registry — which checks run is decided in
184
+ `pr-checkmate.json`, not by copying YAML.
158
185
 
159
186
  ```yaml
160
187
  name: PR CheckMate
@@ -163,21 +190,57 @@ on:
163
190
  branches: [main]
164
191
 
165
192
  permissions:
166
- contents: write
193
+ contents: read
167
194
  pull-requests: write
168
195
 
169
196
  jobs:
170
- quality:
197
+ pr-checkmate:
171
198
  runs-on: ubuntu-latest
172
199
  steps:
173
- - uses: actions/checkout@v4
200
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
174
201
  with: { fetch-depth: 0 }
175
- - uses: actions/setup-node@v4
176
- with: { node-version: 24 }
202
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
203
+ with: { node-version: 24, cache: 'npm' }
177
204
  - run: npm ci
178
- - run: npx pr-checkmate all
205
+ - name: Run PR CheckMate
206
+ env:
207
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
208
+ run: npx pr-checkmate all
179
209
  ```
180
210
 
211
+ Three lines are load-bearing:
212
+
213
+ - `fetch-depth: 0` — without the full history the PR base is not in the clone, no
214
+ diff range can be built, and every diff-based check skips itself. The run says so
215
+ (`Scan Scope: … set fetch-depth: 0`) rather than reporting a clean pass.
216
+ - `GITHUB_TOKEN` — Actions does not put it in the environment on its own. Without it
217
+ the checks still run, but the summary comment never reaches the PR.
218
+ - no `continue-on-error` — a gate that cannot fail the job is not a gate.
219
+
220
+ ### Adopting it on an existing codebase
221
+
222
+ The instinct on an existing codebase is to soften the workflow. Do the opposite: keep
223
+ the gate real and relax individual checks in `pr-checkmate.json`, so whatever you have
224
+ already made clean stays enforced.
225
+
226
+ ```jsonc
227
+ {
228
+ // everything advisory to begin with…
229
+ "severityDefaults": { "failOn": "warn" },
230
+ // …except the ones you are ready to enforce
231
+ "severity": {
232
+ "Security Scan": "error",
233
+ "Diff Security": "error",
234
+ "Duplicate Code": "off"
235
+ }
236
+ }
237
+ ```
238
+
239
+ Promote checks one at a time as the codebase catches up. `pr-checkmate init` also
240
+ offers an advisory workflow if you would rather start there, and it writes a banner
241
+ explaining how to leave that mode. Either way, `init` never overwrites a workflow file
242
+ that already exists.
243
+
181
244
  For a runner-dependency language, add its toolchain setup step before the run: Go with
182
245
  `actions/setup-go`, Rust with `dtolnay/rust-toolchain`, C# with `actions/setup-dotnet`,
183
246
  Ruby with `ruby/setup-ruby` (plus `gem install rubocop`), PHP with
@@ -209,8 +272,21 @@ covering when it runs, the config keys it reads, and how to turn it off.
209
272
 
210
273
  ## Configuration
211
274
 
212
- Everything lives in `pr-checkmate.json`. Run `npx pr-checkmate init` to scaffold the
213
- full file for your detected languages.
275
+ Everything lives in one file, `pr-checkmate.json`. Run `npx pr-checkmate init` to
276
+ scaffold it for your detected languages.
277
+
278
+ The generated file is short on purpose: it holds your decisions plus a few empty
279
+ blocks for the settings people tune most. Everything else stays implicit — the
280
+ config is merged over the built-in defaults at load time, so new defaults reach
281
+ you on upgrade without editing anything, and `init` is a first-run step rather
282
+ than something to repeat after every release.
283
+
284
+ **[The full configuration, annotated, with every option at its default →](https://github.com/Mybono/pr_checkmate_public/blob/main/pr-checkmate.json)**
285
+ Copy the block you need into your file. The `$schema` line in the generated
286
+ config also gives your editor autocomplete and a description for every key.
287
+
288
+ Three settings REPLACE their default rather than extending it, so copy the
289
+ default before editing: `ignoreDirs`, `duplicate.ignore`, `deadCode.ignoreFiles`.
214
290
 
215
291
  - Severity map. The one universal control. Key any check by its display name (as it
216
292
  appears in the report) and set `"error"` to fail the run, `"warn"` for advisory, or
@@ -272,4 +348,4 @@ affect it.
272
348
 
273
349
  LicenseRef Proprietary © 2025-2026 Artur Polishchuk. Free to install and run via the
274
350
  published npm package (see [LICENSE](LICENSE) for the exact grant); the source is not
275
- open-source and is not licensed for copying, modification, or redistribution.
351
+ open-source and is not licensed for copying, modification, or redistribution.
package/cspell.json CHANGED
@@ -1,13 +1,6 @@
1
1
  {
2
2
  "version": "0.2",
3
- "language": "en,ru,uk,he,fr,es",
4
- "import": [
5
- "@cspell/dict-ru_ru/cspell-ext.json",
6
- "@cspell/dict-uk-ua/cspell-ext.json",
7
- "@cspell/dict-he/cspell-ext.json",
8
- "@cspell/dict-fr-fr/cspell-ext.json",
9
- "@cspell/dict-es-es/cspell-ext.json"
10
- ],
3
+ "language": "en,en-GB",
11
4
  "words": [
12
5
  "absdiff",
13
6
  "abspath",
@@ -467,6 +460,7 @@
467
460
  "eficiente",
468
461
  "eigen",
469
462
  "EIGEN",
463
+ "EINTEGRITY",
470
464
  "EKNFFL",
471
465
  "elbert",
472
466
  "elementwise",
@@ -1057,6 +1051,7 @@
1057
1051
  "oaep",
1058
1052
  "Obagi",
1059
1053
  "objdetect",
1054
+ "Octo",
1060
1055
  "octocat",
1061
1056
  "ohlc",
1062
1057
  "ohlcv",
@@ -1757,7 +1752,14 @@
1757
1752
  "/**/yarn.lock",
1758
1753
  "/**/pnpm-lock.yaml",
1759
1754
  "/**/*.min.js",
1760
- "/**/*.min.css"
1755
+ "/**/*.min.css",
1756
+ "/CHANGELOG.md",
1757
+ "/**/__tests__/**",
1758
+ "/**/__mocks__/**",
1759
+ "/**/*.test.*",
1760
+ "/**/*.spec.*",
1761
+ "/**/test/**",
1762
+ "/**/tests/**"
1761
1763
  ],
1762
1764
  "enableFiletypes": ["typescript", "javascript", "markdown", "json"],
1763
1765
  "ignoreRegExpList": ["/https?:\\/\\/[^\\s]+/g", "/\\b[0-9a-f]{7,40}\\b/gi"]
package/dist/cli.js CHANGED
@@ -2,66 +2,10 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const core_1 = require("./core");
5
+ const commands_1 = require("./core/commands");
6
+ const doctor_1 = require("./core/install/doctor");
5
7
  const init_1 = require("./init");
6
8
  const utils_1 = require("./utils");
7
- /** Maps short CLI command names to their registry check names. */
8
- const COMMAND_TO_CHECK = {
9
- lint: 'ESLint',
10
- deps: 'Dependencies',
11
- dependencies: 'Dependencies',
12
- 'npm-audit': 'NPM Audit',
13
- prettier: 'Prettier',
14
- spellcheck: 'Spellcheck',
15
- security: 'Security Scan',
16
- circular: 'Circular Deps',
17
- license: 'License Check',
18
- 'pr-size': 'PR Size',
19
- 'pr-title': 'PR Title',
20
- outdated: 'Outdated Deps',
21
- markdown: 'Markdown',
22
- 'yaml-lint': 'YAML Lint',
23
- symlinks: 'Broken Symlinks',
24
- shellcheck: 'ShellCheck',
25
- typecheck: 'TypeScript',
26
- 'diff-smell': 'Diff Smells',
27
- 'diff-security': 'Diff Security',
28
- 'workflow-security': 'Workflow Security',
29
- 'custom-rules': 'Custom Rules',
30
- 'banned-imports': 'Banned Imports',
31
- 'license-header': 'License Header',
32
- 'config-validation': 'Config Validation',
33
- 'dockerfile-security': 'Dockerfile Security',
34
- 'case-collision': 'Case Collision',
35
- 'vuln-scan': 'Vuln Scan',
36
- 'leftover-debug': 'Leftover Debug',
37
- 'lockfile-drift': 'Lockfile Drift',
38
- 'large-files': 'Large Files',
39
- 'todo-fixme': 'TODO/FIXME',
40
- 'missing-tests': 'Missing Tests',
41
- coverage: 'Coverage',
42
- 'migration-safety': 'Migration Safety',
43
- 'sensitive-files': 'Sensitive Files',
44
- 'pr-body': 'PR Body',
45
- duplicate: 'Duplicate Code',
46
- commitlint: 'Commitlint',
47
- 'cpp-format': 'C++ Format',
48
- 'python-lint': 'Ruff Lint',
49
- 'python-format': 'Ruff Format',
50
- 'python-typecheck': 'Python Types',
51
- 'go-lint': 'Go Vet',
52
- 'go-format': 'Go Format',
53
- 'rust-lint': 'Clippy',
54
- 'rust-format': 'Rustfmt',
55
- 'csharp-format': 'C# Format',
56
- 'ruby-lint': 'RuboCop',
57
- 'php-format': 'PHP CS Fixer',
58
- };
59
- /** Commands that run the whole pipeline (or no pipeline at all) and take no siblings. */
60
- const STANDALONE_COMMANDS = ['init', 'all', 'precommit'];
61
- /** Flags accepted anywhere on the command line, with what each one means. */
62
- const FLAGS = {
63
- '--full': 'review every tracked file instead of the diff (the default outside CI)',
64
- };
65
9
  const argv = process.argv.slice(2);
66
10
  /**
67
11
  * Flags are separated from positional arguments so they compose with every command
@@ -77,10 +21,10 @@ const cmd = args[0];
77
21
  * diff and print a green summary.
78
22
  */
79
23
  function rejectUnknownFlags() {
80
- const unknown = flags.filter(flag => !FLAGS[flag]);
24
+ const unknown = flags.filter(flag => !commands_1.FLAGS[flag]);
81
25
  if (unknown.length === 0)
82
26
  return;
83
- const known = Object.entries(FLAGS)
27
+ const known = Object.entries(commands_1.FLAGS)
84
28
  .map(([flag, help]) => `${flag} (${help})`)
85
29
  .join(', ');
86
30
  utils_1.logger.error(`[cli]: Unknown flag(s) "${unknown.join('", "')}". Available: ${known}`);
@@ -101,6 +45,17 @@ function rejectConflictingScope() {
101
45
  utils_1.logger.error(`[cli]: "init" takes no flags, got: ${flags.join(', ')}.`);
102
46
  process.exit(1);
103
47
  }
48
+ // `--all` belongs to `install` alone. Accepting it elsewhere would look like a
49
+ // scope flag and quietly do nothing, which is the failure mode
50
+ // `rejectUnknownFlags` exists to prevent.
51
+ if (flags.includes('--all') && cmd !== 'install') {
52
+ utils_1.logger.error('[cli]: "--all" applies only to "install". Did you mean "--full"?');
53
+ process.exit(1);
54
+ }
55
+ if (flags.includes('--full') && commands_1.TOOLING_COMMANDS.includes(cmd ?? '')) {
56
+ utils_1.logger.error(`[cli]: "${cmd}" manages tooling and reviews nothing, so "--full" has no meaning.`);
57
+ process.exit(1);
58
+ }
104
59
  }
105
60
  const full = flags.includes('--full');
106
61
  /**
@@ -113,7 +68,7 @@ const full = flags.includes('--full');
113
68
  * added to the list but not to the branch.
114
69
  */
115
70
  function rejectExtraArgs() {
116
- if (!cmd || !STANDALONE_COMMANDS.includes(cmd) || args.length <= 1)
71
+ if (!cmd || !commands_1.STANDALONE_COMMANDS.includes(cmd) || args.length <= 1)
117
72
  return;
118
73
  utils_1.logger.error(`[cli]: "${cmd}" takes no other arguments, got: ${args.slice(1).join(', ')}. Run them as a separate command.`);
119
74
  process.exit(1);
@@ -127,6 +82,24 @@ void (async () => {
127
82
  await (0, init_1.init)();
128
83
  process.exit(0);
129
84
  }
85
+ // Tooling management. These run no checks — they prepare, inspect or clean
86
+ // up what the checks need, so they never touch the diff or the reporter.
87
+ if (cmd === 'install') {
88
+ const results = await (0, doctor_1.installProfiles)(process.cwd(), flags.includes('--all'));
89
+ const failed = results.filter(r => r.status === 'failed');
90
+ for (const result of failed) {
91
+ utils_1.logger.error(`[install]: ❌ ${result.profile}: ${result.reason}`);
92
+ }
93
+ process.exit(failed.length > 0 ? 1 : 0);
94
+ }
95
+ if (cmd === 'doctor') {
96
+ await (0, doctor_1.runDoctor)();
97
+ process.exit(0);
98
+ }
99
+ if (cmd === 'cache-prune') {
100
+ (0, doctor_1.pruneCache)();
101
+ process.exit(0);
102
+ }
130
103
  // Full run: auto-detect the CI platform (GitHub/GitLab/Bitbucket), post the
131
104
  // summary to its PR/MR, and allow formatters to write.
132
105
  if (!cmd || cmd === 'all') {
@@ -150,12 +123,12 @@ void (async () => {
150
123
  // Every remaining argument is a check name, so `pr-checkmate lint spellcheck`
151
124
  // runs both in one pass. Unknown names abort the whole run — a typo must never
152
125
  // shrink the requested check set silently.
153
- const unknown = args.filter(arg => !COMMAND_TO_CHECK[arg]);
126
+ const unknown = args.filter(arg => !commands_1.COMMAND_TO_CHECK[arg]);
154
127
  if (unknown.length > 0) {
155
- utils_1.logger.error(`[cli]: Unknown command(s) "${unknown.join('", "')}". Available: ${STANDALONE_COMMANDS.join(', ')}, ${Object.keys(COMMAND_TO_CHECK).join(', ')}`);
128
+ utils_1.logger.error(`[cli]: Unknown command(s) "${unknown.join('", "')}". Available: ${commands_1.STANDALONE_COMMANDS.join(', ')}, ${Object.keys(commands_1.COMMAND_TO_CHECK).join(', ')}`);
156
129
  process.exit(1);
157
130
  }
158
- const checkNames = [...new Set(args.map(arg => COMMAND_TO_CHECK[arg]))];
131
+ const checkNames = [...new Set(args.map(arg => commands_1.COMMAND_TO_CHECK[arg]))];
159
132
  const report = await (0, core_1.runChecks)({ only: checkNames, reporter: 'stdout', write: true, full });
160
133
  process.exit(report.ok ? 0 : 1);
161
134
  }
@@ -17,6 +17,18 @@ export declare const IGNORED_DIRS: string[];
17
17
  * nested report globs that are not plain directory names.
18
18
  */
19
19
  export declare const IGNORE_PATTERNS: string[];
20
+ /**
21
+ * Where test code lives, as globs.
22
+ *
23
+ * Deliberately NOT part of {@link IGNORED_DIRS}: that list hides a directory from
24
+ * every check, and test code still wants linting, type-checking and formatting.
25
+ * What test code does not want is the handful of checks whose findings are noise
26
+ * on it — duplication (setup boilerplate repeats by design), unused exports (the
27
+ * runner consumes them), spelling (fixture identifiers) and the diff security
28
+ * scan (a test's whole job may be to call the dangerous thing). Each of those
29
+ * defaults to this list, and each lets a client replace it.
30
+ */
31
+ export declare const TEST_PATH_GLOBS: string[];
20
32
  /**
21
33
  * Test files/folders excluded from duplicate-code detection by default.
22
34
  * Test suites legitimately repeat setup/arrange/assert boilerplate, so counting
@@ -24,6 +36,24 @@ export declare const IGNORE_PATTERNS: string[];
24
36
  * override this list via `duplicate.ignore` in pr-checkmate.json.
25
37
  */
26
38
  export declare const DUPLICATE_IGNORE_DEFAULTS: string[];
39
+ /**
40
+ * Paths the diff security scan skips by default.
41
+ *
42
+ * A security test corpus is built out of the exact constructs the scan hunts for
43
+ * — `eval()`, `document.write()`, `__proto__` — so scanning it turns every added
44
+ * test case into a finding, and under `severity: error` into a blocked merge.
45
+ * Clients can override via `diffSecurity.ignorePaths` (which REPLACES this list).
46
+ */
47
+ export declare const DIFF_SECURITY_IGNORE_DEFAULTS: string[];
48
+ /**
49
+ * Files written by a release tool rather than by a person.
50
+ *
51
+ * `CHANGELOG.md` is assembled from commit subjects, so every typo anyone has ever
52
+ * committed reappears in it — 39 of 42 spellcheck findings in one real repository
53
+ * came from that one file, none of them actionable: the text cannot be corrected
54
+ * without rewriting git history, and the next release regenerates it anyway.
55
+ */
56
+ export declare const GENERATED_DOC_FILES: string[];
27
57
  /**
28
58
  * Files excluded from the dead-code (unused-exports) report by default. These are
29
59
  * REGEX sources (matched against each reported file path), not globs — dead-code's
@@ -70,12 +100,30 @@ export declare const DEFAULT_COMMANDS: {
70
100
  /**
71
101
  * Packages allow-listed for the license check by default.
72
102
  *
73
- * These are pr-checkmate's own bundled cspell dictionaries. Some ship under
74
- * copyleft licenses (GPL/LGPL), but they are pr-checkmate dependencies — not
75
- * the consumer's — so clients should never have to allow-list them manually.
103
+ * These are the optional cspell language dictionaries. Some ship under copyleft
104
+ * licenses (GPL/LGPL), and a client who installs one to spellcheck their own
105
+ * language should not have that turn into a licence finding: the dictionary is
106
+ * data for our check, not code they ship.
76
107
  */
77
108
  export declare const DEFAULT_ALLOW_PACKAGES: string[];
109
+ /**
110
+ * Canonical entry point to the PR CheckMate docs. Single source of truth: it is
111
+ * written into every generated `pr-checkmate.json` (via {@link documentation})
112
+ * and printed in the "new version available" notice, so the two can never drift.
113
+ */
114
+ export declare const DOCS_URL = "https://github.com/Mybono/pr_checkmate_public/tree/main/documentation";
115
+ /**
116
+ * The fully annotated configuration, with every option at its default value.
117
+ *
118
+ * A URL rather than a file inside the package on purpose: half the audience runs
119
+ * pr-checkmate through `npx` on a pipeline and has no `node_modules` at all, so
120
+ * a path into the installed package is a dead end for them. The release workflow
121
+ * publishes `src/config/pr-checkmate.template.json` to the public mirror under
122
+ * this name, which keeps one copy for everyone.
123
+ */
124
+ export declare const FULL_CONFIG_URL = "https://github.com/Mybono/pr_checkmate_public/blob/main/pr-checkmate.json";
78
125
  export declare const documentation: {
126
+ pr_checkmate: string;
79
127
  eslint: string;
80
128
  typescriptEslint: string;
81
129
  prettier: string;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.KNOWN_CONFIG_KEYS = exports.CONFIG_FILE = exports.documentation = exports.DEFAULT_ALLOW_PACKAGES = exports.DEFAULT_COMMANDS = exports.SOURCE_EXTENSIONS = exports.DUPLICATE_CODE_FORMATS = exports.CONFIG_FILE_PATTERNS = exports.DEAD_CODE_IGNORE_DEFAULTS = exports.DUPLICATE_IGNORE_DEFAULTS = exports.IGNORE_PATTERNS = exports.IGNORED_DIRS = void 0;
6
+ exports.KNOWN_CONFIG_KEYS = exports.CONFIG_FILE = exports.documentation = exports.FULL_CONFIG_URL = exports.DOCS_URL = exports.DEFAULT_ALLOW_PACKAGES = exports.DEFAULT_COMMANDS = exports.SOURCE_EXTENSIONS = exports.DUPLICATE_CODE_FORMATS = exports.CONFIG_FILE_PATTERNS = exports.DEAD_CODE_IGNORE_DEFAULTS = exports.GENERATED_DOC_FILES = exports.DIFF_SECURITY_IGNORE_DEFAULTS = exports.DUPLICATE_IGNORE_DEFAULTS = exports.TEST_PATH_GLOBS = exports.IGNORE_PATTERNS = exports.IGNORED_DIRS = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
8
  /**
9
9
  * Build output, dependency, tooling, and report directories that are never
@@ -86,6 +86,11 @@ exports.IGNORED_DIRS = [
86
86
  'allure-results',
87
87
  'playwright-report',
88
88
  '.wdio',
89
+ // Generated knowledge-graph output. Committed by repos that keep the graph in
90
+ // version control, and machine-written from end to end: a multi-megabyte
91
+ // `graph.json` trips the large-file check, and `GRAPH_REPORT.md` carries every
92
+ // identifier in the codebase past the spellchecker. Neither is anyone's source.
93
+ 'graphify-out',
89
94
  ];
90
95
  /**
91
96
  * Glob form of the ignore list, for tools that take glob ignores directly
@@ -110,12 +115,17 @@ exports.IGNORE_PATTERNS = [
110
115
  '**/audit-report/**',
111
116
  ];
112
117
  /**
113
- * Test files/folders excluded from duplicate-code detection by default.
114
- * Test suites legitimately repeat setup/arrange/assert boilerplate, so counting
115
- * them as clones is noise that drowns out real production duplication. Clients can
116
- * override this list via `duplicate.ignore` in pr-checkmate.json.
118
+ * Where test code lives, as globs.
119
+ *
120
+ * Deliberately NOT part of {@link IGNORED_DIRS}: that list hides a directory from
121
+ * every check, and test code still wants linting, type-checking and formatting.
122
+ * What test code does not want is the handful of checks whose findings are noise
123
+ * on it — duplication (setup boilerplate repeats by design), unused exports (the
124
+ * runner consumes them), spelling (fixture identifiers) and the diff security
125
+ * scan (a test's whole job may be to call the dangerous thing). Each of those
126
+ * defaults to this list, and each lets a client replace it.
117
127
  */
118
- exports.DUPLICATE_IGNORE_DEFAULTS = [
128
+ exports.TEST_PATH_GLOBS = [
119
129
  '**/__tests__/**',
120
130
  '**/__mocks__/**',
121
131
  '**/*.test.*',
@@ -123,6 +133,31 @@ exports.DUPLICATE_IGNORE_DEFAULTS = [
123
133
  '**/test/**',
124
134
  '**/tests/**',
125
135
  ];
136
+ /**
137
+ * Test files/folders excluded from duplicate-code detection by default.
138
+ * Test suites legitimately repeat setup/arrange/assert boilerplate, so counting
139
+ * them as clones is noise that drowns out real production duplication. Clients can
140
+ * override this list via `duplicate.ignore` in pr-checkmate.json.
141
+ */
142
+ exports.DUPLICATE_IGNORE_DEFAULTS = [...exports.TEST_PATH_GLOBS];
143
+ /**
144
+ * Paths the diff security scan skips by default.
145
+ *
146
+ * A security test corpus is built out of the exact constructs the scan hunts for
147
+ * — `eval()`, `document.write()`, `__proto__` — so scanning it turns every added
148
+ * test case into a finding, and under `severity: error` into a blocked merge.
149
+ * Clients can override via `diffSecurity.ignorePaths` (which REPLACES this list).
150
+ */
151
+ exports.DIFF_SECURITY_IGNORE_DEFAULTS = [...exports.TEST_PATH_GLOBS];
152
+ /**
153
+ * Files written by a release tool rather than by a person.
154
+ *
155
+ * `CHANGELOG.md` is assembled from commit subjects, so every typo anyone has ever
156
+ * committed reappears in it — 39 of 42 spellcheck findings in one real repository
157
+ * came from that one file, none of them actionable: the text cannot be corrected
158
+ * without rewriting git history, and the next release regenerates it anyway.
159
+ */
160
+ exports.GENERATED_DOC_FILES = ['CHANGELOG.md'];
126
161
  /**
127
162
  * Files excluded from the dead-code (unused-exports) report by default. These are
128
163
  * REGEX sources (matched against each reported file path), not globs — dead-code's
@@ -180,8 +215,8 @@ exports.SOURCE_EXTENSIONS = {
180
215
  all: ['.ts', '.tsx', '.js', '.jsx', '.json', '.md', '.mdx'],
181
216
  };
182
217
  exports.DEFAULT_COMMANDS = {
183
- 'npx pr-checkmate init': 'Update pr-checkmate.json',
184
- 'npx pr-checkmate all': 'Run all checks (the PR diff in CI, every tracked file locally)',
218
+ 'npx pr-checkmate init': 'Create pr-checkmate.json and the CI workflow (first-run setup)',
219
+ 'npx pr-checkmate all': 'Run all checks over the changes — the PR diff in CI, your branch changes locally',
185
220
  'npx pr-checkmate all --full': 'Run all checks over every tracked file, CI included',
186
221
  'npx pr-checkmate lint': 'Lint code using ESLint',
187
222
  'npx pr-checkmate prettier': 'Format code using Prettier',
@@ -197,9 +232,10 @@ exports.DEFAULT_COMMANDS = {
197
232
  /**
198
233
  * Packages allow-listed for the license check by default.
199
234
  *
200
- * These are pr-checkmate's own bundled cspell dictionaries. Some ship under
201
- * copyleft licenses (GPL/LGPL), but they are pr-checkmate dependencies — not
202
- * the consumer's — so clients should never have to allow-list them manually.
235
+ * These are the optional cspell language dictionaries. Some ship under copyleft
236
+ * licenses (GPL/LGPL), and a client who installs one to spellcheck their own
237
+ * language should not have that turn into a licence finding: the dictionary is
238
+ * data for our check, not code they ship.
203
239
  */
204
240
  exports.DEFAULT_ALLOW_PACKAGES = [
205
241
  '@cspell/dict-es-es',
@@ -208,7 +244,24 @@ exports.DEFAULT_ALLOW_PACKAGES = [
208
244
  '@cspell/dict-ru_ru',
209
245
  '@cspell/dict-uk-ua',
210
246
  ];
247
+ /**
248
+ * Canonical entry point to the PR CheckMate docs. Single source of truth: it is
249
+ * written into every generated `pr-checkmate.json` (via {@link documentation})
250
+ * and printed in the "new version available" notice, so the two can never drift.
251
+ */
252
+ exports.DOCS_URL = 'https://github.com/Mybono/pr_checkmate_public/tree/main/documentation';
253
+ /**
254
+ * The fully annotated configuration, with every option at its default value.
255
+ *
256
+ * A URL rather than a file inside the package on purpose: half the audience runs
257
+ * pr-checkmate through `npx` on a pipeline and has no `node_modules` at all, so
258
+ * a path into the installed package is a dead end for them. The release workflow
259
+ * publishes `src/config/pr-checkmate.template.json` to the public mirror under
260
+ * this name, which keeps one copy for everyone.
261
+ */
262
+ exports.FULL_CONFIG_URL = 'https://github.com/Mybono/pr_checkmate_public/blob/main/pr-checkmate.json';
211
263
  exports.documentation = {
264
+ pr_checkmate: exports.DOCS_URL,
212
265
  eslint: 'https://eslint.org/docs/latest/rules/',
213
266
  typescriptEslint: 'https://typescript-eslint.io/rules/',
214
267
  prettier: 'https://prettier.io/docs/en/options.html',
@@ -227,8 +280,15 @@ exports.KNOWN_CONFIG_KEYS = new Set([
227
280
  // core
228
281
  'sourcePath',
229
282
  'ignoreDirs',
283
+ 'excludePaths',
230
284
  'languages',
285
+ 'detect',
286
+ 'install',
231
287
  'severity',
288
+ 'severityDefaults',
289
+ 'run',
290
+ 'reporter',
291
+ 'log',
232
292
  'customRules',
233
293
  '$schema',
234
294
  'commands',
@@ -242,6 +302,8 @@ exports.KNOWN_CONFIG_KEYS = new Set([
242
302
  'cspell',
243
303
  'markdownlint',
244
304
  'yamlLint',
305
+ 'shellcheck',
306
+ 'symlinks',
245
307
  'typecheck',
246
308
  // security
247
309
  'security',
@@ -0,0 +1,79 @@
1
+ name: PR CheckMate
2
+
3
+ # The container variant, scaffolded because this repository does not carry
4
+ # pr-checkmate as a dependency.
5
+ #
6
+ # The alternative would be `npx pr-checkmate`, and with no node_modules to reuse
7
+ # that resolves to whatever is newest on the registry and downloads it again on
8
+ # every job: 547 MB and about five minutes, measured. Worse, a major release
9
+ # would land on you without a line changing here — and a major release of a gate
10
+ # can start failing builds that passed yesterday.
11
+ #
12
+ # The image is pulled once and cached by the runner, and the tag below pins the
13
+ # major so nothing moves under you.
14
+
15
+ on:
16
+ pull_request:
17
+ branches: [main, master, develop]
18
+ types: [opened, synchronize, reopened]
19
+
20
+ # Least privilege. `pull-requests: write` is what lets the run upsert its summary
21
+ # comment on the PR; drop it and the checks still run, they just report to the log.
22
+ permissions:
23
+ contents: read
24
+ pull-requests: write
25
+
26
+ # Push three times in a minute and the first two runs become answers to questions
27
+ # nobody is asking any more — they still hold a runner, and they still race to
28
+ # upsert the summary comment, so a stale verdict can land on top of a fresh one.
29
+ # Keyed per PR, so runs on different pull requests never cancel each other.
30
+ concurrency:
31
+ group: pr-checkmate-${{ github.workflow }}-${{ github.ref }}
32
+ cancel-in-progress: true
33
+
34
+ jobs:
35
+ pr-checkmate:
36
+ name: PR CheckMate
37
+ runs-on: ubuntu-latest
38
+ # A hung step otherwise runs to GitHub's six-hour default before anyone hears
39
+ # about it. Every check has its own internal timeout; this is the backstop.
40
+ timeout-minutes: 20
41
+
42
+ steps:
43
+ - name: Checkout code
44
+ uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
45
+ with:
46
+ # Required, not optional. Without the full history the PR base commit is
47
+ # not in the clone, no diff range can be built, and every diff-based
48
+ # check skips itself. The run then says `Scan Scope: … set
49
+ # fetch-depth: 0`, which is this line.
50
+ fetch-depth: 0
51
+
52
+ # One job runs the whole registry. Which checks run is decided in
53
+ # pr-checkmate.json (`severity`), not by splitting this into jobs.
54
+ #
55
+ # `--user` is not a detail: without it every file a formatter rewrites comes
56
+ # back owned by root, and the next step in your pipeline fails on
57
+ # permissions long after this one reported success.
58
+ #
59
+ # The environment variables are passed through by name rather than by value:
60
+ # the run reads them to find the PR, and interpolating them into the command
61
+ # line would put their contents into a shell.
62
+ - name: Run PR CheckMate
63
+ env:
64
+ # Not injected automatically: without this the run cannot authenticate,
65
+ # and the summary comment silently never reaches the PR.
66
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67
+ run: |
68
+ docker run --rm \
69
+ -v "$PWD:/repo" \
70
+ --user "$(id -u):$(id -g)" \
71
+ -e GITHUB_TOKEN \
72
+ -e GITHUB_REPOSITORY \
73
+ -e GITHUB_EVENT_NAME \
74
+ -e GITHUB_EVENT_PATH \
75
+ -e GITHUB_REF \
76
+ -e GITHUB_SHA \
77
+ -e CI \
78
+ -v "$GITHUB_EVENT_PATH:$GITHUB_EVENT_PATH:ro" \
79
+ ghcr.io/mybono/pr-checkmate:__IMAGE_TAG__ all