pr-checkmate 1.22.7 → 1.23.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 (140) hide show
  1. package/README.md +101 -333
  2. package/cspell.json +9 -1
  3. package/dist/cli.js +32 -2
  4. package/dist/config/constants.d.ts +25 -1
  5. package/dist/config/constants.js +157 -38
  6. package/dist/config/index.d.ts +15 -2
  7. package/dist/config/index.js +28 -7
  8. package/dist/config/loader.js +5 -1
  9. package/dist/core/checks/dependencies/banned-imports.d.ts +2 -0
  10. package/dist/core/checks/dependencies/banned-imports.js +71 -0
  11. package/dist/core/checks/{circular-deps.d.ts → dependencies/circular-deps.d.ts} +1 -1
  12. package/dist/core/checks/{circular-deps.js → dependencies/circular-deps.js} +9 -10
  13. package/dist/core/checks/{dependency-check.d.ts → dependencies/dependency-check.d.ts} +1 -1
  14. package/dist/core/checks/{dependency-check.js → dependencies/dependency-check.js} +3 -3
  15. package/dist/core/checks/{license-check.d.ts → dependencies/license-check.d.ts} +1 -1
  16. package/dist/core/checks/{license-check.js → dependencies/license-check.js} +3 -3
  17. package/dist/core/checks/{npm-audit.d.ts → dependencies/npm-audit.d.ts} +1 -1
  18. package/dist/core/checks/{npm-audit.js → dependencies/npm-audit.js} +3 -3
  19. package/dist/core/checks/{outdated-deps.d.ts → dependencies/outdated-deps.d.ts} +1 -1
  20. package/dist/core/checks/{outdated-deps.js → dependencies/outdated-deps.js} +3 -3
  21. package/dist/core/checks/dependencies/vuln-scan.d.ts +9 -0
  22. package/dist/core/checks/dependencies/vuln-scan.js +41 -0
  23. package/dist/core/checks/findings.d.ts +37 -1
  24. package/dist/core/checks/findings.js +80 -3
  25. package/dist/core/checks/git/custom-rules.d.ts +2 -0
  26. package/dist/core/checks/git/custom-rules.js +102 -0
  27. package/dist/core/checks/{diff-smell.d.ts → git/diff-smell.d.ts} +1 -1
  28. package/dist/core/checks/{diff-smell.js → git/diff-smell.js} +3 -3
  29. package/dist/core/checks/git/large-files.d.ts +2 -0
  30. package/dist/core/checks/git/large-files.js +83 -0
  31. package/dist/core/checks/git/leftover-debug.d.ts +2 -0
  32. package/dist/core/checks/git/leftover-debug.js +77 -0
  33. package/dist/core/checks/git/lockfile-drift.d.ts +2 -0
  34. package/dist/core/checks/git/lockfile-drift.js +114 -0
  35. package/dist/core/checks/git/merge-conflict.d.ts +2 -0
  36. package/dist/core/checks/git/merge-conflict.js +109 -0
  37. package/dist/core/checks/git/missing-tests.d.ts +2 -0
  38. package/dist/core/checks/git/missing-tests.js +92 -0
  39. package/dist/core/checks/git/todo-fixme.d.ts +7 -0
  40. package/dist/core/checks/git/todo-fixme.js +74 -0
  41. package/dist/core/checks/{cpp-format.d.ts → languages/cpp-format.d.ts} +1 -1
  42. package/dist/core/checks/{cpp-format.js → languages/cpp-format.js} +3 -3
  43. package/dist/core/checks/languages/csharp-format.d.ts +13 -0
  44. package/dist/core/checks/languages/csharp-format.js +52 -0
  45. package/dist/core/checks/languages/go-format.d.ts +8 -0
  46. package/dist/core/checks/languages/go-format.js +48 -0
  47. package/dist/core/checks/languages/go-lint.d.ts +8 -0
  48. package/dist/core/checks/languages/go-lint.js +48 -0
  49. package/dist/core/checks/{kotlin-lint.d.ts → languages/kotlin-lint.d.ts} +1 -1
  50. package/dist/core/checks/{kotlin-lint.js → languages/kotlin-lint.js} +13 -17
  51. package/dist/core/checks/{lint.d.ts → languages/lint.d.ts} +1 -1
  52. package/dist/core/checks/{lint.js → languages/lint.js} +25 -14
  53. package/dist/core/checks/languages/php-format.d.ts +9 -0
  54. package/dist/core/checks/languages/php-format.js +49 -0
  55. package/dist/core/checks/{prettier.d.ts → languages/prettier.d.ts} +1 -1
  56. package/dist/core/checks/{prettier.js → languages/prettier.js} +10 -10
  57. package/dist/core/checks/{python-format.d.ts → languages/python-format.d.ts} +1 -1
  58. package/dist/core/checks/{python-format.js → languages/python-format.js} +3 -3
  59. package/dist/core/checks/{python-lint.d.ts → languages/python-lint.d.ts} +1 -1
  60. package/dist/core/checks/{python-lint.js → languages/python-lint.js} +3 -3
  61. package/dist/core/checks/languages/python-typecheck.d.ts +9 -0
  62. package/dist/core/checks/languages/python-typecheck.js +66 -0
  63. package/dist/core/checks/languages/ruby-lint.d.ts +8 -0
  64. package/dist/core/checks/languages/ruby-lint.js +50 -0
  65. package/dist/core/checks/languages/rust-format.d.ts +9 -0
  66. package/dist/core/checks/languages/rust-format.js +45 -0
  67. package/dist/core/checks/languages/rust-lint.d.ts +8 -0
  68. package/dist/core/checks/languages/rust-lint.js +50 -0
  69. package/dist/core/checks/{swift-lint.d.ts → languages/swift-lint.d.ts} +1 -1
  70. package/dist/core/checks/{swift-lint.js → languages/swift-lint.js} +13 -17
  71. package/dist/core/checks/{typecheck.d.ts → languages/typecheck.d.ts} +1 -1
  72. package/dist/core/checks/{typecheck.js → languages/typecheck.js} +3 -3
  73. package/dist/core/checks/{commitlint.d.ts → pr/commitlint.d.ts} +1 -1
  74. package/dist/core/checks/{commitlint.js → pr/commitlint.js} +4 -4
  75. package/dist/core/checks/{pr-body.d.ts → pr/pr-body.d.ts} +1 -1
  76. package/dist/core/checks/{pr-body.js → pr/pr-body.js} +2 -2
  77. package/dist/core/checks/{pr-size-check.d.ts → pr/pr-size-check.d.ts} +1 -1
  78. package/dist/core/checks/{pr-size-check.js → pr/pr-size-check.js} +13 -11
  79. package/dist/core/checks/{pr-title-lint.d.ts → pr/pr-title-lint.d.ts} +1 -1
  80. package/dist/core/checks/{pr-title-lint.js → pr/pr-title-lint.js} +2 -2
  81. package/dist/core/checks/quality/case-collision.d.ts +8 -0
  82. package/dist/core/checks/quality/case-collision.js +40 -0
  83. package/dist/core/checks/quality/config-validation.d.ts +8 -0
  84. package/dist/core/checks/quality/config-validation.js +63 -0
  85. package/dist/core/checks/quality/coverage.d.ts +2 -0
  86. package/dist/core/checks/quality/coverage.js +63 -0
  87. package/dist/core/checks/{dead-code.d.ts → quality/dead-code.d.ts} +1 -1
  88. package/dist/core/checks/{dead-code.js → quality/dead-code.js} +4 -4
  89. package/dist/core/checks/{duplicate-check.d.ts → quality/duplicate-check.d.ts} +1 -1
  90. package/dist/core/checks/{duplicate-check.js → quality/duplicate-check.js} +11 -15
  91. package/dist/core/checks/quality/license-header.d.ts +7 -0
  92. package/dist/core/checks/quality/license-header.js +74 -0
  93. package/dist/core/checks/{markdown-lint.d.ts → quality/markdown-lint.d.ts} +1 -1
  94. package/dist/core/checks/{markdown-lint.js → quality/markdown-lint.js} +8 -21
  95. package/dist/core/checks/{spellcheck.d.ts → quality/spellcheck.d.ts} +1 -1
  96. package/dist/core/checks/{spellcheck.js → quality/spellcheck.js} +11 -19
  97. package/dist/core/checks/{diff-security.d.ts → security/diff-security.d.ts} +1 -1
  98. package/dist/core/checks/security/diff-security.js +262 -0
  99. package/dist/core/checks/security/dockerfile-security.d.ts +2 -0
  100. package/dist/core/checks/security/dockerfile-security.js +92 -0
  101. package/dist/core/checks/security/migration-safety.d.ts +2 -0
  102. package/dist/core/checks/security/migration-safety.js +110 -0
  103. package/dist/core/checks/{security.d.ts → security/security.d.ts} +1 -1
  104. package/dist/core/checks/{security.js → security/security.js} +4 -4
  105. package/dist/core/checks/{sensitive-file-guard.d.ts → security/sensitive-file-guard.d.ts} +1 -1
  106. package/dist/core/checks/{sensitive-file-guard.js → security/sensitive-file-guard.js} +4 -4
  107. package/dist/core/checks/security/workflow-security.d.ts +2 -0
  108. package/dist/core/checks/security/workflow-security.js +75 -0
  109. package/dist/core/context.js +54 -10
  110. package/dist/core/registry.js +73 -28
  111. package/dist/core/reporters.d.ts +4 -3
  112. package/dist/core/reporters.js +104 -4
  113. package/dist/core/run-checks.js +21 -3
  114. package/dist/core/targets.d.ts +17 -1
  115. package/dist/core/targets.js +74 -4
  116. package/dist/core/types.d.ts +13 -1
  117. package/dist/index.d.ts +1 -1
  118. package/dist/index.js +3 -1
  119. package/dist/init.js +86 -8
  120. package/dist/interfaces/index.d.ts +101 -1
  121. package/dist/scripts/copy-configs.js +6 -1
  122. package/dist/utils/detect-language.d.ts +1 -1
  123. package/dist/utils/detect-language.js +32 -3
  124. package/dist/utils/glob.d.ts +10 -0
  125. package/dist/utils/glob.js +25 -0
  126. package/dist/utils/index.d.ts +1 -1
  127. package/dist/utils/index.js +1 -1
  128. package/eslint-default-rules.json +62 -0
  129. package/eslint.config.mjs +6 -66
  130. package/package.json +3 -1
  131. package/pr-checkmate.schema.json +166 -0
  132. package/src/config/cspell.json +9 -1
  133. package/src/config/eslint-default-rules.json +62 -0
  134. package/src/config/pr-checkmate.schema.json +166 -0
  135. package/dist/core/checks/diff-security.js +0 -145
  136. package/dist/utils/delta.d.ts +0 -5
  137. package/dist/utils/delta.js +0 -53
  138. package/src/config/eslint.config.mjs +0 -180
  139. /package/dist/core/checks/{ruff-shared.d.ts → languages/ruff-shared.d.ts} +0 -0
  140. /package/dist/core/checks/{ruff-shared.js → languages/ruff-shared.js} +0 -0
@@ -6,6 +6,22 @@ import type { CheckContext } from './types';
6
6
  * `null` when git itself is unusable (caller should `skip`).
7
7
  *
8
8
  * Only ACMR files are returned (Added/Copied/Modified/Renamed) — deletions are
9
- * excluded, so every returned path exists on disk and is safe to read.
9
+ * excluded. Results are additionally filtered to paths that still exist on disk,
10
+ * so a rename/move within the diff range (where the old path shows up but no
11
+ * longer exists) can never hand a missing path to a downstream tool like ESLint
12
+ * or Prettier, which crash hard on a non-existent pattern.
10
13
  */
11
14
  export declare function resolveTargetFiles(ctx: CheckContext, globs: string[]): Promise<string[] | null>;
15
+ /**
16
+ * Like {@link resolveTargetFiles} but restricts results to the configured
17
+ * `sourcePath`(s). Preserves the semantics of the old `sourcePath` full-scan
18
+ * fallback while gaining delta support on every platform via `ctx.baseSha`.
19
+ */
20
+ export declare function resolveScopedTargetFiles(ctx: CheckContext, globs: string[], sourcePaths: string[]): Promise<string[] | null>;
21
+ /**
22
+ * List ALL tracked files matching `globs` (never delta), scoped to
23
+ * `sourcePath`(s). For whole-repo analyses like circular-dependency detection
24
+ * that need the full graph, not just the PR diff. Using `git ls-files` means
25
+ * untracked `node_modules` is excluded by construction.
26
+ */
27
+ export declare function listTrackedFiles(ctx: CheckContext, globs: string[], sourcePaths: string[]): Promise<string[]>;
@@ -1,8 +1,28 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.resolveTargetFiles = resolveTargetFiles;
7
+ exports.resolveScopedTargetFiles = resolveScopedTargetFiles;
8
+ exports.listTrackedFiles = listTrackedFiles;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const config_1 = require("../config");
4
12
  const utils_1 = require("../utils");
5
13
  const execa_1 = require("../utils/execa");
14
+ /**
15
+ * Drop files that live under any excluded directory (client `ignoreDirs` or the
16
+ * curated default), matched on any path segment so nesting depth doesn't matter.
17
+ * Applied to every check's file list so build/tooling/report dirs are never
18
+ * scanned, even when committed.
19
+ */
20
+ function excludeIgnoredDirs(files) {
21
+ const ignored = new Set((0, config_1.getIgnoredDirs)());
22
+ if (ignored.size === 0)
23
+ return files;
24
+ return files.filter(f => !f.split('/').some(segment => ignored.has(segment)));
25
+ }
6
26
  /**
7
27
  * Lists the files a language check should operate on, matched by pathspec `globs`
8
28
  * (e.g. `['*.py']`). Uses the PR diff range when available (delta mode), else
@@ -10,7 +30,10 @@ const execa_1 = require("../utils/execa");
10
30
  * `null` when git itself is unusable (caller should `skip`).
11
31
  *
12
32
  * Only ACMR files are returned (Added/Copied/Modified/Renamed) — deletions are
13
- * excluded, so every returned path exists on disk and is safe to read.
33
+ * excluded. Results are additionally filtered to paths that still exist on disk,
34
+ * so a rename/move within the diff range (where the old path shows up but no
35
+ * longer exists) can never hand a missing path to a downstream tool like ESLint
36
+ * or Prettier, which crash hard on a non-existent pattern.
14
37
  */
15
38
  async function resolveTargetFiles(ctx, globs) {
16
39
  try {
@@ -25,11 +48,16 @@ async function resolveTargetFiles(ctx, globs) {
25
48
  ...globs,
26
49
  ]
27
50
  : ['ls-files', ...globs];
28
- const { stdout } = await (0, execa_1.execa)('git', args, { cwd: ctx.cwd, reject: false });
29
- const files = stdout
51
+ const cacheKey = `t:${args.join('')}`;
52
+ let stdout = ctx.cache?.get(cacheKey);
53
+ if (stdout === undefined) {
54
+ stdout = (await (0, execa_1.execa)('git', args, { cwd: ctx.cwd, reject: false })).stdout;
55
+ ctx.cache?.set(cacheKey, stdout);
56
+ }
57
+ const files = excludeIgnoredDirs(stdout
30
58
  .split('\n')
31
59
  .map(f => f.trim())
32
- .filter(Boolean);
60
+ .filter(Boolean)).filter(f => node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, f)));
33
61
  utils_1.logger.info(`[targets]: ${ctx.baseSha ? '📂 Delta' : 'Full scan'}: ${files.length} file(s) for ${globs.join(', ')}`);
34
62
  return files;
35
63
  }
@@ -38,3 +66,45 @@ async function resolveTargetFiles(ctx, globs) {
38
66
  return null;
39
67
  }
40
68
  }
69
+ /**
70
+ * Restrict a list of repo-relative paths to the configured `sourcePath`(s).
71
+ * A source path of `.` (or one that normalizes to empty) imposes no
72
+ * restriction, so the whole repo is considered.
73
+ */
74
+ function scopeToSourcePaths(files, sourcePaths) {
75
+ const scopes = sourcePaths.map(p => p.replace(/^\.\/?/, '').replace(/\/+$/, '')).filter(Boolean);
76
+ if (scopes.length === 0)
77
+ return files;
78
+ return files.filter(f => scopes.some(s => f === s || f.startsWith(`${s}/`)));
79
+ }
80
+ /**
81
+ * Like {@link resolveTargetFiles} but restricts results to the configured
82
+ * `sourcePath`(s). Preserves the semantics of the old `sourcePath` full-scan
83
+ * fallback while gaining delta support on every platform via `ctx.baseSha`.
84
+ */
85
+ async function resolveScopedTargetFiles(ctx, globs, sourcePaths) {
86
+ const files = await resolveTargetFiles(ctx, globs);
87
+ if (files === null)
88
+ return null;
89
+ return scopeToSourcePaths(files, sourcePaths);
90
+ }
91
+ /**
92
+ * List ALL tracked files matching `globs` (never delta), scoped to
93
+ * `sourcePath`(s). For whole-repo analyses like circular-dependency detection
94
+ * that need the full graph, not just the PR diff. Using `git ls-files` means
95
+ * untracked `node_modules` is excluded by construction.
96
+ */
97
+ async function listTrackedFiles(ctx, globs, sourcePaths) {
98
+ try {
99
+ const { stdout } = await (0, execa_1.execa)('git', ['ls-files', ...globs], { cwd: ctx.cwd, reject: false });
100
+ const files = excludeIgnoredDirs(stdout
101
+ .split('\n')
102
+ .map(f => f.trim())
103
+ .filter(Boolean));
104
+ return scopeToSourcePaths(files, sourcePaths);
105
+ }
106
+ catch (err) {
107
+ utils_1.logger.warn(`[targets]: ⚠️ Could not list tracked files\n${err}`);
108
+ return [];
109
+ }
110
+ }
@@ -24,6 +24,12 @@ export interface CheckContext {
24
24
  /** Diff range for delta-aware checks; undefined → full-repo scan. */
25
25
  baseSha?: string;
26
26
  headSha?: string;
27
+ /**
28
+ * Per-run memoization cache for identical git invocations (many checks diff
29
+ * the same range). Created once per `runChecks`; safe because git state is
30
+ * stable within a run. Keyed by the git args.
31
+ */
32
+ cache?: Map<string, string>;
27
33
  }
28
34
  /** Outcome of a single check — the name is attached by the orchestrator. */
29
35
  export interface CheckOutcome {
@@ -42,7 +48,7 @@ export interface Check {
42
48
  applies(ctx: CheckContext): boolean | Promise<boolean>;
43
49
  run(ctx: CheckContext): Promise<CheckOutcome>;
44
50
  }
45
- export type ReporterKind = 'none' | 'stdout' | 'github';
51
+ export type ReporterKind = 'none' | 'stdout' | 'github' | 'gitlab' | 'bitbucket' | 'auto';
46
52
  /** Custom reporter: receives the final report to emit however the consumer wants. */
47
53
  export type ReporterFn = (report: CheckReport) => void | Promise<void>;
48
54
  export interface RunOptions {
@@ -78,6 +84,12 @@ export interface RunOptions {
78
84
  config?: PRCheckMateConfig;
79
85
  baseSha?: string;
80
86
  headSha?: string;
87
+ /**
88
+ * Pre-commit / local mode: check the staged changes (index vs HEAD) instead of
89
+ * a PR range. Implemented by diffing HEAD against the index tree
90
+ * (`git write-tree`), so every delta-aware check works unchanged.
91
+ */
92
+ staged?: boolean;
81
93
  }
82
94
  export interface CheckReport {
83
95
  results: CheckResult[];
package/dist/index.d.ts CHANGED
@@ -6,5 +6,5 @@
6
6
  export { runChecks, buildContext, REGISTRY } from './core';
7
7
  export { pass, warn, fail, skip, defineCheck } from './core';
8
8
  export type { Check, CheckContext, CheckOutcome, CheckReport, CheckResult, CheckStatus, Language, Phase, ReporterFn, ReporterKind, RunOptions, } from './core';
9
- export { getSourcePath, getPackageConfigPath, hasLocalConfig } from './config';
9
+ export { getSourcePath, getSourcePaths, getIgnoredDirs, getPackageConfigPath, hasLocalConfig, } from './config';
10
10
  export type { PRCheckMateConfig } from './interfaces';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * @module pr-checkmate
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.hasLocalConfig = exports.getPackageConfigPath = exports.getSourcePath = exports.defineCheck = exports.skip = exports.fail = exports.warn = exports.pass = exports.REGISTRY = exports.buildContext = exports.runChecks = void 0;
8
+ exports.hasLocalConfig = exports.getPackageConfigPath = exports.getIgnoredDirs = exports.getSourcePaths = exports.getSourcePath = exports.defineCheck = exports.skip = exports.fail = exports.warn = exports.pass = exports.REGISTRY = exports.buildContext = exports.runChecks = void 0;
9
9
  // Primary programmatic API: run checks and receive a structured report.
10
10
  // Never throws for violations, never calls process.exit — the caller decides.
11
11
  var core_1 = require("./core");
@@ -22,5 +22,7 @@ Object.defineProperty(exports, "defineCheck", { enumerable: true, get: function
22
22
  // Config helpers for consumers embedding pr-checkmate.
23
23
  var config_1 = require("./config");
24
24
  Object.defineProperty(exports, "getSourcePath", { enumerable: true, get: function () { return config_1.getSourcePath; } });
25
+ Object.defineProperty(exports, "getSourcePaths", { enumerable: true, get: function () { return config_1.getSourcePaths; } });
26
+ Object.defineProperty(exports, "getIgnoredDirs", { enumerable: true, get: function () { return config_1.getIgnoredDirs; } });
25
27
  Object.defineProperty(exports, "getPackageConfigPath", { enumerable: true, get: function () { return config_1.getPackageConfigPath; } });
26
28
  Object.defineProperty(exports, "hasLocalConfig", { enumerable: true, get: function () { return config_1.hasLocalConfig; } });
package/dist/init.js CHANGED
@@ -10,19 +10,45 @@ const inquirer_1 = __importDefault(require("inquirer"));
10
10
  const config_1 = require("./config");
11
11
  const utils_1 = require("./utils");
12
12
  const CONFIG_FILE = path_1.default.resolve(process.cwd(), 'pr-checkmate.json');
13
+ /**
14
+ * Load the canonical default ESLint rules (the same set eslint.config.mjs
15
+ * applies) so the generated pr-checkmate.json lists every rule for the user to
16
+ * change or disable. Falls back to an empty object if the file is unavailable.
17
+ */
18
+ function loadDefaultEslintRules() {
19
+ try {
20
+ return JSON.parse(fs_1.default.readFileSync((0, config_1.getPackageConfigPath)('eslint-default-rules.json'), 'utf-8'));
21
+ }
22
+ catch (err) {
23
+ utils_1.logger.warn(`[init]: ⚠️ Could not load default ESLint rules: ${err}`);
24
+ return {};
25
+ }
26
+ }
13
27
  /**
14
28
  * Default configuration structure with customization options
15
29
  */
16
30
  const DEFAULT_CONFIG = {
17
- sourcePath: 'src',
31
+ // Points editors (VS Code, etc.) at the bundled JSON schema for autocomplete
32
+ // and inline validation of this file.
33
+ $schema: './node_modules/pr-checkmate/pr-checkmate.schema.json',
34
+ // Human-readable hints. JSON has no comments, so these `//`-prefixed keys are
35
+ // just informational strings — PR CheckMate ignores them at runtime.
36
+ '// reporting': [
37
+ 'Results post to the PR/MR automatically.',
38
+ 'GitHub: no setup — the runner injects GITHUB_TOKEN; just keep `permissions: pull-requests: write` in the workflow.',
39
+ 'GitLab: create a token (Project → Settings → Access Tokens, scope `api`) and add it as CI/CD variable GITLAB_TOKEN.',
40
+ 'Bitbucket: create a token (Repo → Settings → Access tokens, scope pull-requests:write) and add it as repo variable BITBUCKET_TOKEN.',
41
+ 'The token lives in YOUR CI variables, never in this file. Without it, checks still run and print to the log — only the comment is skipped.',
42
+ ].join(' '),
43
+ sourcePath: '.',
44
+ // Directories excluded from every check (matched on any path segment). Edit
45
+ // freely — add your own generated/vendored dirs or remove ones you want scanned.
46
+ ignoreDirs: config_1.IGNORED_DIRS,
18
47
  commands: config_1.DEFAULT_COMMANDS,
19
- // ESLint customization
48
+ // ESLint — the full default rule set is written out so every rule is visible
49
+ // and editable. Change any value, or set a rule to "off" to disable it.
20
50
  eslint: {
21
- rules: {
22
- // Off by default: its `--fix` injects empty `/** */` JSDoc stubs into
23
- // client source files. Set to 'warn'/'error' to opt into doc enforcement.
24
- 'jsdoc/require-jsdoc': 'off',
25
- },
51
+ rules: loadDefaultEslintRules(),
26
52
  ignorePatterns: [],
27
53
  },
28
54
  // Prettier customization
@@ -80,6 +106,57 @@ const DEFAULT_CONFIG = {
80
106
  // test runner. Add barrels/entry points here to exempt intentional exports.
81
107
  ignoreFiles: config_1.DEAD_CODE_IGNORE_DEFAULTS,
82
108
  },
109
+ // User-defined rules: regex patterns run against added diff lines. The escape
110
+ // hatch to enforce any project rule without code. Example entry:
111
+ // { "name": "no-focused-tests", "pattern": "\\b(describe|it)\\.only\\(",
112
+ // "message": "focused test left in", "severity": "error",
113
+ // "include": ["src/**"], "exclude": ["**/*.test.*"] }
114
+ customRules: [],
115
+ // Universal per-check severity override, keyed by the check's display name (as
116
+ // shown in the report). "error" fails the run, "warn" is advisory, "off"
117
+ // disables the check. Empty by default — every check uses its built-in
118
+ // severity. Examples: { "Merge Conflict": "warn", "ESLint": "error" }.
119
+ severity: {},
120
+ // Git / PR-diff heuristics (delta-aware). Merge Conflict is blocking by
121
+ // default; the rest are advisory — promote any of them with `severity`.
122
+ mergeConflict: { enabled: true },
123
+ leftoverDebug: { enabled: true },
124
+ lockfileDrift: { enabled: true },
125
+ largeFiles: { enabled: true, maxSizeKb: 500, allow: [] },
126
+ missingTests: { enabled: true },
127
+ todoFixme: { enabled: true, requireTicket: false },
128
+ migrationSafety: { enabled: true },
129
+ // Security (regex diff scans + sensitive-file guard). Secret scan / npm audit
130
+ // live under `security`.
131
+ diffSecurity: { enabled: true },
132
+ sensitiveFileGuard: { enabled: true },
133
+ workflowSecurity: { enabled: true }, // GitHub Actions: injection, unpinned actions
134
+ dockerfileSecurity: { enabled: true }, // Dockerfile: :latest, USER root, curl|bash, …
135
+ caseCollision: { enabled: true }, // files differing only by case
136
+ vulnScan: { enabled: false }, // cross-language dep vulns (osv-scanner, runner-dependency)
137
+ // Test-coverage presence. Opt-in: enable once your CI produces a coverage
138
+ // report (lcov/cobertura/jacoco/…); warns if none is found.
139
+ coverage: { enabled: false },
140
+ // PR metadata / dependency hygiene.
141
+ prSize: { enabled: true, maxFiles: 50, maxLines: 1000 },
142
+ commitlint: { enabled: true },
143
+ // Dependencies you want to forbid. Example: { "name": "moment", "message": "use date-fns" }.
144
+ bannedImports: [],
145
+ // License/copyright header on new source files. Opt-in — set enabled + pattern.
146
+ licenseHeader: { enabled: false, pattern: '', lines: 10 },
147
+ outdatedDeps: { enabled: true, ignore: [] },
148
+ // Per-language checks. Bundled (no client install): TypeScript, Python (Ruff),
149
+ // C++ (clang-format). Runner-dependency (need the tool on the runner, skip
150
+ // gracefully if absent): Swift, Kotlin, Go, Rust, C#, Ruby, PHP.
151
+ python: { enabled: true, typecheck: true },
152
+ cpp: { enabled: true },
153
+ swift: { enabled: true },
154
+ kotlin: { enabled: true },
155
+ go: { enabled: true },
156
+ rust: { enabled: true },
157
+ csharp: { enabled: true },
158
+ ruby: { enabled: true },
159
+ php: { enabled: true },
83
160
  documentation: config_1.documentation,
84
161
  };
85
162
  const MANUAL_ENTRY = 'Enter manually';
@@ -90,7 +167,8 @@ async function promptSourcePath() {
90
167
  type: 'list',
91
168
  name: 'sourcePath',
92
169
  message: 'Where is your project source code?',
93
- choices: ['src', 'app', MANUAL_ENTRY],
170
+ default: '.',
171
+ choices: [{ name: '. (entire repo — recommended)', value: '.' }, 'src', 'app', MANUAL_ENTRY],
94
172
  },
95
173
  {
96
174
  type: 'input',
@@ -21,8 +21,19 @@ export interface CspellConfig {
21
21
  ignoreRegExpList: string[];
22
22
  }
23
23
  export interface PRCheckMateConfig {
24
- sourcePath: string;
24
+ sourcePath: string | string[];
25
+ ignoreDirs?: string[];
25
26
  languages?: Language[];
27
+ customRules?: Array<{
28
+ name: string;
29
+ pattern: string;
30
+ message?: string;
31
+ severity?: 'error' | 'warn';
32
+ include?: string[];
33
+ exclude?: string[];
34
+ flags?: string;
35
+ }>;
36
+ severity?: Record<string, 'error' | 'warn' | 'off'>;
26
37
  lint?: {
27
38
  rules?: Record<string, any>;
28
39
  ignorePatterns?: string[];
@@ -59,10 +70,52 @@ export interface PRCheckMateConfig {
59
70
  lint?: boolean;
60
71
  format?: boolean;
61
72
  };
73
+ typecheck?: boolean;
74
+ typeChecker?: 'mypy' | 'pyright';
62
75
  };
63
76
  diffSmell?: {
64
77
  enabled?: boolean;
65
78
  };
79
+ prSize?: {
80
+ enabled?: boolean;
81
+ maxFiles?: number;
82
+ maxLines?: number;
83
+ };
84
+ leftoverDebug?: {
85
+ enabled?: boolean;
86
+ severity?: 'error' | 'warn';
87
+ };
88
+ todoFixme?: {
89
+ enabled?: boolean;
90
+ severity?: 'error' | 'warn';
91
+ requireTicket?: boolean;
92
+ keywords?: string[];
93
+ };
94
+ lockfileDrift?: {
95
+ enabled?: boolean;
96
+ severity?: 'error' | 'warn';
97
+ };
98
+ missingTests?: {
99
+ enabled?: boolean;
100
+ severity?: 'error' | 'warn';
101
+ ignore?: string[];
102
+ };
103
+ migrationSafety?: {
104
+ enabled?: boolean;
105
+ severity?: 'error' | 'warn';
106
+ paths?: string[];
107
+ };
108
+ coverage?: {
109
+ enabled?: boolean;
110
+ severity?: 'error' | 'warn';
111
+ reportPath?: string | string[];
112
+ };
113
+ largeFiles?: {
114
+ enabled?: boolean;
115
+ severity?: 'error' | 'warn';
116
+ maxSizeKb?: number;
117
+ allow?: string[];
118
+ };
66
119
  prBody?: {
67
120
  enabled?: boolean;
68
121
  minLength?: number;
@@ -121,6 +174,38 @@ export interface PRCheckMateConfig {
121
174
  };
122
175
  diffSecurity?: {
123
176
  enabled?: boolean;
177
+ ignore?: string[];
178
+ };
179
+ mergeConflict?: {
180
+ enabled?: boolean;
181
+ severity?: 'error' | 'warn';
182
+ };
183
+ workflowSecurity?: {
184
+ enabled?: boolean;
185
+ ignore?: string[];
186
+ };
187
+ dockerfileSecurity?: {
188
+ enabled?: boolean;
189
+ ignore?: string[];
190
+ };
191
+ caseCollision?: {
192
+ enabled?: boolean;
193
+ };
194
+ configValidation?: {
195
+ enabled?: boolean;
196
+ };
197
+ vulnScan?: {
198
+ enabled?: boolean;
199
+ };
200
+ bannedImports?: Array<{
201
+ name: string;
202
+ message?: string;
203
+ severity?: 'error' | 'warn';
204
+ }>;
205
+ licenseHeader?: {
206
+ enabled?: boolean;
207
+ pattern?: string;
208
+ lines?: number;
124
209
  };
125
210
  sensitiveFileGuard?: {
126
211
  enabled?: boolean;
@@ -131,6 +216,21 @@ export interface PRCheckMateConfig {
131
216
  kotlin?: {
132
217
  enabled?: boolean;
133
218
  };
219
+ go?: {
220
+ enabled?: boolean;
221
+ };
222
+ rust?: {
223
+ enabled?: boolean;
224
+ };
225
+ csharp?: {
226
+ enabled?: boolean;
227
+ };
228
+ ruby?: {
229
+ enabled?: boolean;
230
+ };
231
+ php?: {
232
+ enabled?: boolean;
233
+ };
134
234
  deadCode?: {
135
235
  enabled?: boolean;
136
236
  /**
@@ -7,12 +7,17 @@ const node_fs_1 = __importDefault(require("node:fs"));
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
8
  const utils_1 = require("../utils");
9
9
  const configFiles = [
10
- { source: 'src/config/eslint.config.mjs', dest: 'eslint.config.mjs' },
10
+ // Source is named so ESLint's auto-discovery does NOT treat it as a nested
11
+ // config for files under src/config/ — it ships to the package root as the
12
+ // canonical `eslint.config.mjs`.
13
+ { source: 'src/config/eslint-flat-config.mjs', dest: 'eslint.config.mjs' },
14
+ { source: 'src/config/eslint-default-rules.json', dest: 'eslint-default-rules.json' },
11
15
  { source: 'src/config/.eslintrc.js', dest: '.eslintrc.js' },
12
16
  { source: 'src/config/.prettierrc', dest: '.prettierrc' },
13
17
  { source: 'src/config/cspell.json', dest: 'cspell.json' },
14
18
  { source: 'tsconfig.json', dest: 'tsconfig.json' },
15
19
  { source: 'src/config/pr-checkmate-workflow.yml', dest: 'dist/config/pr-checkmate-workflow.yml' },
20
+ { source: 'src/config/pr-checkmate.schema.json', dest: 'pr-checkmate.schema.json' },
16
21
  ];
17
22
  /**
18
23
  *
@@ -1,4 +1,4 @@
1
- export type Language = 'typescript' | 'javascript' | 'python' | 'cpp' | 'swift' | 'kotlin';
1
+ export type Language = 'typescript' | 'javascript' | 'python' | 'cpp' | 'swift' | 'kotlin' | 'go' | 'rust' | 'csharp' | 'ruby' | 'php';
2
2
  /**
3
3
  * Detects which languages a repository uses, combining marker files (definitive)
4
4
  * with tracked source-file counts (breadth). A language is reported when it has a
@@ -7,6 +7,7 @@ exports.detectLanguages = detectLanguages;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const execa_1 = require("./execa");
10
+ const glob_1 = require("./glob");
10
11
  const logger_1 = require("./logger");
11
12
  /**
12
13
  * Marker files that unambiguously indicate a language's presence, independent of
@@ -26,6 +27,13 @@ const MARKER_FILES = {
26
27
  'settings.gradle.kts',
27
28
  'AndroidManifest.xml',
28
29
  ],
30
+ go: ['go.mod'],
31
+ rust: ['Cargo.toml'],
32
+ // Glob markers (`*.csproj`/`*.sln`) — matched against root dir entries, since
33
+ // C# projects have no single fixed marker filename.
34
+ csharp: ['*.csproj', '*.sln'],
35
+ ruby: ['Gemfile', '.rubocop.yml'],
36
+ php: ['composer.json'],
29
37
  };
30
38
  /**
31
39
  * Source extensions per language, used to count tracked files via `git ls-files`.
@@ -37,9 +45,25 @@ const EXTENSIONS = {
37
45
  cpp: ['.cpp', '.cc', '.cxx', '.c++', '.hpp', '.hh', '.hxx'],
38
46
  swift: ['.swift'],
39
47
  kotlin: ['.kt', '.kts', '.java'],
48
+ go: ['.go'],
49
+ rust: ['.rs'],
50
+ csharp: ['.cs'],
51
+ ruby: ['.rb', '.rake'],
52
+ php: ['.php'],
40
53
  };
41
54
  function hasMarker(root, lang) {
42
- return MARKER_FILES[lang].some(name => node_fs_1.default.existsSync(node_path_1.default.join(root, name)));
55
+ return MARKER_FILES[lang].some(name => {
56
+ if (!name.includes('*'))
57
+ return node_fs_1.default.existsSync(node_path_1.default.join(root, name));
58
+ // Glob marker (e.g. `*.csproj`): match against root-directory entries.
59
+ try {
60
+ const re = (0, glob_1.globToRegExp)(name);
61
+ return node_fs_1.default.readdirSync(root).some(entry => re.test(entry));
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ });
43
67
  }
44
68
  /**
45
69
  * Counts tracked files per language using `git ls-files`. Returns an empty map
@@ -54,6 +78,11 @@ async function countTrackedFiles(root) {
54
78
  cpp: 0,
55
79
  swift: 0,
56
80
  kotlin: 0,
81
+ go: 0,
82
+ rust: 0,
83
+ csharp: 0,
84
+ ruby: 0,
85
+ php: 0,
57
86
  };
58
87
  try {
59
88
  const { stdout } = await (0, execa_1.execa)('git', ['ls-files'], { cwd: root });
@@ -87,7 +116,7 @@ async function detectLanguages(root = process.cwd()) {
87
116
  const counts = await countTrackedFiles(root);
88
117
  const detected = Object.keys(EXTENSIONS).filter(lang => hasMarker(root, lang) || counts[lang] > 0);
89
118
  detected.sort((a, b) => counts[b] - counts[a]);
90
- logger_1.logger.info(`[detectLanguages]: Detected ${detected.length ? detected.join(', ') : 'none'} ` +
91
- `(counts: ts=${counts.typescript}, js=${counts.javascript}, py=${counts.python}, cpp=${counts.cpp}, swift=${counts.swift}, kt=${counts.kotlin})`);
119
+ const countStr = Object.keys(counts).map(l => `${l}=${counts[l]}`).join(', ');
120
+ logger_1.logger.info(`[detectLanguages]: Detected ${detected.length ? detected.join(', ') : 'none'} (counts: ${countStr})`);
92
121
  return detected;
93
122
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Minimal glob → RegExp for path matching against config patterns. Supports `*`
3
+ * (matches within a path segment), `**` (across segments), and a leading `** /`
4
+ * that also matches the repo root (zero segments). Anchored to the whole path.
5
+ * Not a full glob implementation — enough for the allow/ignore lists in
6
+ * pr-checkmate.json (e.g. "assets/**", "*.png", "** / *.d.ts").
7
+ */
8
+ export declare function globToRegExp(glob: string): RegExp;
9
+ /** True when `file` matches any of the glob `patterns`. */
10
+ export declare function matchesAnyGlob(file: string, patterns: string[]): boolean;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.globToRegExp = globToRegExp;
4
+ exports.matchesAnyGlob = matchesAnyGlob;
5
+ /**
6
+ * Minimal glob → RegExp for path matching against config patterns. Supports `*`
7
+ * (matches within a path segment), `**` (across segments), and a leading `** /`
8
+ * that also matches the repo root (zero segments). Anchored to the whole path.
9
+ * Not a full glob implementation — enough for the allow/ignore lists in
10
+ * pr-checkmate.json (e.g. "assets/**", "*.png", "** / *.d.ts").
11
+ */
12
+ function globToRegExp(glob) {
13
+ const re = glob
14
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex specials (not * or /)
15
+ .replace(/\*\*\//g, ' dir ') // `**/` → optional leading dirs (matches root too)
16
+ .replace(/\*\*/g, ' any ') // bare `**` → across segments
17
+ .replace(/\*/g, '[^/]*') // `*` → within a segment
18
+ .replace(/ dir /g, '(?:.*/)?')
19
+ .replace(/ any /g, '.*');
20
+ return new RegExp(`^${re}$`);
21
+ }
22
+ /** True when `file` matches any of the glob `patterns`. */
23
+ function matchesAnyGlob(file, patterns) {
24
+ return patterns.some(p => globToRegExp(p).test(file));
25
+ }
@@ -1,9 +1,9 @@
1
1
  export * from './logger';
2
- export * from './delta';
3
2
  export * from './result-collector';
4
3
  export * from './resolve-bin';
5
4
  export * from './own-deps';
6
5
  export * from './detect-language';
6
+ export * from './glob';
7
7
  /**
8
8
  *
9
9
  */
@@ -21,11 +21,11 @@ exports.cleanupTempFile = cleanupTempFile;
21
21
  const node_fs_1 = __importDefault(require("node:fs"));
22
22
  const logger_1 = require("./logger");
23
23
  __exportStar(require("./logger"), exports);
24
- __exportStar(require("./delta"), exports);
25
24
  __exportStar(require("./result-collector"), exports);
26
25
  __exportStar(require("./resolve-bin"), exports);
27
26
  __exportStar(require("./own-deps"), exports);
28
27
  __exportStar(require("./detect-language"), exports);
28
+ __exportStar(require("./glob"), exports);
29
29
  /**
30
30
  *
31
31
  */