pr-checkmate 2.1.0 → 3.0.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 (109) hide show
  1. package/README.md +63 -267
  2. package/cspell.json +155 -133
  3. package/dist/config/constants.d.ts +13 -1
  4. package/dist/config/constants.js +20 -7
  5. package/dist/config/index.d.ts +23 -11
  6. package/dist/config/index.js +27 -33
  7. package/dist/core/checks/dependencies/banned-imports.js +4 -1
  8. package/dist/core/checks/dependencies/circular-deps.js +14 -5
  9. package/dist/core/checks/dependencies/dependency-check.js +2 -2
  10. package/dist/core/checks/dependencies/grype-scan.js +2 -1
  11. package/dist/core/checks/dependencies/license-check.js +1 -6
  12. package/dist/core/checks/dependencies/npm-audit.js +1 -1
  13. package/dist/core/checks/dependencies/outdated-deps.js +1 -6
  14. package/dist/core/checks/dependencies/vuln-scan.js +2 -1
  15. package/dist/core/checks/findings.d.ts +46 -2
  16. package/dist/core/checks/findings.js +140 -2
  17. package/dist/core/checks/git/custom-rules.js +2 -0
  18. package/dist/core/checks/git/diff-smell.js +6 -1
  19. package/dist/core/checks/git/leftover-debug.js +8 -2
  20. package/dist/core/checks/git/lockfile-drift.js +6 -1
  21. package/dist/core/checks/git/merge-conflict.js +1 -1
  22. package/dist/core/checks/git/missing-tests.js +1 -1
  23. package/dist/core/checks/git/todo-fixme.js +10 -2
  24. package/dist/core/checks/languages/cpp-format.js +19 -42
  25. package/dist/core/checks/languages/external-checks.d.ts +77 -0
  26. package/dist/core/checks/languages/external-checks.js +200 -0
  27. package/dist/core/checks/languages/external-runner.d.ts +119 -0
  28. package/dist/core/checks/languages/external-runner.js +143 -0
  29. package/dist/core/checks/languages/go-format.d.ts +4 -4
  30. package/dist/core/checks/languages/go-format.js +151 -30
  31. package/dist/core/checks/languages/lint.js +19 -19
  32. package/dist/core/checks/languages/prettier.js +2 -7
  33. package/dist/core/checks/languages/python-typecheck.d.ts +10 -5
  34. package/dist/core/checks/languages/python-typecheck.js +161 -37
  35. package/dist/core/checks/languages/typecheck.js +10 -8
  36. package/dist/core/checks/pr/commitlint.js +7 -1
  37. package/dist/core/checks/quality/case-collision.js +6 -4
  38. package/dist/core/checks/quality/config-validation.d.ts +0 -7
  39. package/dist/core/checks/quality/config-validation.js +30 -0
  40. package/dist/core/checks/quality/duplicate-check.js +86 -28
  41. package/dist/core/checks/quality/license-header.js +7 -4
  42. package/dist/core/checks/quality/markdown-lint.js +1 -6
  43. package/dist/core/checks/quality/spellcheck.js +34 -5
  44. package/dist/core/checks/quality/symlinks.js +6 -4
  45. package/dist/core/checks/security/diff-security.js +26 -18
  46. package/dist/core/checks/security/dockerfile-security.js +7 -1
  47. package/dist/core/checks/security/migration-safety.js +32 -4
  48. package/dist/core/checks/security/security.d.ts +56 -0
  49. package/dist/core/checks/security/security.js +135 -11
  50. package/dist/core/checks/security/sensitive-file-guard.js +40 -6
  51. package/dist/core/checks/security/workflow-security.js +7 -1
  52. package/dist/core/checks/tool-install.d.ts +102 -0
  53. package/dist/core/checks/tool-install.js +69 -0
  54. package/dist/core/context.js +9 -1
  55. package/dist/core/install/cache.js +42 -11
  56. package/dist/core/install/doctor.d.ts +2 -2
  57. package/dist/core/install/doctor.js +17 -16
  58. package/dist/core/install/ensure.js +22 -3
  59. package/dist/core/install/lock.d.ts +2 -1
  60. package/dist/core/install/prepare.d.ts +75 -0
  61. package/dist/core/install/prepare.js +151 -0
  62. package/dist/core/install/profiles.d.ts +17 -3
  63. package/dist/core/install/profiles.js +27 -0
  64. package/dist/core/install/reuse.d.ts +41 -0
  65. package/dist/core/install/reuse.js +107 -0
  66. package/dist/core/registry.js +10 -18
  67. package/dist/core/reporters.js +8 -2
  68. package/dist/core/run-checks.js +74 -3
  69. package/dist/core/targets.d.ts +116 -5
  70. package/dist/core/targets.js +101 -16
  71. package/dist/core/types.d.ts +26 -2
  72. package/dist/core/types.js +28 -0
  73. package/dist/init.js +21 -1
  74. package/dist/interfaces/index.d.ts +1 -0
  75. package/dist/utils/detect-language.js +11 -1
  76. package/dist/utils/index.d.ts +1 -0
  77. package/dist/utils/index.js +1 -0
  78. package/dist/utils/package-version.d.ts +4 -0
  79. package/dist/utils/package-version.js +41 -0
  80. package/dist/utils/resolve-bin.d.ts +56 -6
  81. package/dist/utils/resolve-bin.js +136 -26
  82. package/dist/utils/update-notifier.js +5 -7
  83. package/gitleaks.toml +14 -0
  84. package/install-manifests/go/package-lock.json +24 -0
  85. package/install-manifests/go/package.json +8 -0
  86. package/install-manifests/pytypes/package-lock.json +45 -0
  87. package/install-manifests/pytypes/package.json +8 -0
  88. package/package.json +4 -2
  89. package/pr-checkmate.schema.json +8 -2
  90. package/src/config/cspell.json +155 -133
  91. package/src/config/pr-checkmate.schema.json +8 -2
  92. package/dist/core/checks/languages/csharp-format.d.ts +0 -13
  93. package/dist/core/checks/languages/csharp-format.js +0 -52
  94. package/dist/core/checks/languages/go-lint.d.ts +0 -8
  95. package/dist/core/checks/languages/go-lint.js +0 -48
  96. package/dist/core/checks/languages/kotlin-lint.d.ts +0 -2
  97. package/dist/core/checks/languages/kotlin-lint.js +0 -42
  98. package/dist/core/checks/languages/php-format.d.ts +0 -9
  99. package/dist/core/checks/languages/php-format.js +0 -49
  100. package/dist/core/checks/languages/ruby-lint.d.ts +0 -8
  101. package/dist/core/checks/languages/ruby-lint.js +0 -50
  102. package/dist/core/checks/languages/rust-format.d.ts +0 -9
  103. package/dist/core/checks/languages/rust-format.js +0 -45
  104. package/dist/core/checks/languages/rust-lint.d.ts +0 -8
  105. package/dist/core/checks/languages/rust-lint.js +0 -50
  106. package/dist/core/checks/languages/shellcheck.d.ts +0 -8
  107. package/dist/core/checks/languages/shellcheck.js +0 -55
  108. package/dist/core/checks/languages/swift-lint.d.ts +0 -2
  109. package/dist/core/checks/languages/swift-lint.js +0 -46
@@ -52,6 +52,12 @@ export declare const DIFF_SECURITY_IGNORE_DEFAULTS: string[];
52
52
  * committed reappears in it — 39 of 42 spellcheck findings in one real repository
53
53
  * came from that one file, none of them actionable: the text cannot be corrected
54
54
  * without rewriting git history, and the next release regenerates it anyway.
55
+ *
56
+ * Matched at ANY depth, not just at the repository root. The root-only form was
57
+ * the original, and it left every nested changelog unhandled: a component-config
58
+ * repository with one per component produced 157 of 157 spelling findings and 240
59
+ * of 246 markdown findings from files nobody writes by hand. A generated file is
60
+ * generated wherever it sits.
55
61
  */
56
62
  export declare const GENERATED_DOC_FILES: string[];
57
63
  /**
@@ -70,6 +76,13 @@ export declare const CONFIG_FILE_PATTERNS: string[];
70
76
  * config/CI YAML and JSON out of the report — they are data, not code, and their
71
77
  * repetition is not meaningful duplication. This mirrors the code-only file set the
72
78
  * check already uses when scanning a git diff, so full-tree and diff scans agree.
79
+ *
80
+ * One entry per language pr-checkmate detects, all of them verified against the
81
+ * jscpd binary rather than assumed: the list used to stop at C++ while the check
82
+ * only ever handed jscpd `.ts`/`.js` files, so the Go, Rust, C#, Ruby and PHP
83
+ * halves were unreachable and the Python one was decoration. Copy-paste is a
84
+ * language-independent smell, and the tokenizer behind this check has always been
85
+ * able to see it — the file list was the only thing in the way.
73
86
  */
74
87
  export declare const DUPLICATE_CODE_FORMATS: string[];
75
88
  /**
@@ -129,7 +142,6 @@ export declare const documentation: {
129
142
  prettier: string;
130
143
  cspell: string;
131
144
  };
132
- export declare const CONFIG_FILE: string;
133
145
  /**
134
146
  * Every top-level key PR CheckMate recognises in pr-checkmate.json — the union
135
147
  * of the `PRCheckMateConfig` interface fields and the keys the `init` generator
@@ -1,10 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
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
- const node_path_1 = __importDefault(require("node:path"));
3
+ exports.KNOWN_CONFIG_KEYS = 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;
8
4
  /**
9
5
  * Build output, dependency, tooling, and report directories that are never
10
6
  * product code. Every check excludes them by directory-name segment match (see
@@ -156,8 +152,14 @@ exports.DIFF_SECURITY_IGNORE_DEFAULTS = [...exports.TEST_PATH_GLOBS];
156
152
  * committed reappears in it — 39 of 42 spellcheck findings in one real repository
157
153
  * came from that one file, none of them actionable: the text cannot be corrected
158
154
  * without rewriting git history, and the next release regenerates it anyway.
155
+ *
156
+ * Matched at ANY depth, not just at the repository root. The root-only form was
157
+ * the original, and it left every nested changelog unhandled: a component-config
158
+ * repository with one per component produced 157 of 157 spelling findings and 240
159
+ * of 246 markdown findings from files nobody writes by hand. A generated file is
160
+ * generated wherever it sits.
159
161
  */
160
- exports.GENERATED_DOC_FILES = ['CHANGELOG.md'];
162
+ exports.GENERATED_DOC_FILES = ['**/CHANGELOG.md'];
161
163
  /**
162
164
  * Files excluded from the dead-code (unused-exports) report by default. These are
163
165
  * REGEX sources (matched against each reported file path), not globs — dead-code's
@@ -192,6 +194,13 @@ exports.CONFIG_FILE_PATTERNS = [
192
194
  * config/CI YAML and JSON out of the report — they are data, not code, and their
193
195
  * repetition is not meaningful duplication. This mirrors the code-only file set the
194
196
  * check already uses when scanning a git diff, so full-tree and diff scans agree.
197
+ *
198
+ * One entry per language pr-checkmate detects, all of them verified against the
199
+ * jscpd binary rather than assumed: the list used to stop at C++ while the check
200
+ * only ever handed jscpd `.ts`/`.js` files, so the Go, Rust, C#, Ruby and PHP
201
+ * halves were unreachable and the Python one was decoration. Copy-paste is a
202
+ * language-independent smell, and the tokenizer behind this check has always been
203
+ * able to see it — the file list was the only thing in the way.
195
204
  */
196
205
  exports.DUPLICATE_CODE_FORMATS = [
197
206
  'javascript',
@@ -203,6 +212,11 @@ exports.DUPLICATE_CODE_FORMATS = [
203
212
  'swift',
204
213
  'c',
205
214
  'cpp',
215
+ 'csharp',
216
+ 'go',
217
+ 'php',
218
+ 'ruby',
219
+ 'rust',
206
220
  ];
207
221
  /**
208
222
  * File extensions to check
@@ -267,7 +281,6 @@ exports.documentation = {
267
281
  prettier: 'https://prettier.io/docs/en/options.html',
268
282
  cspell: 'https://cspell.org/configuration/',
269
283
  };
270
- exports.CONFIG_FILE = node_path_1.default.resolve(process.cwd(), 'pr-checkmate.json');
271
284
  /**
272
285
  * Every top-level key PR CheckMate recognises in pr-checkmate.json — the union
273
286
  * of the `PRCheckMateConfig` interface fields and the keys the `init` generator
@@ -1,23 +1,35 @@
1
1
  export * from './constants';
2
2
  export { loadProjectConfig } from '../config/loader';
3
3
  /**
4
- * Get the configured source path(s) dynamically as a normalized list.
5
- * Accepts a single string or an array in pr-checkmate.json; empty/invalid
6
- * values fall back to `['.']` (the whole repo). Clients narrow scanning to a
7
- * subdirectory by setting `sourcePath` in their pr-checkmate.json. Always reads
8
- * fresh config.
4
+ * The configured source path(s), normalised to a list.
5
+ *
6
+ * Accepts a string or an array; empty and invalid values fall back to `['.']`,
7
+ * the whole repository. Clients narrow scanning to a subdirectory by setting
8
+ * `sourcePath`.
9
+ *
10
+ * `cwd` is a parameter rather than `process.cwd()` baked in, and that is the
11
+ * whole of ticket 074. This used to read a `CONFIG_FILE` constant resolved at
12
+ * *module load*, so the answer was fixed to whatever directory the process
13
+ * started in — `chdir` could not change it and neither could a caller. For the
14
+ * CLI that is harmless, since its working directory never moves; for
15
+ * `runChecks({ cwd })` it meant the SDK consumer's own `sourcePath` was applied
16
+ * to somebody else's repository, silently scoping every scoped check to a
17
+ * directory that may not exist there.
18
+ *
19
+ * It also read the file itself, duplicating `loadProjectConfig` with its own
20
+ * defaults. One reader is now the only reader.
9
21
  */
10
- export declare function getSourcePaths(): string[];
22
+ export declare function getSourcePaths(cwd?: string): string[];
11
23
  /**
12
- * Get a single source path (first configured entry) for callers that only
13
- * support one directory. Prefer `getSourcePaths()` for scan targets.
24
+ * The first configured source path, for callers that only support one directory.
25
+ * Prefer {@link getSourcePaths} for scan targets.
14
26
  */
15
- export declare function getSourcePath(): string;
27
+ export declare function getSourcePath(cwd?: string): string;
16
28
  /**
17
29
  * Get the effective list of directory names excluded from every check
18
30
  * (client `ignoreDirs` in pr-checkmate.json, or the curated default).
19
31
  */
20
- export declare function getIgnoredDirs(): string[];
32
+ export declare function getIgnoredDirs(cwd?: string): string[];
21
33
  /**
22
34
  * Get path to package config files
23
35
  */
@@ -25,4 +37,4 @@ export declare function getPackageConfigPath(fileName: string): string;
25
37
  /**
26
38
  * Check if project has local config
27
39
  */
28
- export declare function hasLocalConfig(configNames: string[]): boolean;
40
+ export declare function hasLocalConfig(configNames: string[], cwd?: string): boolean;
@@ -26,51 +26,46 @@ exports.hasLocalConfig = hasLocalConfig;
26
26
  const node_fs_1 = __importDefault(require("node:fs"));
27
27
  const node_path_1 = __importDefault(require("node:path"));
28
28
  const loader_1 = require("../config/loader");
29
- const utils_1 = require("../utils");
30
- const constants_1 = require("./constants");
31
29
  __exportStar(require("./constants"), exports);
32
30
  var loader_2 = require("../config/loader");
33
31
  Object.defineProperty(exports, "loadProjectConfig", { enumerable: true, get: function () { return loader_2.loadProjectConfig; } });
34
32
  /**
35
- * Load configuration dynamically (always reads fresh config)
33
+ * The configured source path(s), normalised to a list.
34
+ *
35
+ * Accepts a string or an array; empty and invalid values fall back to `['.']`,
36
+ * the whole repository. Clients narrow scanning to a subdirectory by setting
37
+ * `sourcePath`.
38
+ *
39
+ * `cwd` is a parameter rather than `process.cwd()` baked in, and that is the
40
+ * whole of ticket 074. This used to read a `CONFIG_FILE` constant resolved at
41
+ * *module load*, so the answer was fixed to whatever directory the process
42
+ * started in — `chdir` could not change it and neither could a caller. For the
43
+ * CLI that is harmless, since its working directory never moves; for
44
+ * `runChecks({ cwd })` it meant the SDK consumer's own `sourcePath` was applied
45
+ * to somebody else's repository, silently scoping every scoped check to a
46
+ * directory that may not exist there.
47
+ *
48
+ * It also read the file itself, duplicating `loadProjectConfig` with its own
49
+ * defaults. One reader is now the only reader.
36
50
  */
37
- function loadConfig() {
38
- try {
39
- if (node_fs_1.default.existsSync(constants_1.CONFIG_FILE)) {
40
- const configContent = node_fs_1.default.readFileSync(constants_1.CONFIG_FILE, 'utf-8');
41
- return JSON.parse(configContent);
42
- }
43
- }
44
- catch (err) {
45
- utils_1.logger.warn(`⚠️ Failed to read config from ${constants_1.CONFIG_FILE}, using defaults${err}`);
46
- }
47
- return { sourcePath: '.' };
48
- }
49
- /**
50
- * Get the configured source path(s) dynamically as a normalized list.
51
- * Accepts a single string or an array in pr-checkmate.json; empty/invalid
52
- * values fall back to `['.']` (the whole repo). Clients narrow scanning to a
53
- * subdirectory by setting `sourcePath` in their pr-checkmate.json. Always reads
54
- * fresh config.
55
- */
56
- function getSourcePaths() {
57
- const { sourcePath } = loadConfig();
51
+ function getSourcePaths(cwd = process.cwd()) {
52
+ const { sourcePath } = (0, loader_1.loadProjectConfig)(cwd);
58
53
  const paths = (Array.isArray(sourcePath) ? sourcePath : [sourcePath]).filter((p) => typeof p === 'string' && p.trim().length > 0);
59
54
  return paths.length > 0 ? paths : ['.'];
60
55
  }
61
56
  /**
62
- * Get a single source path (first configured entry) for callers that only
63
- * support one directory. Prefer `getSourcePaths()` for scan targets.
57
+ * The first configured source path, for callers that only support one directory.
58
+ * Prefer {@link getSourcePaths} for scan targets.
64
59
  */
65
- function getSourcePath() {
66
- return getSourcePaths()[0];
60
+ function getSourcePath(cwd) {
61
+ return getSourcePaths(cwd)[0];
67
62
  }
68
63
  /**
69
64
  * Get the effective list of directory names excluded from every check
70
65
  * (client `ignoreDirs` in pr-checkmate.json, or the curated default).
71
66
  */
72
- function getIgnoredDirs() {
73
- return (0, loader_1.loadProjectConfig)().ignoreDirs ?? [];
67
+ function getIgnoredDirs(cwd) {
68
+ return (0, loader_1.loadProjectConfig)(cwd).ignoreDirs ?? [];
74
69
  }
75
70
  /**
76
71
  * Get path to package config files
@@ -82,7 +77,6 @@ function getPackageConfigPath(fileName) {
82
77
  /**
83
78
  * Check if project has local config
84
79
  */
85
- function hasLocalConfig(configNames) {
86
- const projectRoot = process.cwd();
87
- return configNames.some(name => node_fs_1.default.existsSync(node_path_1.default.join(projectRoot, name)));
80
+ function hasLocalConfig(configNames, cwd = process.cwd()) {
81
+ return configNames.some(name => node_fs_1.default.existsSync(node_path_1.default.join(cwd, name)));
88
82
  }
@@ -39,7 +39,7 @@ exports.bannedImportsCheck = {
39
39
  try {
40
40
  const errors = {};
41
41
  const warnings = {};
42
- await (0, findings_1.reviewLines)(ctx, ['*'], stdout => {
42
+ const reviewed = await (0, findings_1.reviewLines)(ctx, ['*'], stdout => {
43
43
  var _a;
44
44
  for (const raw of stdout.split('\n')) {
45
45
  if (!raw.startsWith('+') || raw.startsWith('+++'))
@@ -53,6 +53,9 @@ exports.bannedImportsCheck = {
53
53
  }
54
54
  }
55
55
  });
56
+ // An unreadable file list is not "no banned imports" (ticket 072).
57
+ if (!reviewed)
58
+ return (0, types_1.skip)('git unavailable');
56
59
  // A ban configured as "error" blocks the run, regardless of phase — that
57
60
  // is the default `failOn`, and the client can still relax it per repo.
58
61
  return (0, findings_1.reportSeverityBuckets)(errors, warnings, {
@@ -9,6 +9,7 @@ const node_path_1 = __importDefault(require("node:path"));
9
9
  const config_1 = require("../../../config");
10
10
  const utils_1 = require("../../../utils");
11
11
  const execa_1 = require("../../../utils/execa");
12
+ const reuse_1 = require("../../install/reuse");
12
13
  const targets_1 = require("../../targets");
13
14
  const types_1 = require("../../types");
14
15
  const findings_1 = require("../findings");
@@ -36,8 +37,10 @@ const SOURCE_GLOBS = ['*.ts', '*.tsx', '*.js', '*.jsx'];
36
37
  * dpdm from the registry on first use — and when that fetch could not happen
37
38
  * (offline or locked-down CI, private registry, exit 127) nothing was analyzed.
38
39
  */
39
- function resolveDpdm(cwd) {
40
- for (const options of [{ paths: [cwd] }, undefined]) {
40
+ function resolveDpdm(ctx) {
41
+ // No configuration of its own — dpdm reads the module graph — so the version
42
+ // check is the right question here and `clientOwnsConfig` stays off.
43
+ for (const options of [{ paths: (0, reuse_1.clientToolPaths)('dpdm', ctx) }, undefined]) {
41
44
  try {
42
45
  return (0, utils_1.resolvePackageBin)('dpdm', 'lib/bin/dpdm.js', options);
43
46
  }
@@ -80,7 +83,13 @@ exports.circularDepsCheck = {
80
83
  return ctx.languages.includes('typescript') || ctx.languages.includes('javascript');
81
84
  },
82
85
  async run(ctx) {
83
- const allFiles = await (0, targets_1.listTrackedFiles)(ctx, SOURCE_GLOBS, (0, config_1.getSourcePaths)());
86
+ const allFiles = await (0, targets_1.listTrackedFiles)(ctx, SOURCE_GLOBS, (0, config_1.getSourcePaths)(ctx.cwd));
87
+ // A cycle detector needs the WHOLE graph, so an unreadable file list is the
88
+ // one case where passing would be worst: half a graph produces confident
89
+ // nonsense, and no graph reported as `pass` says the repository is acyclic
90
+ // when nobody looked (ticket 072).
91
+ if (allFiles === null)
92
+ return (0, types_1.skip)('git unavailable');
84
93
  if (allFiles.length === 0)
85
94
  return (0, types_1.pass)();
86
95
  // Files the client exempted (generated code, vendored bundles) are dropped
@@ -90,7 +99,7 @@ exports.circularDepsCheck = {
90
99
  const files = ignore.length > 0 ? allFiles.filter(f => !(0, utils_1.matchesAnyGlob)(f, ignore)) : allFiles;
91
100
  if (files.length === 0)
92
101
  return (0, types_1.skip)('all files ignored');
93
- const dpdmBin = resolveDpdm(ctx.cwd);
102
+ const dpdmBin = resolveDpdm(ctx);
94
103
  if (!dpdmBin) {
95
104
  utils_1.logger.warn('[Circular Deps]: ⚠️ dpdm could not be resolved — skipping');
96
105
  return (0, types_1.skip)('dpdm unavailable');
@@ -114,7 +123,7 @@ exports.circularDepsCheck = {
114
123
  'circular:1',
115
124
  '-o',
116
125
  reportPath,
117
- ...toFileArgs(files, (0, config_1.getSourcePaths)()),
126
+ ...toFileArgs(files, (0, config_1.getSourcePaths)(ctx.cwd)),
118
127
  ], { cwd: ctx.cwd, reject: false, timeout });
119
128
  if (result.timedOut) {
120
129
  utils_1.logger.warn(`[Circular Deps]: ⚠️ dpdm timed out after ${timeout}ms — skipping`);
@@ -106,7 +106,7 @@ exports.dependencyCheck = {
106
106
  applies(ctx) {
107
107
  if (ctx.config.dependency?.enabled === false)
108
108
  return false;
109
- return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'package.json'));
109
+ return (0, types_1.isNpmProject)(ctx);
110
110
  },
111
111
  async run(ctx) {
112
112
  // Resolve the bundled knip binary and run it via the current node — never
@@ -122,7 +122,7 @@ exports.dependencyCheck = {
122
122
  // from a config file only, so they go into a temp one — but a client's own knip
123
123
  // config wins outright, exactly as it does for cspell.
124
124
  const ignore = ctx.config.dependency?.ignore ?? [];
125
- const clientOwnsConfig = (0, config_1.hasLocalConfig)(KNIP_CONFIG_FILES) || hasPackageJsonConfig(ctx.cwd);
125
+ const clientOwnsConfig = (0, config_1.hasLocalConfig)(KNIP_CONFIG_FILES, ctx.cwd) || hasPackageJsonConfig(ctx.cwd);
126
126
  let tempConfigPath = null;
127
127
  if (ignore.length > 0 && clientOwnsConfig) {
128
128
  // Say so out loud: a silently ignored `ignore` list looks like a broken check.
@@ -4,6 +4,7 @@ exports.grypeScanCheck = void 0;
4
4
  const utils_1 = require("../../../utils");
5
5
  const execa_1 = require("../../../utils/execa");
6
6
  const types_1 = require("../../types");
7
+ const tool_install_1 = require("../tool-install");
7
8
  const SEVERITY_ORDER = ['negligible', 'low', 'medium', 'high', 'critical'];
8
9
  /** grype artifact `type` → the ecosystem we can resolve direct deps for. */
9
10
  const TYPE_TO_ECOSYSTEM = {
@@ -69,7 +70,7 @@ exports.grypeScanCheck = {
69
70
  // execa v9 with reject:false resolves (not throws) with `code: 'ENOENT'`
70
71
  // when the binary is absent — detect the missing scanner on the result.
71
72
  if (result.code === 'ENOENT')
72
- return (0, types_1.skip)('grype not installed');
73
+ return (0, tool_install_1.notInstalled)('grype');
73
74
  if (result.timedOut) {
74
75
  utils_1.logger.warn(`[Grype Scan]: ⚠️ grype timed out after ${timeout}ms — skipping`);
75
76
  return (0, types_1.skip)('grype timed out');
@@ -1,11 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.licenseCheck = void 0;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
- const node_path_1 = __importDefault(require("node:path"));
9
4
  const utils_1 = require("../../../utils");
10
5
  const execa_1 = require("../../../utils/execa");
11
6
  const types_1 = require("../../types");
@@ -19,7 +14,7 @@ exports.licenseCheck = {
19
14
  name: 'License Check',
20
15
  phase: 'blocking',
21
16
  applies(ctx) {
22
- return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'package.json'));
17
+ return (0, types_1.isNpmProject)(ctx);
23
18
  },
24
19
  async run(ctx) {
25
20
  const allowPackages = new Set(ctx.config.licenseCheck?.allowPackages ?? []);
@@ -34,7 +34,7 @@ exports.npmAuditCheck = {
34
34
  applies(ctx) {
35
35
  if (ctx.config.security?.['npm-audit']?.enabled === false)
36
36
  return false;
37
- return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'package.json'));
37
+ return (0, types_1.isNpmProject)(ctx);
38
38
  },
39
39
  async run(ctx) {
40
40
  // `high`, not `moderate`. A moderate CVE in a direct dependency often has no
@@ -1,11 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.outdatedDepsCheck = void 0;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
- const node_path_1 = __importDefault(require("node:path"));
9
4
  const utils_1 = require("../../../utils");
10
5
  const execa_1 = require("../../../utils/execa");
11
6
  const types_1 = require("../../types");
@@ -16,7 +11,7 @@ exports.outdatedDepsCheck = {
16
11
  applies(ctx) {
17
12
  if (ctx.config.outdatedDeps?.enabled === false)
18
13
  return false;
19
- return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'package.json'));
14
+ return (0, types_1.isNpmProject)(ctx);
20
15
  },
21
16
  async run(ctx) {
22
17
  // `npm outdated` queries the registry for every dependency and can take a
@@ -4,6 +4,7 @@ exports.vulnScanCheck = void 0;
4
4
  const utils_1 = require("../../../utils");
5
5
  const execa_1 = require("../../../utils/execa");
6
6
  const types_1 = require("../../types");
7
+ const tool_install_1 = require("../tool-install");
7
8
  /**
8
9
  * Cross-language dependency vulnerability scan via osv-scanner, which reads
9
10
  * lockfiles across ecosystems (npm, PyPI, Go, crates, Maven, …) against the OSV
@@ -33,7 +34,7 @@ exports.vulnScanCheck = {
33
34
  // execa v9 with reject:false resolves (not throws) with `code: 'ENOENT'`
34
35
  // when the binary is absent — detect the missing scanner on the result.
35
36
  if (result.code === 'ENOENT')
36
- return (0, types_1.skip)('osv-scanner not installed');
37
+ return (0, tool_install_1.notInstalled)('osv-scanner');
37
38
  if (result.timedOut) {
38
39
  utils_1.logger.warn(`[Vuln Scan]: ⚠️ osv-scanner timed out after ${timeout}ms — skipping`);
39
40
  return (0, types_1.skip)('osv-scanner timed out');
@@ -74,8 +74,15 @@ export declare function scanTrackedFiles(ctx: CheckContext, files: string[], onF
74
74
  *
75
75
  * `onChunk` is called once per diff in delta mode and once per file in full mode,
76
76
  * which keeps a whole monorepo from being concatenated before matching starts.
77
+ *
78
+ * Returns whether the review actually happened. `false` means git could not
79
+ * produce a file list, and the caller has to say so rather than report a pass:
80
+ * a check that reviewed nothing looks exactly like a check that found nothing,
81
+ * and that is the whole of ticket 072. `fullScanBehaviour: "skip"` is the one
82
+ * case where `true` is returned having reviewed nothing on purpose — the client
83
+ * asked for that, so it is not a hole in coverage.
77
84
  */
78
- export declare function reviewLines(ctx: CheckContext, globs: string[], onChunk: (stdout: string) => void): Promise<void>;
85
+ export declare function reviewLines(ctx: CheckContext, globs: string[], onChunk: (stdout: string) => void): Promise<boolean>;
79
86
  /**
80
87
  * Which comment markers apply to a file, since they are not universal:
81
88
  * - `c-like` — `//` only. `#` is NOT a comment in C-family syntax, where it
@@ -129,6 +136,17 @@ export interface DiffPattern {
129
136
  */
130
137
  view?: 'raw' | 'code';
131
138
  }
139
+ /**
140
+ * How many added lines a taint survives, for the one check that asks for it.
141
+ *
142
+ * Exported because the default belongs to Diff Security, not to this function:
143
+ * `scanDiffPatterns` is shared by Diff Smells, Workflow Security and Dockerfile
144
+ * Security too, and defaulting the heuristic ON here leaked a finding labelled
145
+ * "SQL injection risk" into a code-smell report — under the wrong check, three
146
+ * times per run, off a config key (`diffSecurity.dataflowWindow`) that has no
147
+ * meaning for any of them. Caught by our own Diff Smells row.
148
+ */
149
+ export declare const DEFAULT_DATAFLOW_WINDOW = 10;
132
150
  /**
133
151
  * Bucket added diff lines by the {@link DiffPattern}s they match, split by
134
152
  * severity. Patterns are expected to anchor on the leading `+` themselves
@@ -150,8 +168,34 @@ export declare function scanDiffPatterns(stdout: string, patterns: readonly Diff
150
168
  comments?: 'language' | 'legacy' | 'off';
151
169
  /** Globs whose files are skipped entirely (client `<check>.ignorePaths`). */
152
170
  ignorePaths?: string[];
171
+ /**
172
+ * Added lines a taint survives for the cross-line SQL heuristic.
173
+ *
174
+ * Off unless the caller asks. Only Diff Security owns this behaviour — it is
175
+ * configured by `diffSecurity.dataflowWindow` and reports a security label —
176
+ * so every other caller of this function gets a plain pattern scan.
177
+ */
178
+ dataflowWindow?: number;
153
179
  }): SeverityBuckets;
154
- /** Total number of findings across all labels in a bucket. */
180
+ /**
181
+ * Log a capped sample of raw output lines under a one-line summary.
182
+ *
183
+ * The cap is the point: a linter with 400 offences must not push the rest of the
184
+ * report out of a CI log's scrollback, and the count that follows the sample is
185
+ * what keeps the truncation from reading as the whole story.
186
+ *
187
+ * Deliberately not the private `logFindings` below, which this nearly collided
188
+ * with. That one takes findings already grouped into labelled buckets and pairs
189
+ * the log with a severity; this one takes lines a tool printed and does nothing
190
+ * but show them, which is what a check wrapping somebody else's output needs and
191
+ * the only thing it can honestly claim about them.
192
+ *
193
+ * Here rather than beside its first caller because three categories want it now —
194
+ * the language runner, the Python type check, and Duplicate Code — and a
195
+ * `quality/` check importing from `languages/` to get a logger was the wrong
196
+ * shape (ticket 079).
197
+ */
198
+ export declare function logSample(name: string, summary: string, lines: string[]): void;
155
199
  export declare function bucketTotal(bucket: FindingBucket): number;
156
200
  /**
157
201
  * Report a bucket at the check's configured level, summarized as