pr-checkmate 3.2.3 → 4.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 (174) hide show
  1. package/.eslintrc.js +3 -9
  2. package/LICENSE +8 -3
  3. package/README.md +54 -5
  4. package/dist/cjs/agent/hook.d.ts +37 -0
  5. package/dist/cjs/agent/hook.js +175 -0
  6. package/dist/cjs/agent/registration.d.ts +10 -0
  7. package/dist/cjs/agent/registration.js +28 -0
  8. package/dist/cjs/agent/setup.d.ts +20 -0
  9. package/dist/cjs/agent/setup.js +229 -0
  10. package/dist/cjs/cli.js +101 -0
  11. package/dist/cjs/config/constants.d.ts +2 -0
  12. package/dist/cjs/config/constants.js +5 -1
  13. package/dist/cjs/config/pr-checkmate.template.json +11 -1
  14. package/dist/cjs/core/checks/dependencies/grype-scan.js +18 -0
  15. package/dist/cjs/core/checks/findings.js +33 -7
  16. package/dist/cjs/core/checks/git/large-files.d.ts +1 -0
  17. package/dist/cjs/core/checks/git/large-files.js +3 -3
  18. package/dist/cjs/core/checks/languages/lint.js +12 -0
  19. package/dist/cjs/core/checks/languages/ruff-shared.js +6 -1
  20. package/dist/cjs/core/checks/pr/commitlint.js +4 -2
  21. package/dist/cjs/core/checks/pr/pr-size-check.d.ts +2 -0
  22. package/dist/cjs/core/checks/pr/pr-size-check.js +5 -5
  23. package/dist/cjs/core/checks/quality/package-size.d.ts +23 -1
  24. package/dist/cjs/core/checks/quality/package-size.js +20 -4
  25. package/dist/cjs/core/checks/quality/spellcheck.js +8 -5
  26. package/dist/cjs/core/checks/security/agent-instructions.d.ts +4 -0
  27. package/dist/cjs/core/checks/security/agent-instructions.js +149 -0
  28. package/dist/cjs/core/checks/security/diff-security.d.ts +7 -0
  29. package/dist/cjs/core/checks/security/diff-security.js +21 -1
  30. package/dist/cjs/core/checks/security/security.d.ts +0 -13
  31. package/dist/cjs/core/checks/security/security.js +27 -0
  32. package/dist/cjs/core/checks/security/workflow-security.js +33 -0
  33. package/dist/cjs/core/commands.d.ts +6 -0
  34. package/dist/cjs/core/commands.js +11 -1
  35. package/dist/cjs/core/finding-store.d.ts +16 -0
  36. package/dist/cjs/core/finding-store.js +73 -0
  37. package/dist/cjs/core/install/doctor.js +12 -0
  38. package/dist/cjs/core/install/grype-db.d.ts +25 -0
  39. package/dist/cjs/core/install/grype-db.js +45 -0
  40. package/dist/cjs/core/install/profiles.d.ts +1 -1
  41. package/dist/cjs/core/install/profiles.js +1 -1
  42. package/dist/cjs/core/install/reuse.js +6 -3
  43. package/dist/cjs/core/registry.js +2 -0
  44. package/dist/cjs/core/reporters.js +9 -0
  45. package/dist/cjs/core/run-checks.js +15 -5
  46. package/dist/cjs/core/sarif.d.ts +13 -0
  47. package/dist/cjs/core/sarif.js +60 -0
  48. package/dist/cjs/core/types.d.ts +7 -2
  49. package/dist/cjs/index.d.ts +2 -1
  50. package/dist/cjs/index.js +4 -1
  51. package/dist/cjs/init.d.ts +0 -8
  52. package/dist/cjs/init.js +12 -2
  53. package/dist/cjs/interfaces/index.d.ts +12 -0
  54. package/dist/cjs/mcp/config-tools.d.ts +30 -0
  55. package/dist/cjs/mcp/config-tools.js +320 -0
  56. package/dist/cjs/mcp/dependency.d.ts +33 -0
  57. package/dist/cjs/mcp/dependency.js +407 -0
  58. package/dist/cjs/mcp/protocol.d.ts +97 -0
  59. package/dist/cjs/mcp/protocol.js +181 -0
  60. package/dist/cjs/mcp/review.d.ts +63 -0
  61. package/dist/cjs/mcp/review.js +150 -0
  62. package/dist/cjs/mcp/serve.d.ts +8 -0
  63. package/dist/cjs/mcp/serve.js +49 -0
  64. package/dist/cjs/mcp/settings.d.ts +13 -0
  65. package/dist/cjs/mcp/settings.js +50 -0
  66. package/dist/cjs/mcp/tools.d.ts +23 -0
  67. package/dist/cjs/mcp/tools.js +633 -0
  68. package/dist/cjs/utils/execa.d.ts +7 -0
  69. package/dist/cjs/utils/execa.js +41 -1
  70. package/dist/cjs/utils/own-deps.js +8 -1
  71. package/dist/cjs/utils/resolve-bin.d.ts +2 -0
  72. package/dist/cjs/utils/resolve-bin.js +5 -0
  73. package/dist/cjs/utils/result-collector.d.ts +17 -1
  74. package/dist/cjs/utils/result-collector.js +7 -2
  75. package/dist/esm/agent/hook.d.ts +37 -0
  76. package/dist/esm/agent/hook.js +167 -0
  77. package/dist/esm/agent/registration.d.ts +10 -0
  78. package/dist/esm/agent/registration.js +21 -0
  79. package/dist/esm/agent/setup.d.ts +20 -0
  80. package/dist/esm/agent/setup.js +220 -0
  81. package/dist/esm/cli.js +100 -2
  82. package/dist/esm/config/constants.d.ts +2 -0
  83. package/dist/esm/config/constants.js +4 -0
  84. package/dist/esm/config/pr-checkmate.template.json +11 -1
  85. package/dist/esm/core/checks/dependencies/dependency-check.js +2 -2
  86. package/dist/esm/core/checks/dependencies/grype-scan.js +18 -0
  87. package/dist/esm/core/checks/dependencies/license-check.js +2 -2
  88. package/dist/esm/core/checks/dependencies/npm-audit.js +2 -2
  89. package/dist/esm/core/checks/findings.js +33 -7
  90. package/dist/esm/core/checks/git/diff-smell.js +1 -1
  91. package/dist/esm/core/checks/git/large-files.d.ts +1 -0
  92. package/dist/esm/core/checks/git/large-files.js +1 -1
  93. package/dist/esm/core/checks/languages/cpp-format.js +1 -1
  94. package/dist/esm/core/checks/languages/lint.js +12 -3
  95. package/dist/esm/core/checks/languages/prettier.js +3 -3
  96. package/dist/esm/core/checks/languages/python-format.js +2 -2
  97. package/dist/esm/core/checks/languages/python-lint.js +2 -2
  98. package/dist/esm/core/checks/languages/ruff-shared.js +6 -1
  99. package/dist/esm/core/checks/languages/typecheck.js +1 -1
  100. package/dist/esm/core/checks/pr/commitlint.js +4 -2
  101. package/dist/esm/core/checks/pr/pr-body.js +1 -1
  102. package/dist/esm/core/checks/pr/pr-size-check.d.ts +2 -0
  103. package/dist/esm/core/checks/pr/pr-size-check.js +3 -3
  104. package/dist/esm/core/checks/pr/pr-title-lint.js +1 -1
  105. package/dist/esm/core/checks/quality/dead-code.js +1 -1
  106. package/dist/esm/core/checks/quality/duplicate-check.js +1 -1
  107. package/dist/esm/core/checks/quality/markdown-lint.js +1 -1
  108. package/dist/esm/core/checks/quality/package-size.d.ts +23 -1
  109. package/dist/esm/core/checks/quality/package-size.js +19 -4
  110. package/dist/esm/core/checks/quality/spellcheck.js +11 -8
  111. package/dist/esm/core/checks/security/agent-instructions.d.ts +4 -0
  112. package/dist/esm/core/checks/security/agent-instructions.js +146 -0
  113. package/dist/esm/core/checks/security/diff-security.d.ts +7 -0
  114. package/dist/esm/core/checks/security/diff-security.js +21 -1
  115. package/dist/esm/core/checks/security/security.d.ts +0 -13
  116. package/dist/esm/core/checks/security/security.js +28 -1
  117. package/dist/esm/core/checks/security/workflow-security.js +33 -0
  118. package/dist/esm/core/commands.d.ts +6 -0
  119. package/dist/esm/core/commands.js +10 -0
  120. package/dist/esm/core/finding-store.d.ts +16 -0
  121. package/dist/esm/core/finding-store.js +67 -0
  122. package/dist/esm/core/install/doctor.js +12 -0
  123. package/dist/esm/core/install/grype-db.d.ts +25 -0
  124. package/dist/esm/core/install/grype-db.js +41 -0
  125. package/dist/esm/core/install/prepare.js +1 -1
  126. package/dist/esm/core/install/profiles.d.ts +1 -1
  127. package/dist/esm/core/install/profiles.js +1 -1
  128. package/dist/esm/core/install/reuse.js +6 -3
  129. package/dist/esm/core/registry.js +2 -0
  130. package/dist/esm/core/reporters.js +9 -0
  131. package/dist/esm/core/run-checks.js +17 -7
  132. package/dist/esm/core/sarif.d.ts +13 -0
  133. package/dist/esm/core/sarif.js +57 -0
  134. package/dist/esm/core/types.d.ts +7 -2
  135. package/dist/esm/index.d.ts +2 -1
  136. package/dist/esm/index.js +2 -0
  137. package/dist/esm/init.d.ts +0 -8
  138. package/dist/esm/init.js +15 -4
  139. package/dist/esm/interfaces/index.d.ts +12 -0
  140. package/dist/esm/mcp/config-tools.d.ts +30 -0
  141. package/dist/esm/mcp/config-tools.js +310 -0
  142. package/dist/esm/mcp/dependency.d.ts +33 -0
  143. package/dist/esm/mcp/dependency.js +402 -0
  144. package/dist/esm/mcp/protocol.d.ts +97 -0
  145. package/dist/esm/mcp/protocol.js +175 -0
  146. package/dist/esm/mcp/review.d.ts +63 -0
  147. package/dist/esm/mcp/review.js +139 -0
  148. package/dist/esm/mcp/serve.d.ts +8 -0
  149. package/dist/esm/mcp/serve.js +43 -0
  150. package/dist/esm/mcp/settings.d.ts +13 -0
  151. package/dist/esm/mcp/settings.js +43 -0
  152. package/dist/esm/mcp/tools.d.ts +23 -0
  153. package/dist/esm/mcp/tools.js +626 -0
  154. package/dist/esm/utils/execa.d.ts +7 -0
  155. package/dist/esm/utils/execa.js +40 -1
  156. package/dist/esm/utils/own-deps.js +8 -1
  157. package/dist/esm/utils/resolve-bin.d.ts +2 -0
  158. package/dist/esm/utils/resolve-bin.js +4 -0
  159. package/dist/esm/utils/result-collector.d.ts +17 -1
  160. package/dist/esm/utils/result-collector.js +7 -2
  161. package/eslint-default-rules.json +2 -8
  162. package/eslint.config.mjs +37 -8
  163. package/install-manifests/commitlint/package-lock.json +5 -4
  164. package/install-manifests/core/package-lock.json +5 -3
  165. package/install-manifests/js/package-lock.json +250 -571
  166. package/install-manifests/js/package.json +10 -8
  167. package/install-manifests/markdown/package-lock.json +16 -13
  168. package/install-manifests/markdown/package.json +3 -0
  169. package/install-manifests/spellcheck/package-lock.json +3 -3
  170. package/package.json +24 -16
  171. package/pr-checkmate.schema.json +35 -0
  172. package/src/config/.eslintrc.js +3 -9
  173. package/src/config/eslint-default-rules.json +2 -8
  174. package/src/config/pr-checkmate.schema.json +35 -0
package/dist/cjs/cli.js CHANGED
@@ -1,11 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const hook_js_1 = require("./agent/hook.js");
10
+ const setup_js_1 = require("./agent/setup.js");
11
+ const loader_js_1 = require("./config/loader.js");
4
12
  const commands_js_1 = require("./core/commands.js");
5
13
  const index_js_1 = require("./core/index.js");
6
14
  const doctor_js_1 = require("./core/install/doctor.js");
15
+ const grype_db_js_1 = require("./core/install/grype-db.js");
16
+ const sarif_js_1 = require("./core/sarif.js");
7
17
  const init_js_1 = require("./init.js");
18
+ const serve_js_1 = require("./mcp/serve.js");
19
+ const tools_js_1 = require("./mcp/tools.js");
20
+ const execa_js_1 = require("./utils/execa.js");
8
21
  const index_js_2 = require("./utils/index.js");
22
+ const package_version_js_1 = require("./utils/package-version.js");
9
23
  const argv = process.argv.slice(2);
10
24
  /**
11
25
  * Flags are separated from positional arguments so they compose with every command
@@ -52,6 +66,28 @@ function rejectConflictingScope() {
52
66
  index_js_2.logger.error('[cli]: "--all" applies only to "install". Did you mean "--full"?');
53
67
  process.exit(1);
54
68
  }
69
+ // `mcp` speaks a protocol on stdout and `hook` is invoked by a harness with a
70
+ // fixed payload: neither has anything to configure from the command line, and a
71
+ // flag that quietly did nothing would look like it had.
72
+ if ((cmd === 'mcp' || cmd === 'hook') && flags.length > 0) {
73
+ index_js_2.logger.error(`[cli]: "${cmd}" takes no flags, got: ${flags.join(', ')}.`);
74
+ process.exit(1);
75
+ }
76
+ // Only a command that runs checks has findings to export; `all`, `precommit` and a
77
+ // named check do, every other standalone command does not.
78
+ const runsChecks = !cmd || cmd === 'all' || cmd === 'precommit' || !commands_js_1.STANDALONE_COMMANDS.includes(cmd);
79
+ if (flags.includes('--sarif') && !runsChecks) {
80
+ index_js_2.logger.error('[cli]: "--sarif" applies only to commands that run checks.');
81
+ process.exit(1);
82
+ }
83
+ if (flags.includes('--no-hook') && cmd !== 'agent-setup') {
84
+ index_js_2.logger.error('[cli]: "--no-hook" applies only to "agent-setup".');
85
+ process.exit(1);
86
+ }
87
+ if (flags.includes('--full') && commands_js_1.AGENT_COMMANDS.includes(cmd ?? '')) {
88
+ index_js_2.logger.error(`[cli]: "${cmd}" reviews nothing on its own, so "--full" has no meaning.`);
89
+ process.exit(1);
90
+ }
55
91
  if (flags.includes('--full') && commands_js_1.TOOLING_COMMANDS.includes(cmd ?? '')) {
56
92
  index_js_2.logger.error(`[cli]: "${cmd}" manages tooling and reviews nothing, so "--full" has no meaning.`);
57
93
  process.exit(1);
@@ -73,6 +109,55 @@ function rejectExtraArgs() {
73
109
  index_js_2.logger.error(`[cli]: "${cmd}" takes no other arguments, got: ${args.slice(1).join(', ')}. Run them as a separate command.`);
74
110
  process.exit(1);
75
111
  }
112
+ /**
113
+ * Write the report as SARIF when `--sarif` was passed. Written before the exit code
114
+ * is decided, so a failing run still leaves the file a dashboard needs to show why.
115
+ */
116
+ function writeSarifIfRequested(report) {
117
+ if (!flags.includes('--sarif'))
118
+ return;
119
+ const file = node_path_1.default.resolve(process.cwd(), 'pr-checkmate.sarif');
120
+ node_fs_1.default.writeFileSync(file, `${JSON.stringify((0, sarif_js_1.toSarif)(report, (0, package_version_js_1.packageVersion)()), null, 2)}\n`);
121
+ index_js_2.logger.info(`[sarif]: wrote ${file}`);
122
+ }
123
+ /** Everything the harness wrote to stdin, as text; empty when stdin is closed or a terminal. */
124
+ async function readStdin() {
125
+ if (process.stdin.isTTY)
126
+ return '';
127
+ const chunks = [];
128
+ for await (const chunk of process.stdin)
129
+ chunks.push(Buffer.from(chunk));
130
+ return Buffer.concat(chunks).toString('utf-8');
131
+ }
132
+ /**
133
+ * The commands that serve AI agents. Returns whether `cmd` was one of them (and has
134
+ * therefore already exited); kept out of the main dispatcher so that one stays
135
+ * readable as the list of commands grows.
136
+ *
137
+ * `mcp` must stay ahead of anything that could print: from here until stdin closes,
138
+ * stdout carries protocol messages and nothing else.
139
+ */
140
+ async function runAgentCommand() {
141
+ if (cmd === 'mcp') {
142
+ await (0, serve_js_1.serve)((0, tools_js_1.createServerDefinition)());
143
+ process.exit(0);
144
+ }
145
+ if (cmd === 'hook') {
146
+ // Discarded rather than shown: the harness reads stdout and stderr of a hook
147
+ // as instructions to the model, so only the verdict may reach either.
148
+ (0, execa_js_1.isolateInheritedStdio)('discard');
149
+ index_js_2.logger.setSink({ out: () => undefined, err: () => undefined });
150
+ const verdict = await (0, hook_js_1.evaluateHook)(await readStdin(), process.cwd());
151
+ if (verdict.stderr)
152
+ process.stderr.write(`${verdict.stderr}\n`);
153
+ process.exit(verdict.exitCode);
154
+ }
155
+ if (cmd === 'agent-setup') {
156
+ (0, setup_js_1.reportSetup)((0, setup_js_1.setupAgent)(process.cwd(), { hook: !flags.includes('--no-hook') }));
157
+ process.exit(0);
158
+ }
159
+ return false;
160
+ }
76
161
  void (async () => {
77
162
  try {
78
163
  rejectUnknownFlags();
@@ -82,6 +167,8 @@ void (async () => {
82
167
  await (0, init_js_1.init)();
83
168
  process.exit(0);
84
169
  }
170
+ if (await runAgentCommand())
171
+ return;
85
172
  // Tooling management. These run no checks — they prepare, inspect or clean
86
173
  // up what the checks need, so they never touch the diff or the reporter.
87
174
  if (cmd === 'install') {
@@ -90,6 +177,17 @@ void (async () => {
90
177
  for (const result of failed) {
91
178
  index_js_2.logger.error(`[install]: ❌ ${result.profiles.join(', ') || 'tooling'}: ${result.reason}`);
92
179
  }
180
+ // Warm grype's vulnerability database too, with no time cap: the download is
181
+ // hundreds of MB and the scan itself would only be allowed a couple of minutes.
182
+ // Best-effort — an absent grype or a failed download is a warning, not a
183
+ // failed install, since Grype Scan skips cleanly without it.
184
+ if ((0, loader_js_1.loadProjectConfig)().grypeScan?.enabled !== false) {
185
+ const why = await (0, grype_db_js_1.ensureGrypeDb)(process.cwd());
186
+ if (why && why !== 'ENOENT')
187
+ index_js_2.logger.warn(`[install]: ⚠️ grype: ${why}`);
188
+ else if (!why)
189
+ index_js_2.logger.info('[install]: grype vulnerability database is up to date');
190
+ }
93
191
  process.exit(failed.length > 0 ? 1 : 0);
94
192
  }
95
193
  if (cmd === 'doctor') {
@@ -112,12 +210,14 @@ void (async () => {
112
210
  index_js_2.logger.info('');
113
211
  notice.forEach(line => index_js_2.logger.info(line));
114
212
  }
213
+ writeSarifIfRequested(report);
115
214
  process.exit(report.ok ? 0 : 1);
116
215
  }
117
216
  // Local pre-commit: check the staged changes (index vs HEAD), print to
118
217
  // stdout, never modify files. Exits non-zero on failure to block the commit.
119
218
  if (cmd === 'precommit') {
120
219
  const report = await (0, index_js_1.runChecks)({ staged: true, reporter: 'stdout', write: false });
220
+ writeSarifIfRequested(report);
121
221
  process.exit(report.ok ? 0 : 1);
122
222
  }
123
223
  // Every remaining argument is a check name, so `pr-checkmate lint spellcheck`
@@ -130,6 +230,7 @@ void (async () => {
130
230
  }
131
231
  const checkNames = [...new Set(args.map(arg => commands_js_1.COMMAND_TO_CHECK[arg]))];
132
232
  const report = await (0, index_js_1.runChecks)({ only: checkNames, reporter: 'stdout', write: true, full });
233
+ writeSarifIfRequested(report);
133
234
  process.exit(report.ok ? 0 : 1);
134
235
  }
135
236
  catch (err) {
@@ -95,6 +95,7 @@ export declare const SOURCE_EXTENSIONS: {
95
95
  json: string[];
96
96
  all: string[];
97
97
  };
98
+ export declare const AGENT_SETUP_COMMAND = "npx pr-checkmate agent-setup";
98
99
  export declare const DEFAULT_COMMANDS: {
99
100
  'npx pr-checkmate init': string;
100
101
  'npx pr-checkmate all': string;
@@ -109,6 +110,7 @@ export declare const DEFAULT_COMMANDS: {
109
110
  'npx pr-checkmate diff-smell': string;
110
111
  'npx pr-checkmate pr-body': string;
111
112
  'npx pr-checkmate duplicate': string;
113
+ "npx pr-checkmate agent-setup": string;
112
114
  };
113
115
  /**
114
116
  * Packages allow-listed for the license check by default.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
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;
3
+ exports.KNOWN_CONFIG_KEYS = exports.documentation = exports.FULL_CONFIG_URL = exports.DOCS_URL = exports.DEFAULT_ALLOW_PACKAGES = exports.DEFAULT_COMMANDS = exports.AGENT_SETUP_COMMAND = 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;
4
4
  /**
5
5
  * Build output, dependency, tooling, and report directories that are never
6
6
  * product code. Every check excludes them by directory-name segment match (see
@@ -228,6 +228,7 @@ exports.SOURCE_EXTENSIONS = {
228
228
  json: ['.json'],
229
229
  all: ['.ts', '.tsx', '.js', '.jsx', '.json', '.md', '.mdx'],
230
230
  };
231
+ exports.AGENT_SETUP_COMMAND = 'npx pr-checkmate agent-setup';
231
232
  exports.DEFAULT_COMMANDS = {
232
233
  'npx pr-checkmate init': 'Create pr-checkmate.json and the CI workflow (first-run setup)',
233
234
  'npx pr-checkmate all': 'Run all checks over the changes — the PR diff in CI, your branch changes locally',
@@ -242,6 +243,7 @@ exports.DEFAULT_COMMANDS = {
242
243
  'npx pr-checkmate diff-smell': 'Scan git diff for code smells',
243
244
  'npx pr-checkmate pr-body': 'Validate PR description',
244
245
  'npx pr-checkmate duplicate': 'Detect duplicate code blocks (jscpd)',
246
+ [exports.AGENT_SETUP_COMMAND]: 'Connect your AI agent: MCP server, rules for it and edit hooks (writes .mcp.json)',
245
247
  };
246
248
  /**
247
249
  * Packages allow-listed for the license check by default.
@@ -323,6 +325,8 @@ exports.KNOWN_CONFIG_KEYS = new Set([
323
325
  'diffSecurity',
324
326
  'workflowSecurity',
325
327
  'dockerfileSecurity',
328
+ 'agentInstructions',
329
+ 'mcp',
326
330
  'sensitiveFileGuard',
327
331
  'migrationSafety',
328
332
  // dependencies
@@ -97,6 +97,16 @@
97
97
  "enabled": true,
98
98
  "ignore": []
99
99
  },
100
+ "agentInstructions": {
101
+ "enabled": true,
102
+ "ignore": []
103
+ },
104
+ "//mcp": "What the MCP server offers an AI agent here. 'enabled': false turns it off; 'disabledTools' hides tools by name; 'readOnly' hides the ones that write files. Applies from the agent's next request.",
105
+ "mcp": {
106
+ "enabled": true,
107
+ "disabledTools": [],
108
+ "readOnly": false
109
+ },
100
110
  "//sensitiveFileGuard": "Flags .env files, private keys, CI config and lockfiles. 'ignorePaths' excuses ONE file by glob (a .pem test fixture); 'ignore' silences a whole CATEGORY and would excuse the next real key too — reach for ignorePaths first.",
101
111
  "sensitiveFileGuard": {
102
112
  "enabled": true,
@@ -318,7 +328,7 @@
318
328
  "//ignores": "APPENDED to the built-in list: node_modules, dist, coverage, graphify-out, .claude and CHANGELOG.md.",
319
329
  "ignores": []
320
330
  },
321
- "//7b-install": "WHICH TOOLING IS FETCHED. The package ships ~233 MB and no repository needs all of it. \"adaptive\" (default) fetches only what this repository turns out to need; \"eager\" fetches everything up front, which is what a closed network or a prebuilt image wants and is also the exact previous behaviour; \"manual\" fetches nothing because the tools are already on the runner. A missing tool never fails silently: the affected checks skip with a reason in the report, or fail the run outright with onMissingTool: \"error\".",
331
+ "//7b-install": "WHICH TOOLING IS FETCHED. The tooling totals ~233 MB and no repository needs all of it. \"adaptive\" (default) fetches only what this repository turns out to need; \"eager\" fetches everything up front, which is what a closed network or a prebuilt image wants and is also the exact previous behaviour; \"manual\" fetches nothing because the tools are already on the runner. A missing tool never fails silently: the affected checks skip with a reason in the report, or fail the run outright with onMissingTool: \"error\".",
322
332
  "install": {
323
333
  "strategy": "adaptive",
324
334
  "//profiles": "Extra profiles beyond what detection concluded, for generated code or a monorepo layout detection cannot see through. Names: core, js, python, cpp, spellcheck, markdown, commitlint, duplicate.",
@@ -4,6 +4,7 @@ exports.grypeScanCheck = void 0;
4
4
  exports.resolveGrypeScan = resolveGrypeScan;
5
5
  const execa_js_1 = require("../../../utils/execa.js");
6
6
  const index_js_1 = require("../../../utils/index.js");
7
+ const grype_db_js_1 = require("../../install/grype-db.js");
7
8
  const types_js_1 = require("../../types.js");
8
9
  const tool_install_js_1 = require("../tool-install.js");
9
10
  const SEVERITY_ORDER = ['negligible', 'low', 'medium', 'high', 'critical'];
@@ -21,6 +22,14 @@ const TYPE_TO_ECOSYSTEM = {
21
22
  * looks like a hung commit — an advisory check must never be able to do that.
22
23
  */
23
24
  const DEFAULT_TIMEOUT_MS = 120000;
25
+ /**
26
+ * Cap for refreshing grype's vulnerability database. A stale database (grype
27
+ * refuses one older than 5 days) is re-downloaded, hundreds of MB, which takes
28
+ * longer than the scan's own cap. Kept below the per-check timeout so the check
29
+ * itself reports the outcome instead of being cut off by the runner. `npx
30
+ * pr-checkmate install` refreshes it without any cap, ahead of time.
31
+ */
32
+ const DB_UPDATE_TIMEOUT_MS = 240000;
24
33
  function severityRank(severity) {
25
34
  return SEVERITY_ORDER.indexOf((severity ?? '').toLowerCase());
26
35
  }
@@ -76,10 +85,19 @@ exports.grypeScanCheck = {
76
85
  // third-party code is not something the client could fix anyway (see below).
77
86
  const excludes = (ctx.config.ignoreDirs ?? []).flatMap(dir => ['--exclude', `**/${dir}/**`]);
78
87
  const timeout = settings.timeoutMs ?? DEFAULT_TIMEOUT_MS;
88
+ const unusable = await (0, grype_db_js_1.ensureGrypeDb)(ctx.cwd, DB_UPDATE_TIMEOUT_MS);
89
+ if (unusable === 'ENOENT')
90
+ return (0, tool_install_js_1.notInstalled)('grype');
91
+ if (unusable) {
92
+ index_js_1.logger.warn(`[Grype Scan]: ⚠️ ${unusable} — skipping`);
93
+ return (0, types_js_1.skip)(unusable);
94
+ }
95
+ // The database was just checked, so the scan must not try to fetch one itself.
79
96
  const result = await (0, execa_js_1.execa)('grype', ['dir:.', '-o', 'json', '-q', ...excludes], {
80
97
  cwd: ctx.cwd,
81
98
  reject: false,
82
99
  timeout,
100
+ env: { GRYPE_DB_AUTO_UPDATE: 'false' },
83
101
  });
84
102
  // execa v9 with reject:false resolves (not throws) with `code: 'ENOENT'`
85
103
  // when the binary is absent — detect the missing scanner on the result.
@@ -23,6 +23,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
23
23
  const node_path_1 = __importDefault(require("node:path"));
24
24
  const execa_js_1 = require("../../utils/execa.js");
25
25
  const index_js_1 = require("../../utils/index.js");
26
+ const finding_store_js_1 = require("../finding-store.js");
26
27
  const targets_js_1 = require("../targets.js");
27
28
  const types_js_1 = require("../types.js");
28
29
  /**
@@ -391,6 +392,8 @@ exports.DEFAULT_DATAFLOW_WINDOW = 10;
391
392
  const CROSS_LINE_SQL_LABEL = 'Interpolated value reaches a query (SQL injection risk, cross-line)';
392
393
  /** `@@ ... @@` — a new hunk, and the boundary taint never crosses. */
393
394
  const DIFF_HUNK_HEADER = /^@@ /;
395
+ /** The first line of the new side of a hunk: `@@ -3,2 +10,4 @@` → 10. */
396
+ const HUNK_NEW_START = /^@@ -\d+(?:,\d+)? \+(\d+)/;
394
397
  /**
395
398
  * Whether this line hands `name` to a query sink.
396
399
  *
@@ -464,6 +467,9 @@ function scanDiffPatterns(stdout, patterns, opts = {}) {
464
467
  const mode = opts.comments ?? 'language';
465
468
  const ignorePaths = opts.ignorePaths ?? [];
466
469
  let syntax = 'both';
470
+ // Where the walk is, for findings that need to say so.
471
+ let file = '';
472
+ let newLine = 0;
467
473
  // Block comments span lines, so the state belongs to the walk, not to one line.
468
474
  let inBlockComment = false;
469
475
  // Whole files can be excluded by glob; the flag persists until the next header.
@@ -474,6 +480,7 @@ function scanDiffPatterns(stdout, patterns, opts = {}) {
474
480
  for (const line of stdout.split('\n')) {
475
481
  const header = DIFF_FILE_HEADER.exec(line);
476
482
  if (header) {
483
+ file = header[1];
477
484
  syntax = mode === 'language' ? commentSyntaxFor(header[1]) : 'both';
478
485
  inBlockComment = false;
479
486
  skippingFile = ignorePaths.length > 0 && (0, index_js_1.matchesAnyGlob)(header[1], ignorePaths);
@@ -482,9 +489,15 @@ function scanDiffPatterns(stdout, patterns, opts = {}) {
482
489
  }
483
490
  // Checked before the `+` filter below, which a hunk header does not satisfy.
484
491
  if (DIFF_HUNK_HEADER.test(line)) {
492
+ newLine = Number(HUNK_NEW_START.exec(line)?.[1] ?? 0);
485
493
  taint.reset();
486
494
  continue;
487
495
  }
496
+ // Counted before any skip: an ignored or excluded line still occupies a line
497
+ // number in the file, and the ones after it must not shift.
498
+ const lineNo = newLine;
499
+ if (line.startsWith('+'))
500
+ newLine++;
488
501
  if (skippingFile)
489
502
  continue;
490
503
  // Only added lines carry block-comment state: a `-` line is code being
@@ -496,18 +509,28 @@ function scanDiffPatterns(stdout, patterns, opts = {}) {
496
509
  const prepared = uncomment(line, { mode, syntax, inBlockComment });
497
510
  inBlockComment = prepared.inBlockComment;
498
511
  const asCode = stripStringLiterals(prepared.text);
499
- for (const { pattern, label, severity, view } of patterns) {
500
- if (pattern.test(view === 'code' ? asCode : prepared.text)) {
501
- const bucket = severity === 'error' ? errors : warnings;
502
- (bucket[label] ?? (bucket[label] = [])).push(line.slice(1).trim().substring(0, SAMPLE_MAX_LENGTH));
503
- }
504
- }
512
+ const where = { file, line: lineNo };
513
+ recordMatches(patterns, { text: prepared.text, asCode }, line, where, { errors, warnings });
505
514
  if (taint.inspect(prepared.text, asCode)) {
506
- (warnings[CROSS_LINE_SQL_LABEL] ?? (warnings[CROSS_LINE_SQL_LABEL] = [])).push(line.slice(1).trim().substring(0, SAMPLE_MAX_LENGTH));
515
+ record(warnings, CROSS_LINE_SQL_LABEL, line, where);
507
516
  }
508
517
  }
509
518
  return { errors, warnings };
510
519
  }
520
+ /** File one added line under `label` and remember where it was, for findings that say so. */
521
+ function record(bucket, label, line, where) {
522
+ const sample = line.slice(1).trim().substring(0, SAMPLE_MAX_LENGTH);
523
+ (bucket[label] ?? (bucket[label] = [])).push(sample);
524
+ (0, finding_store_js_1.noteLocation)(label, sample, where.file, where.line);
525
+ }
526
+ /** File every pattern that matches one prepared line under its own label and severity. */
527
+ function recordMatches(patterns, view, line, where, into) {
528
+ for (const { pattern, label, severity, view: wants } of patterns) {
529
+ if (pattern.test(wants === 'code' ? view.asCode : view.text)) {
530
+ record(severity === 'error' ? into.errors : into.warnings, label, line, where);
531
+ }
532
+ }
533
+ }
511
534
  /**
512
535
  * Reduce one added line to what should be matched against: block comments
513
536
  * blanked (carrying the open state on), then the trailing line comment removed.
@@ -598,6 +621,7 @@ function bucketTotal(bucket) {
598
621
  */
599
622
  function reportCountedFindings(findings, ctx, opts) {
600
623
  const sev = severityEmitter(opts.severity, opts.fallback ?? 'warn');
624
+ (0, finding_store_js_1.fileFindings)(findings, sev.icon === '❌' ? 'error' : 'warn');
601
625
  logFindings(findings, {
602
626
  prefix: opts.prefix,
603
627
  sampleLimit: opts.sampleLimit ?? ctx.config.log?.sampleLimit ?? 3,
@@ -702,6 +726,8 @@ function reportSeverityBuckets(errorsIn, warningsIn, opts) {
702
726
  const ignore = opts.ignore ?? [];
703
727
  const filed = applyOverrides(dropIgnored(errorsIn, ignore), dropIgnored(warningsIn, ignore), opts.severityOverrides ?? {});
704
728
  const { errors, warnings } = filed;
729
+ (0, finding_store_js_1.fileFindings)(errors, 'error');
730
+ (0, finding_store_js_1.fileFindings)(warnings, 'warn');
705
731
  const totalErrors = bucketTotal(errors);
706
732
  const totalWarnings = bucketTotal(warnings);
707
733
  if (totalErrors === 0 && totalWarnings === 0)
@@ -1,2 +1,3 @@
1
1
  import { Check } from '../../types.js';
2
+ export declare const DEFAULT_MAX_KB = 500;
2
3
  export declare const largeFilesCheck: Check;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.largeFilesCheck = void 0;
6
+ exports.largeFilesCheck = exports.DEFAULT_MAX_KB = void 0;
7
7
  const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const execa_js_1 = require("../../../utils/execa.js");
@@ -11,7 +11,7 @@ const index_js_1 = require("../../../utils/index.js");
11
11
  const targets_js_1 = require("../../targets.js");
12
12
  const types_js_1 = require("../../types.js");
13
13
  const findings_js_1 = require("../findings.js");
14
- const DEFAULT_MAX_KB = 500;
14
+ exports.DEFAULT_MAX_KB = 500;
15
15
  /**
16
16
  * Lockfiles are legitimately large and always generated, never accidental — so
17
17
  * they're exempt from the size/binary guard by default (a 600 KB
@@ -70,7 +70,7 @@ exports.largeFilesCheck = {
70
70
  return (0, types_js_1.skip)('git unavailable');
71
71
  if (files.length === 0)
72
72
  return (0, types_js_1.pass)();
73
- const maxKb = ctx.config.largeFiles?.maxSizeKb ?? DEFAULT_MAX_KB;
73
+ const maxKb = ctx.config.largeFiles?.maxSizeKb ?? exports.DEFAULT_MAX_KB;
74
74
  const maxBytes = maxKb * 1024;
75
75
  const allow = [...LOCKFILE_ALLOW, ...(ctx.config.largeFiles?.allow ?? [])];
76
76
  if (!ctx.baseSha && ctx.config.run?.fullScanBehaviour === 'skip')
@@ -1,6 +1,10 @@
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.lintCheck = void 0;
7
+ const node_path_1 = __importDefault(require("node:path"));
4
8
  const index_js_1 = require("../../../config/index.js");
5
9
  const execa_js_1 = require("../../../utils/execa.js");
6
10
  const index_js_2 = require("../../../utils/index.js");
@@ -76,10 +80,18 @@ exports.lintCheck = {
76
80
  index_js_2.logger.info(`[ESLint]: Using pr-checkmate config (ESLint v${version}, ${configFile})`);
77
81
  }
78
82
  index_js_1.IGNORE_PATTERNS.forEach(p => args.push('--ignore-pattern', p));
83
+ // Our bundled config loads its plugins through these roots, and an ESLint
84
+ // older than 9 resolves legacy-config plugins relative to the path below — in
85
+ // both cases the plugins are in a fetched profile rather than beside us.
86
+ const toolRoots = (0, index_js_2.registeredToolPaths)();
87
+ if (!hasLocal && version < 9 && toolRoots.length > 0) {
88
+ args.push('--resolve-plugins-relative-to', toolRoots[0]);
89
+ }
79
90
  const result = await (0, execa_js_1.execa)(process.execPath, [eslintBin, ...args], {
80
91
  cwd: ctx.cwd,
81
92
  stdio: 'inherit',
82
93
  reject: false,
94
+ env: { PR_CHECKMATE_TOOL_ROOTS: toolRoots.join(node_path_1.default.delimiter) },
83
95
  });
84
96
  if (result.exitCode === 0)
85
97
  return (0, types_js_1.pass)();
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PY_GLOBS = void 0;
4
4
  exports.createRuffWorkspace = createRuffWorkspace;
5
+ const index_js_1 = require("../../../utils/index.js");
5
6
  exports.PY_GLOBS = ['*.py'];
6
7
  /**
7
8
  * The rules we lint Python with — pinned here rather than left to Ruff's defaults.
@@ -28,7 +29,11 @@ const SELECTED_RULES = ['E4', 'E7', 'E9', 'F'];
28
29
  * singleton) so the WASM module loads only when a check runs.
29
30
  */
30
31
  function createRuffWorkspace() {
31
- const { Workspace, PositionEncoding } = require('@astral-sh/ruff-wasm-nodejs');
32
+ // Resolved rather than required by name: the package lives in the `python`
33
+ // profile, which is fetched into the cache directory when this repository
34
+ // needs it, and a bare specifier only searches our own tree.
35
+ const entry = (0, index_js_1.resolvePackageBin)('@astral-sh/ruff-wasm-nodejs', 'ruff_wasm.js');
36
+ const { Workspace, PositionEncoding } = require(entry);
32
37
  const settings = {
33
38
  ...Workspace.defaultSettings(),
34
39
  lint: { select: SELECTED_RULES },
@@ -40,8 +40,10 @@ const DEFAULT_IGNORES = ['^Release\\b', '^chore\\(release\\):'];
40
40
  function buildTempConfig(types, ignores) {
41
41
  // Emit a CJS module so `ignores` can hold functions — commitlint requires
42
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');
43
+ // Resolved through the same roots as the binary, not by bare specifier: both
44
+ // live in the `commitlint` profile, which is fetched into the cache directory,
45
+ // and `require.resolve` alone only searches our own tree.
46
+ const configConventional = (0, index_js_2.resolvePackageBin)('@commitlint/config-conventional', 'lib/index.js');
45
47
  const content = `const patterns = ${JSON.stringify(ignores)}.map((p) => new RegExp(p, 'i'));
46
48
  module.exports = {
47
49
  extends: [${JSON.stringify(configConventional)}],
@@ -1,2 +1,4 @@
1
1
  import { Check } from '../../types.js';
2
+ export declare const DEFAULT_MAX_FILES = 50;
3
+ export declare const DEFAULT_MAX_LINES = 1000;
2
4
  export declare const prSizeCheck: Check;
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.prSizeCheck = void 0;
3
+ exports.prSizeCheck = exports.DEFAULT_MAX_LINES = exports.DEFAULT_MAX_FILES = void 0;
4
4
  const execa_js_1 = require("../../../utils/execa.js");
5
5
  const index_js_1 = require("../../../utils/index.js");
6
6
  const types_js_1 = require("../../types.js");
7
- const DEFAULT_MAX_FILES = 50;
8
- const DEFAULT_MAX_LINES = 1000;
7
+ exports.DEFAULT_MAX_FILES = 50;
8
+ exports.DEFAULT_MAX_LINES = 1000;
9
9
  exports.prSizeCheck = {
10
10
  name: 'PR Size',
11
11
  phase: 'informational',
@@ -14,8 +14,8 @@ exports.prSizeCheck = {
14
14
  return ctx.config.prSize?.enabled !== false;
15
15
  },
16
16
  async run(ctx) {
17
- const maxFiles = ctx.config.prSize?.maxFiles ?? DEFAULT_MAX_FILES;
18
- const maxLines = ctx.config.prSize?.maxLines ?? DEFAULT_MAX_LINES;
17
+ const maxFiles = ctx.config.prSize?.maxFiles ?? exports.DEFAULT_MAX_FILES;
18
+ const maxLines = ctx.config.prSize?.maxLines ?? exports.DEFAULT_MAX_LINES;
19
19
  try {
20
20
  const { stdout } = await (0, execa_js_1.execa)('git', ['diff', '--shortstat', ctx.baseSha, ctx.headSha ?? 'HEAD'], {
21
21
  cwd: ctx.cwd,
@@ -1,6 +1,27 @@
1
1
  import { Check } from '../../types.js';
2
+ /** The slice of `npm pack --dry-run --json` this check reads. */
3
+ interface PackedFile {
4
+ path: string;
5
+ size: number;
6
+ }
7
+ interface PackManifest {
8
+ name: string;
9
+ version: string;
10
+ size: number;
11
+ unpackedSize: number;
12
+ entryCount: number;
13
+ files: PackedFile[];
14
+ }
15
+ /**
16
+ * The manifest out of whatever `npm pack --json` printed.
17
+ *
18
+ * npm 10 prints `[ {manifest} ]`; npm 11 prints `{ "<name>": {manifest} }`. Reading
19
+ * only the first form turned every npm 11 runner into "could not describe this
20
+ * package" and the check into a permanent skip.
21
+ */
22
+ export declare function pickManifest(parsed: unknown): PackManifest | null;
2
23
  /** Why we could not read a published version. */
3
- export type QueryFailure = 'absent' | 'unauthenticated' | 'unreadable';
24
+ type QueryFailure = 'absent' | 'unauthenticated' | 'unreadable';
4
25
  /**
5
26
  * Tell "never published" apart from "we were not let in".
6
27
  *
@@ -34,3 +55,4 @@ export declare function classifyQueryFailure(stderr: string): QueryFailure;
34
55
  * { "Package Size": "error" }` makes it a gate for a repository that wants one.
35
56
  */
36
57
  export declare const packageSizeCheck: Check;
58
+ export {};
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.packageSizeCheck = void 0;
7
+ exports.pickManifest = pickManifest;
7
8
  exports.classifyQueryFailure = classifyQueryFailure;
8
9
  const node_fs_1 = __importDefault(require("node:fs"));
9
10
  const node_path_1 = __importDefault(require("node:path"));
@@ -15,6 +16,22 @@ const findings_js_1 = require("../findings.js");
15
16
  const DEFAULT_MAX_GROWTH = 20;
16
17
  /** How many of the files behind the growth are named. */
17
18
  const NAMED_FILES = 5;
19
+ /**
20
+ * The manifest out of whatever `npm pack --json` printed.
21
+ *
22
+ * npm 10 prints `[ {manifest} ]`; npm 11 prints `{ "<name>": {manifest} }`. Reading
23
+ * only the first form turned every npm 11 runner into "could not describe this
24
+ * package" and the check into a permanent skip.
25
+ */
26
+ function pickManifest(parsed) {
27
+ const entries = Array.isArray(parsed)
28
+ ? parsed
29
+ : parsed !== null && typeof parsed === 'object'
30
+ ? Object.values(parsed)
31
+ : [];
32
+ const first = entries[0];
33
+ return first !== null && typeof first === 'object' ? first : null;
34
+ }
18
35
  /**
19
36
  * Ask npm what a tarball would contain, without producing one.
20
37
  *
@@ -48,10 +65,9 @@ async function packManifest(cwd, spec, registry) {
48
65
  if (result.exitCode !== 0)
49
66
  return { failure: classifyQueryFailure(result.stderr ?? '') };
50
67
  try {
51
- const parsed = JSON.parse(result.stdout);
52
- if (Array.isArray(parsed) && parsed.length > 0) {
53
- return { manifest: parsed[0] };
54
- }
68
+ const manifest = pickManifest(JSON.parse(result.stdout));
69
+ if (manifest)
70
+ return { manifest };
55
71
  }
56
72
  catch {
57
73
  // Fall through: npm answered with something that is not a manifest.
@@ -204,12 +204,15 @@ function dropMissingLanguages(language, missing) {
204
204
  */
205
205
  function resolveFrom(cwd) {
206
206
  return id => {
207
- try {
208
- return require.resolve(id, { paths: [cwd] });
209
- }
210
- catch {
211
- return require.resolve(id);
207
+ // The client's copy first, then the fetched `spellcheck` profile (cspell's own
208
+ // dictionaries live there), then our tree.
209
+ for (const root of [cwd, ...(0, index_js_2.registeredToolPaths)()]) {
210
+ try {
211
+ return require.resolve(id, { paths: [root] });
212
+ }
213
+ catch { }
212
214
  }
215
+ return require.resolve(id);
213
216
  };
214
217
  }
215
218
  function createTempCspellConfig(cwd) {
@@ -0,0 +1,4 @@
1
+ import { Check } from '../../types.js';
2
+ import { DiffPattern } from '../findings.js';
3
+ export declare const AGENT_PATTERNS: DiffPattern[];
4
+ export declare const agentInstructionsCheck: Check;