pr-checkmate 1.21.15 → 1.22.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/.eslintrc.js CHANGED
@@ -41,7 +41,7 @@ module.exports = {
41
41
  sourceType: 'module',
42
42
  project: './tsconfig.json',
43
43
  },
44
- plugins: ['@typescript-eslint', 'prettier', 'import', 'jsdoc'],
44
+ plugins: ['@typescript-eslint', 'prettier', 'import-x', 'jsdoc'],
45
45
  extends: [
46
46
  'eslint:recommended',
47
47
  'plugin:@typescript-eslint/recommended',
@@ -83,14 +83,14 @@ module.exports = {
83
83
  // Complexity analysis (#61)
84
84
  complexity: ['warn', 15],
85
85
  // Import ordering (#65)
86
- 'import/order': [
86
+ 'import-x/order': [
87
87
  'warn',
88
88
  {
89
89
  groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
90
90
  alphabetize: { order: 'asc', caseInsensitive: true },
91
91
  },
92
92
  ],
93
- 'import/no-duplicates': 'error',
93
+ 'import-x/no-duplicates': 'error',
94
94
  // Documentation (#62)
95
95
  'jsdoc/require-jsdoc': [
96
96
  'warn',
package/README.md CHANGED
@@ -4,6 +4,12 @@
4
4
  ![npm](https://img.shields.io/npm/v/pr-checkmate?color=blue&label=npm)
5
5
  ![License](https://img.shields.io/badge/license-Proprietary-lightgrey)
6
6
 
7
+ ![ESLint](https://img.shields.io/badge/ESLint-10-4B32C3?logo=eslint&logoColor=white)
8
+ ![Prettier](https://img.shields.io/badge/Prettier-3-F7B93E?logo=prettier&logoColor=white)
9
+ ![TypeScript](https://img.shields.io/badge/TypeScript-6-3178C6?logo=typescript&logoColor=white)
10
+ ![cspell](https://img.shields.io/badge/cspell-10-4285F4)
11
+ ![jscpd](https://img.shields.io/badge/jscpd-5-blue)
12
+
7
13
  > **PR CheckMate** is a universal PR quality checker that runs as a single `npm install` and works across **TypeScript, JavaScript, Python, C++, Swift, and Android/Kotlin** repositories — no per-language toolchain setup required.
8
14
 
9
15
  ---
@@ -280,7 +286,7 @@ In GitHub Actions PR context, language checks automatically switch to **delta mo
280
286
 
281
287
  After `npx pr-checkmate all`, a summary table is posted to the PR. On repeated pushes the comment is updated in place — no duplicate comments.
282
288
 
283
- ```
289
+ ```text
284
290
  ## PR CheckMate Summary
285
291
 
286
292
  11 passed · 3 warnings · 0 failed · 0 skipped
package/cspell.json CHANGED
@@ -189,6 +189,18 @@
189
189
  "sultansadikov",
190
190
  "telaviv",
191
191
  "travis",
192
+ "kotlin",
193
+ "kucherenko",
194
+ "opencollective",
195
+ "Werror",
196
+ "positiving",
197
+ "pyflakes",
198
+ "pycodestyle",
199
+ "swiftlint",
200
+ "pathspec",
201
+ "Pipfile",
202
+ "conanfile",
203
+ "ktlint",
192
204
  "Términos",
193
205
  "Teudat",
194
206
  "Tiempo",
@@ -543,7 +555,145 @@
543
555
  "Makefiles",
544
556
  "dpdm",
545
557
  "commitlintrc",
546
- "shortstat"
558
+ "shortstat",
559
+ "Cooldown",
560
+ "SCRIPTERROR",
561
+ "attendace",
562
+ "attendance",
563
+ "kupat",
564
+ "gemel",
565
+ "hishtalmut",
566
+ "keren",
567
+ "tofes",
568
+ "Bdikat",
569
+ "Hishuv",
570
+ "formcontrolname",
571
+ "zrufot",
572
+ "Klali",
573
+ "contenteditable",
574
+ "האניקומב",
575
+ "ishur",
576
+ "Yeshut",
577
+ "Nihul",
578
+ "pratim",
579
+ "nispahim",
580
+ "networkidle",
581
+ "domcontentloaded",
582
+ "secapp",
583
+ "taxinfo",
584
+ "gmishurim",
585
+ "ezor",
586
+ "ishi",
587
+ "ishurim",
588
+ "nikui",
589
+ "mekabel",
590
+ "shdochshana",
591
+ "gershayim",
592
+ "Rashum",
593
+ "Shem",
594
+ "Mishpaha",
595
+ "Rshum",
596
+ "Prati",
597
+ "Ktovet",
598
+ "Doar",
599
+ "Megurim",
600
+ "ddlkidomet",
601
+ "Aher",
602
+ "Esek",
603
+ "Isuk",
604
+ "Mazav",
605
+ "Mish",
606
+ "Beyachad",
607
+ "Shlichat",
608
+ "Neezarti",
609
+ "Doch",
610
+ "Klalim",
611
+ "Hehzer",
612
+ "Ozer",
613
+ "נייע",
614
+ "recognised",
615
+ "קולומייץ",
616
+ "אירינה",
617
+ "סרגיי",
618
+ "פולישוק",
619
+ "ולדימיר",
620
+ "moov",
621
+ "virtualenv",
622
+ "pytesseract",
623
+ "qrdet",
624
+ "Jsons",
625
+ "easyocr",
626
+ "EXIF",
627
+ "fliplr",
628
+ "noautorotate",
629
+ "timestep",
630
+ "VAQH",
631
+ "DSBL",
632
+ "MAXU",
633
+ "XCUI",
634
+ "Apks",
635
+ "bootstatus",
636
+ "BEGINSWITH",
637
+ "anypassword",
638
+ "wrongpassword",
639
+ "xmark",
640
+ "didnt",
641
+ "Anthropic",
642
+ "bartowski",
643
+ "specialisation",
644
+ "cursorrules",
645
+ "deepseek",
646
+ "GGUF",
647
+ "hf",
648
+ "llm",
649
+ "machdep",
650
+ "migrat",
651
+ "modelcontextprotocol",
652
+ "mxbai",
653
+ "noheader",
654
+ "codedb",
655
+ "nomic",
656
+ "nounits",
657
+ "nvidia",
658
+ "ollama",
659
+ "qwen",
660
+ "shellcheck",
661
+ "standards",
662
+ "vram",
663
+ "ATATT",
664
+ "ATSTT",
665
+ "AUTOINCREMENT",
666
+ "BEAGENT",
667
+ "CEREBRAS",
668
+ "cpucore",
669
+ "Fingerprinter",
670
+ "FREELLM",
671
+ "ircular",
672
+ "integrat",
673
+ "LOOKBACK",
674
+ "markitdown",
675
+ "memswap",
676
+ "nknown",
677
+ "processlist",
678
+ "unixepoch",
679
+ "unreviewed",
680
+ "xapp",
681
+ "bpoa",
682
+ "installroot",
683
+ "dedups",
684
+ "behaviourally",
685
+ "normalised",
686
+ "раннера",
687
+ "раннере",
688
+ "пайплайн",
689
+ "dind",
690
+ "dlib",
691
+ "pugixml",
692
+ "opencvcommit",
693
+ "litert",
694
+ "SHTPB",
695
+ "FUTX",
696
+ "subdirs"
547
697
  ],
548
698
  "ignorePaths": [
549
699
  "**/node_modules/**",
@@ -163,6 +163,10 @@ function getDefaultConfig() {
163
163
  'test',
164
164
  'build',
165
165
  ],
166
+ // Skip release PRs and squash-merge titles that don't follow conventional
167
+ // commits (e.g. "Release 15 (#114)"). commitlint already ignores literal
168
+ // "Merge"/"Revert" messages, but not GitHub squash-merge PR titles.
169
+ ignores: ['^Release\\b', '^chore\\(release\\):'],
166
170
  },
167
171
  licenseCheck: {
168
172
  allowPackages: [...constants_1.DEFAULT_ALLOW_PACKAGES],
@@ -36,14 +36,21 @@ const DEFAULT_TYPES = [
36
36
  'test',
37
37
  'build',
38
38
  ];
39
- function buildTempConfig(types) {
40
- const config = {
41
- // Resolve via Node module resolution — robust to npm hoisting in the client.
42
- extends: [require.resolve('@commitlint/config-conventional')],
43
- rules: { 'type-enum': [2, 'always', types] },
44
- };
45
- const tmpFile = node_path_1.default.join(node_os_1.default.tmpdir(), `.commitlint-${process.pid}.json`);
46
- node_fs_1.default.writeFileSync(tmpFile, JSON.stringify(config, null, 2));
39
+ const DEFAULT_IGNORES = ['^Release\\b', '^chore\\(release\\):'];
40
+ function buildTempConfig(types, ignores) {
41
+ // Emit a CJS module so `ignores` can hold functions — commitlint requires
42
+ // predicate functions there, which a JSON config cannot express.
43
+ // Resolve config-conventional via Node module resolution — robust to hoisting.
44
+ const configConventional = require.resolve('@commitlint/config-conventional');
45
+ const content = `const patterns = ${JSON.stringify(ignores)}.map((p) => new RegExp(p, 'i'));
46
+ module.exports = {
47
+ extends: [${JSON.stringify(configConventional)}],
48
+ rules: { 'type-enum': [2, 'always', ${JSON.stringify(types)}] },
49
+ ignores: [(commit) => patterns.some((re) => re.test(commit))],
50
+ };
51
+ `;
52
+ const tmpFile = node_path_1.default.join(node_os_1.default.tmpdir(), `.commitlint-${process.pid}.cjs`);
53
+ node_fs_1.default.writeFileSync(tmpFile, content);
47
54
  return tmpFile;
48
55
  }
49
56
  exports.commitlintCheck = {
@@ -65,7 +72,8 @@ exports.commitlintCheck = {
65
72
  }
66
73
  else {
67
74
  const types = ctx.config.commitlint?.types ?? DEFAULT_TYPES;
68
- tempConfigFile = buildTempConfig(types);
75
+ const ignores = ctx.config.commitlint?.ignores ?? DEFAULT_IGNORES;
76
+ tempConfigFile = buildTempConfig(types, ignores);
69
77
  args.push('--config', tempConfigFile);
70
78
  }
71
79
  const result = await (0, execa_1.execa)(process.execPath, [commitlintBin, ...args], {
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.diffSecurityCheck = void 0;
4
- const execa_1 = require("execa");
5
4
  const utils_1 = require("../../utils");
6
5
  const types_1 = require("../types");
6
+ const findings_1 = require("./findings");
7
7
  const SECURITY_PATTERNS = [
8
8
  {
9
9
  pattern: /^\+(?!\+).*dangerouslySetInnerHTML\s*=/,
@@ -99,6 +99,20 @@ const SECURITY_PATTERNS = [
99
99
  severity: 'error',
100
100
  },
101
101
  ];
102
+ /** Bucket added-line matches by severity, keyed by pattern label. */
103
+ function scanDiff(stdout) {
104
+ const errors = {};
105
+ const warnings = {};
106
+ for (const line of stdout.split('\n')) {
107
+ for (const { pattern, label, severity } of SECURITY_PATTERNS) {
108
+ if (pattern.test(line)) {
109
+ const bucket = severity === 'error' ? errors : warnings;
110
+ (bucket[label] ?? (bucket[label] = [])).push(line.slice(1).trim().substring(0, 120));
111
+ }
112
+ }
113
+ }
114
+ return { errors, warnings };
115
+ }
102
116
  exports.diffSecurityCheck = {
103
117
  name: 'Diff Security',
104
118
  phase: 'informational',
@@ -107,52 +121,21 @@ exports.diffSecurityCheck = {
107
121
  },
108
122
  async run(ctx) {
109
123
  try {
110
- const { stdout } = await (0, execa_1.execa)('git', [
111
- 'diff',
112
- '--unified=0',
113
- '--diff-filter=ACMR',
114
- ctx.baseSha,
115
- 'HEAD',
116
- '--',
124
+ const stdout = await (0, findings_1.diffAddedLines)(ctx, [
117
125
  '*.ts',
118
126
  '*.tsx',
119
127
  '*.js',
120
128
  '*.jsx',
121
129
  '*.mjs',
122
130
  '*.cjs',
123
- ], { cwd: ctx.cwd });
124
- const errors = {};
125
- const warnings = {};
126
- for (const line of stdout.split('\n')) {
127
- for (const { pattern, label, severity } of SECURITY_PATTERNS) {
128
- if (pattern.test(line)) {
129
- const bucket = severity === 'error' ? errors : warnings;
130
- (bucket[label] ?? (bucket[label] = [])).push(line.slice(1).trim().substring(0, 120));
131
- }
132
- }
133
- }
134
- const totalErrors = Object.values(errors).reduce((s, a) => s + a.length, 0);
135
- const totalWarnings = Object.values(warnings).reduce((s, a) => s + a.length, 0);
136
- if (totalErrors === 0 && totalWarnings === 0)
137
- return (0, types_1.pass)();
138
- for (const [label, occurrences] of Object.entries(errors)) {
139
- utils_1.logger.error(`[Diff Security]: ❌ ${label}: ${occurrences.length} occurrence(s)`);
140
- occurrences.slice(0, 3).forEach(l => utils_1.logger.error(` ${l}`));
141
- if (occurrences.length > 3)
142
- utils_1.logger.error(` ... and ${occurrences.length - 3} more`);
143
- }
144
- for (const [label, occurrences] of Object.entries(warnings)) {
145
- utils_1.logger.warn(`[Diff Security]: ⚠️ ${label}: ${occurrences.length} occurrence(s)`);
146
- occurrences.slice(0, 3).forEach(l => utils_1.logger.warn(` ${l}`));
147
- if (occurrences.length > 3)
148
- utils_1.logger.warn(` ... and ${occurrences.length - 3} more`);
149
- }
150
- const parts = [];
151
- if (totalErrors > 0)
152
- parts.push(`${totalErrors} issue(s)`);
153
- if (totalWarnings > 0)
154
- parts.push(`${totalWarnings} warning(s)`);
155
- return (0, types_1.warn)(parts.join(', '));
131
+ ]);
132
+ const { errors, warnings } = scanDiff(stdout);
133
+ return (0, findings_1.reportSeverityBuckets)(errors, warnings, {
134
+ prefix: 'Diff Security',
135
+ errorSummary: n => `${n} issue(s)`,
136
+ warningSummary: n => `${n} warning(s)`,
137
+ sampleLimit: 3,
138
+ });
156
139
  }
157
140
  catch (err) {
158
141
  utils_1.logger.warn(`[Diff Security]: ⚠️ Could not analyze diff\n${err}`);
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.diffSmellCheck = void 0;
4
- const execa_1 = require("execa");
5
4
  const utils_1 = require("../../utils");
6
5
  const types_1 = require("../types");
6
+ const findings_1 = require("./findings");
7
7
  const SMELL_PATTERNS = [
8
8
  {
9
9
  pattern: /^\+(?!\+)(?:[^/]|\/[^/]).*\bconsole\.(log|error|warn|debug|info)\s*\(/,
@@ -23,18 +23,7 @@ exports.diffSmellCheck = {
23
23
  },
24
24
  async run(ctx) {
25
25
  try {
26
- const { stdout } = await (0, execa_1.execa)('git', [
27
- 'diff',
28
- '--unified=0',
29
- '--diff-filter=ACMR',
30
- ctx.baseSha,
31
- 'HEAD',
32
- '--',
33
- '*.ts',
34
- '*.tsx',
35
- '*.js',
36
- '*.jsx',
37
- ], { cwd: ctx.cwd });
26
+ const stdout = await (0, findings_1.diffAddedLines)(ctx, ['*.ts', '*.tsx', '*.js', '*.jsx']);
38
27
  const findings = {};
39
28
  for (const line of stdout.split('\n')) {
40
29
  for (const { pattern, label } of SMELL_PATTERNS) {
@@ -43,15 +32,9 @@ exports.diffSmellCheck = {
43
32
  }
44
33
  }
45
34
  }
46
- const total = Object.values(findings).reduce((sum, arr) => sum + arr.length, 0);
47
- if (total === 0)
35
+ if ((0, findings_1.bucketTotal)(findings) === 0)
48
36
  return (0, types_1.pass)();
49
- for (const [label, occurrences] of Object.entries(findings)) {
50
- utils_1.logger.warn(`[Diff Smells]: ⚠️ ${label}: ${occurrences.length} occurrence(s)`);
51
- occurrences.slice(0, 3).forEach(l => utils_1.logger.warn(` ${l}`));
52
- if (occurrences.length > 3)
53
- utils_1.logger.warn(` ... and ${occurrences.length - 3} more`);
54
- }
37
+ (0, findings_1.logFindings)(findings, { prefix: 'Diff Smells', sampleLimit: 3 });
55
38
  const summary = Object.entries(findings)
56
39
  .map(([label, arr]) => `${arr.length}× ${label}`)
57
40
  .join(', ');
@@ -10,6 +10,23 @@ const execa_1 = require("execa");
10
10
  const utils_1 = require("../../utils");
11
11
  const delta_1 = require("../../utils/delta");
12
12
  const types_1 = require("../types");
13
+ // jscpd v5 appends promotional/funding lines after its report. Strip them so
14
+ // they never reach the client's terminal when running our package.
15
+ const JSCPD_PROMO_MARKERS = [
16
+ 'Auto-refactor with AI',
17
+ 'github.com/kucherenko/jscpd',
18
+ 'Gangsta Agents',
19
+ 'gangsta.page',
20
+ 'Support jscpd project',
21
+ 'opencollective.com/jscpd',
22
+ ];
23
+ function stripJscpdPromo(text) {
24
+ return text
25
+ .split('\n')
26
+ .filter(line => !JSCPD_PROMO_MARKERS.some(marker => line.includes(marker)))
27
+ .join('\n')
28
+ .trimEnd();
29
+ }
13
30
  exports.duplicateCheck = {
14
31
  name: 'Duplicate Code',
15
32
  phase: 'informational',
@@ -26,7 +43,7 @@ exports.duplicateCheck = {
26
43
  return (0, types_1.skip)('no relevant files changed');
27
44
  const srcPath = node_path_1.default.join(ctx.cwd, 'src');
28
45
  const targetPaths = changedFiles ?? (node_fs_1.default.existsSync(srcPath) ? [srcPath] : [ctx.cwd]);
29
- const jscpdBin = (0, utils_1.resolvePackageBin)('jscpd', 'bin/jscpd');
46
+ const jscpdBin = (0, utils_1.resolvePackageBin)('jscpd', 'run-jscpd.js');
30
47
  try {
31
48
  const result = await (0, execa_1.execa)(process.execPath, [
32
49
  jscpdBin,
@@ -38,10 +55,9 @@ exports.duplicateCheck = {
38
55
  'console',
39
56
  '--ignore',
40
57
  'node_modules/**,dist/**,.git/**',
41
- '--quiet',
42
58
  ...targetPaths,
43
59
  ], { cwd: ctx.cwd, reject: false });
44
- const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
60
+ const output = stripJscpdPromo([result.stdout, result.stderr].filter(Boolean).join('\n'));
45
61
  const clonesMatch = output.match(/(\d+)\s+clone/i);
46
62
  const cloneCount = clonesMatch ? parseInt(clonesMatch[1], 10) : 0;
47
63
  if (cloneCount > 0) {
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.diffAddedLines = diffAddedLines;
4
+ exports.bucketTotal = bucketTotal;
5
+ exports.logFindings = logFindings;
6
+ exports.reportSeverityBuckets = reportSeverityBuckets;
7
+ const execa_1 = require("execa");
8
+ const utils_1 = require("../../utils");
9
+ const types_1 = require("../types");
10
+ /**
11
+ * Run `git diff` over the given file globs, returning the raw unified diff
12
+ * (zero context, added/copied/modified/renamed files) for added-line scanning.
13
+ */
14
+ async function diffAddedLines(ctx, globs) {
15
+ const { stdout } = await (0, execa_1.execa)('git', ['diff', '--unified=0', '--diff-filter=ACMR', ctx.baseSha, 'HEAD', '--', ...globs], { cwd: ctx.cwd });
16
+ return stdout;
17
+ }
18
+ /** Total number of findings across all labels in a bucket. */
19
+ function bucketTotal(bucket) {
20
+ return Object.values(bucket).reduce((sum, arr) => sum + arr.length, 0);
21
+ }
22
+ /** Log one bucket of findings using the shared check format. */
23
+ function logFindings(bucket, opts) {
24
+ const emit = opts.isError ? utils_1.logger.error.bind(utils_1.logger) : utils_1.logger.warn.bind(utils_1.logger);
25
+ const icon = opts.isError ? '❌' : '⚠️';
26
+ const showCount = opts.showCount ?? true;
27
+ const { sampleLimit } = opts;
28
+ for (const [label, items] of Object.entries(bucket)) {
29
+ emit(`[${opts.prefix}]: ${icon} ${label}${showCount ? `: ${items.length} occurrence(s)` : ':'}`);
30
+ const shown = sampleLimit === undefined ? items : items.slice(0, sampleLimit);
31
+ shown.forEach(item => emit(` ${item}`));
32
+ if (sampleLimit !== undefined && items.length > sampleLimit) {
33
+ emit(` ... and ${items.length - sampleLimit} more`);
34
+ }
35
+ }
36
+ }
37
+ /**
38
+ * Report error/warning finding buckets: log both, then produce the outcome.
39
+ * Returns pass() when both buckets are empty, otherwise warn() with a summary.
40
+ */
41
+ function reportSeverityBuckets(errors, warnings, opts) {
42
+ const totalErrors = bucketTotal(errors);
43
+ const totalWarnings = bucketTotal(warnings);
44
+ if (totalErrors === 0 && totalWarnings === 0)
45
+ return (0, types_1.pass)();
46
+ const logOpts = { prefix: opts.prefix, showCount: opts.showCount, sampleLimit: opts.sampleLimit };
47
+ logFindings(errors, { ...logOpts, isError: true });
48
+ logFindings(warnings, { ...logOpts, isError: false });
49
+ const parts = [];
50
+ if (totalErrors > 0)
51
+ parts.push(opts.errorSummary(totalErrors));
52
+ if (totalWarnings > 0)
53
+ parts.push(opts.warningSummary(totalWarnings));
54
+ return (0, types_1.warn)(parts.join(', '));
55
+ }
@@ -24,6 +24,11 @@ exports.licenseCheck = {
24
24
  async run(ctx) {
25
25
  const allowPackages = new Set(ctx.config.licenseCheck?.allowPackages ?? []);
26
26
  const allowLicenses = ctx.config.licenseCheck?.allowLicenses ?? [];
27
+ // pr-checkmate installs into the consumer's node_modules, so its own runtime
28
+ // deps (eslint plugins, cspell dictionaries, …) leak into the client's
29
+ // production tree. Those are our dependencies, not the client's — never fail
30
+ // a client's PR because of a license we ourselves pulled in.
31
+ const ownDeps = (0, utils_1.getOwnDependencyClosure)();
27
32
  // Resolve the bundled license-checker binary and run it via the current node — never
28
33
  // shell out to `npx`, which fails (exit 127) when license-checker isn't on the path.
29
34
  let licenseCheckerBin;
@@ -44,7 +49,8 @@ exports.licenseCheck = {
44
49
  }
45
50
  const violations = [];
46
51
  for (const [pkg, info] of Object.entries(licenses)) {
47
- if (allowPackages.has(bareName(pkg)))
52
+ const name = bareName(pkg);
53
+ if (allowPackages.has(name) || ownDeps.has(name))
48
54
  continue;
49
55
  const raw = info.licenses ?? '';
50
56
  const license = Array.isArray(raw) ? raw.join(' OR ') : raw;
@@ -38,7 +38,14 @@ exports.lintCheck = {
38
38
  '.eslintrc.json',
39
39
  '.eslintrc',
40
40
  ]);
41
- const args = [...targets, '--ext', '.ts,.tsx,.js,.jsx'];
41
+ const version = await getESLintMajorVersion(eslintBin);
42
+ const args = [...targets];
43
+ // `--ext` is an eslintrc-only flag. Under flat config (ESLint 9+) it is
44
+ // obsolete — extensions come from the config's `files` glob — and in
45
+ // ESLint 10 passing it makes explicit files fall back to the default JS
46
+ // parser, breaking TypeScript linting. Only pass it on the legacy path.
47
+ if (version < 9)
48
+ args.push('--ext', '.ts,.tsx,.js,.jsx');
42
49
  // Only auto-fix when the caller allows file mutations.
43
50
  if (ctx.write)
44
51
  args.push('--fix');
@@ -46,7 +53,6 @@ exports.lintCheck = {
46
53
  utils_1.logger.info(`[ESLint]: Using local config (checking: ${sourcePath})`);
47
54
  }
48
55
  else {
49
- const version = await getESLintMajorVersion(eslintBin);
50
56
  const configFile = version >= 9 ? 'eslint.config.mjs' : '.eslintrc.js';
51
57
  args.push('--config', (0, config_1.getPackageConfigPath)(configFile));
52
58
  utils_1.logger.info(`[ESLint]: Using pr-checkmate config (ESLint v${version}, ${configFile})`);
@@ -87,19 +87,17 @@ exports.markdownLintCheck = {
87
87
  '.claude/**',
88
88
  ...(userConfig.ignores ?? []),
89
89
  ];
90
- const gitignorePath = node_path_1.default.join(ctx.cwd, '.gitignore');
91
- const tmpIgnoreFile = node_path_1.default.join(node_os_1.default.tmpdir(), `.markdownlintignore-${process.pid}`);
92
- const gitignoreContent = node_fs_1.default.existsSync(gitignorePath)
93
- ? node_fs_1.default.readFileSync(gitignorePath, 'utf-8') + '\n'
94
- : '';
95
- node_fs_1.default.writeFileSync(tmpIgnoreFile, gitignoreContent + defaultIgnorePatterns.join('\n') + '\n');
96
- const args = [
97
- ...(ctx.write ? ['--fix'] : []),
98
- ...targets,
99
- ...configArg,
100
- '--ignore-path',
101
- tmpIgnoreFile,
102
- ];
90
+ // markdownlint-cli2 has no --ignore-path flag; exclusions are expressed as
91
+ // negated globs (`#pattern`). A bare directory name also needs `#name/**`
92
+ // to exclude its contents.
93
+ const ignoreGlobs = defaultIgnorePatterns.flatMap(pattern => {
94
+ const clean = pattern.replace(/\/+$/, '');
95
+ const globs = [`#${clean}`];
96
+ if (!clean.includes('*'))
97
+ globs.push(`#${clean}/**`);
98
+ return globs;
99
+ });
100
+ const args = [...(ctx.write ? ['--fix'] : []), ...targets, ...ignoreGlobs, ...configArg];
103
101
  try {
104
102
  const result = await (0, execa_1.execa)(process.execPath, [markdownlintBin, ...args], {
105
103
  cwd: ctx.cwd,
@@ -111,8 +109,6 @@ exports.markdownLintCheck = {
111
109
  finally {
112
110
  if (tempConfigFile && node_fs_1.default.existsSync(tempConfigFile))
113
111
  node_fs_1.default.unlinkSync(tempConfigFile);
114
- if (node_fs_1.default.existsSync(tmpIgnoreFile))
115
- node_fs_1.default.unlinkSync(tmpIgnoreFile);
116
112
  }
117
113
  },
118
114
  };