pr-checkmate 3.3.0 → 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.
- package/LICENSE +8 -3
- package/README.md +53 -4
- package/dist/cjs/agent/hook.d.ts +37 -0
- package/dist/cjs/agent/hook.js +175 -0
- package/dist/cjs/agent/registration.d.ts +10 -0
- package/dist/cjs/agent/registration.js +28 -0
- package/dist/cjs/agent/setup.d.ts +20 -0
- package/dist/cjs/agent/setup.js +229 -0
- package/dist/cjs/cli.js +88 -0
- package/dist/cjs/config/constants.d.ts +2 -0
- package/dist/cjs/config/constants.js +5 -1
- package/dist/cjs/config/pr-checkmate.template.json +10 -0
- package/dist/cjs/core/checks/findings.js +33 -7
- package/dist/cjs/core/checks/git/large-files.d.ts +1 -0
- package/dist/cjs/core/checks/git/large-files.js +3 -3
- package/dist/cjs/core/checks/pr/pr-size-check.d.ts +2 -0
- package/dist/cjs/core/checks/pr/pr-size-check.js +5 -5
- package/dist/cjs/core/checks/security/agent-instructions.d.ts +4 -0
- package/dist/cjs/core/checks/security/agent-instructions.js +149 -0
- package/dist/cjs/core/checks/security/diff-security.d.ts +7 -0
- package/dist/cjs/core/checks/security/diff-security.js +21 -1
- package/dist/cjs/core/checks/security/security.d.ts +0 -13
- package/dist/cjs/core/checks/security/security.js +27 -0
- package/dist/cjs/core/checks/security/workflow-security.js +33 -0
- package/dist/cjs/core/commands.d.ts +6 -0
- package/dist/cjs/core/commands.js +11 -1
- package/dist/cjs/core/finding-store.d.ts +16 -0
- package/dist/cjs/core/finding-store.js +73 -0
- package/dist/cjs/core/registry.js +2 -0
- package/dist/cjs/core/reporters.js +9 -0
- package/dist/cjs/core/run-checks.js +15 -5
- package/dist/cjs/core/sarif.d.ts +13 -0
- package/dist/cjs/core/sarif.js +60 -0
- package/dist/cjs/core/types.d.ts +7 -2
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/init.js +11 -0
- package/dist/cjs/interfaces/index.d.ts +12 -0
- package/dist/cjs/mcp/config-tools.d.ts +30 -0
- package/dist/cjs/mcp/config-tools.js +320 -0
- package/dist/cjs/mcp/dependency.d.ts +33 -0
- package/dist/cjs/mcp/dependency.js +407 -0
- package/dist/cjs/mcp/protocol.d.ts +97 -0
- package/dist/cjs/mcp/protocol.js +181 -0
- package/dist/cjs/mcp/review.d.ts +63 -0
- package/dist/cjs/mcp/review.js +150 -0
- package/dist/cjs/mcp/serve.d.ts +8 -0
- package/dist/cjs/mcp/serve.js +49 -0
- package/dist/cjs/mcp/settings.d.ts +13 -0
- package/dist/cjs/mcp/settings.js +50 -0
- package/dist/cjs/mcp/tools.d.ts +23 -0
- package/dist/cjs/mcp/tools.js +633 -0
- package/dist/cjs/utils/execa.d.ts +7 -0
- package/dist/cjs/utils/execa.js +41 -1
- package/dist/cjs/utils/result-collector.d.ts +17 -1
- package/dist/cjs/utils/result-collector.js +7 -2
- package/dist/esm/agent/hook.d.ts +37 -0
- package/dist/esm/agent/hook.js +167 -0
- package/dist/esm/agent/registration.d.ts +10 -0
- package/dist/esm/agent/registration.js +21 -0
- package/dist/esm/agent/setup.d.ts +20 -0
- package/dist/esm/agent/setup.js +220 -0
- package/dist/esm/cli.js +86 -1
- package/dist/esm/config/constants.d.ts +2 -0
- package/dist/esm/config/constants.js +4 -0
- package/dist/esm/config/pr-checkmate.template.json +10 -0
- package/dist/esm/core/checks/findings.js +33 -7
- package/dist/esm/core/checks/git/large-files.d.ts +1 -0
- package/dist/esm/core/checks/git/large-files.js +1 -1
- package/dist/esm/core/checks/pr/pr-size-check.d.ts +2 -0
- package/dist/esm/core/checks/pr/pr-size-check.js +2 -2
- package/dist/esm/core/checks/security/agent-instructions.d.ts +4 -0
- package/dist/esm/core/checks/security/agent-instructions.js +146 -0
- package/dist/esm/core/checks/security/diff-security.d.ts +7 -0
- package/dist/esm/core/checks/security/diff-security.js +20 -0
- package/dist/esm/core/checks/security/security.d.ts +0 -13
- package/dist/esm/core/checks/security/security.js +27 -0
- package/dist/esm/core/checks/security/workflow-security.js +33 -0
- package/dist/esm/core/commands.d.ts +6 -0
- package/dist/esm/core/commands.js +10 -0
- package/dist/esm/core/finding-store.d.ts +16 -0
- package/dist/esm/core/finding-store.js +67 -0
- package/dist/esm/core/registry.js +2 -0
- package/dist/esm/core/reporters.js +9 -0
- package/dist/esm/core/run-checks.js +15 -5
- package/dist/esm/core/sarif.d.ts +13 -0
- package/dist/esm/core/sarif.js +57 -0
- package/dist/esm/core/types.d.ts +7 -2
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/init.js +12 -1
- package/dist/esm/interfaces/index.d.ts +12 -0
- package/dist/esm/mcp/config-tools.d.ts +30 -0
- package/dist/esm/mcp/config-tools.js +310 -0
- package/dist/esm/mcp/dependency.d.ts +33 -0
- package/dist/esm/mcp/dependency.js +402 -0
- package/dist/esm/mcp/protocol.d.ts +97 -0
- package/dist/esm/mcp/protocol.js +175 -0
- package/dist/esm/mcp/review.d.ts +63 -0
- package/dist/esm/mcp/review.js +139 -0
- package/dist/esm/mcp/serve.d.ts +8 -0
- package/dist/esm/mcp/serve.js +43 -0
- package/dist/esm/mcp/settings.d.ts +13 -0
- package/dist/esm/mcp/settings.js +43 -0
- package/dist/esm/mcp/tools.d.ts +23 -0
- package/dist/esm/mcp/tools.js +626 -0
- package/dist/esm/utils/execa.d.ts +7 -0
- package/dist/esm/utils/execa.js +40 -1
- package/dist/esm/utils/result-collector.d.ts +17 -1
- package/dist/esm/utils/result-collector.js +7 -2
- package/package.json +6 -2
- package/pr-checkmate.schema.json +35 -0
- package/src/config/pr-checkmate.schema.json +35 -0
|
@@ -41,6 +41,7 @@ export const COMMAND_TO_CHECK = {
|
|
|
41
41
|
'license-header': 'License Header',
|
|
42
42
|
'config-validation': 'Config Validation',
|
|
43
43
|
'dockerfile-security': 'Dockerfile Security',
|
|
44
|
+
'agent-instructions': 'Agent Instructions',
|
|
44
45
|
'case-collision': 'Case Collision',
|
|
45
46
|
'vuln-scan': 'Vuln Scan',
|
|
46
47
|
'leftover-debug': 'Leftover Debug',
|
|
@@ -76,14 +77,23 @@ export const COMMAND_TO_CHECK = {
|
|
|
76
77
|
* and not a reading of our source.
|
|
77
78
|
*/
|
|
78
79
|
export const TOOLING_COMMANDS = ['install', 'doctor', 'cache-prune'];
|
|
80
|
+
/**
|
|
81
|
+
* Commands that serve AI agents rather than review a diff. They share the CLI's
|
|
82
|
+
* entry point and nothing else: no scope flags apply, and `mcp` in particular owns
|
|
83
|
+
* stdout for the protocol, so none of them may be confused with a check name.
|
|
84
|
+
*/
|
|
85
|
+
export const AGENT_COMMANDS = ['mcp', 'hook', 'agent-setup'];
|
|
79
86
|
export const STANDALONE_COMMANDS = [
|
|
80
87
|
'init',
|
|
81
88
|
'all',
|
|
82
89
|
'precommit',
|
|
83
90
|
...TOOLING_COMMANDS,
|
|
91
|
+
...AGENT_COMMANDS,
|
|
84
92
|
];
|
|
85
93
|
/** Flags accepted anywhere on the command line, with what each one means. */
|
|
86
94
|
export const FLAGS = {
|
|
87
95
|
'--full': 'review every tracked file instead of the diff (the default outside CI)',
|
|
88
96
|
'--all': 'with "install": fetch every profile, not only the ones this repository needs',
|
|
97
|
+
'--sarif': 'write findings as SARIF 2.1.0 to pr-checkmate.sarif (check-running commands only)',
|
|
98
|
+
'--no-hook': 'with "agent-setup": skip the git-commit gate for Claude Code',
|
|
89
99
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Finding } from '../utils/result-collector.js';
|
|
2
|
+
/** Run `work` collecting every finding filed inside it. */
|
|
3
|
+
export declare function collectFindings<T>(work: () => Promise<T>): Promise<{
|
|
4
|
+
result: T;
|
|
5
|
+
findings: Finding[];
|
|
6
|
+
}>;
|
|
7
|
+
/** A scanner saw `sample` under `label` at `file:line`. Claimed later by {@link fileFindings}. */
|
|
8
|
+
export declare function noteLocation(label: string, sample: string, file: string, line: number): void;
|
|
9
|
+
/**
|
|
10
|
+
* Turn the findings that survived filtering into located ones. Each sample claims
|
|
11
|
+
* the oldest location noted for it, so the same line appearing twice resolves to
|
|
12
|
+
* two places rather than one place twice.
|
|
13
|
+
*/
|
|
14
|
+
export declare function fileFindings(bucket: Record<string, string[]>, severity: Finding['severity']): void;
|
|
15
|
+
/** File a finding directly, for scanners that already know exactly where it is. */
|
|
16
|
+
export declare function addFinding(finding: Finding): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a finding was, carried from the scanner that saw it to the report that
|
|
3
|
+
* names it.
|
|
4
|
+
*
|
|
5
|
+
* Scanners reduce what they find to strings in a bucket — `"debugger statement" →
|
|
6
|
+
* ["debugger;"]` — because the report only ever needed a count and a sample. An
|
|
7
|
+
* agent needs more: which file, which line. Rewriting forty checks to carry that
|
|
8
|
+
* would have been forty chances to disagree with each other, so the location is
|
|
9
|
+
* noted once, where the diff is walked, and attached once, where the buckets are
|
|
10
|
+
* filed.
|
|
11
|
+
*
|
|
12
|
+
* It is attached at filing time, not scan time, on purpose. `ignore` and
|
|
13
|
+
* `severityOverrides` run in between, and a finding the client has silenced must
|
|
14
|
+
* not reach an agent that would then dutifully "fix" it.
|
|
15
|
+
*
|
|
16
|
+
* The store is an `AsyncLocalStorage` scope per check. Checks of one phase run in
|
|
17
|
+
* parallel, so a module-level list would mix their findings; a scope cannot. Code
|
|
18
|
+
* running outside `collectFindings` (the library API, unit tests) finds no store
|
|
19
|
+
* and every call here does nothing.
|
|
20
|
+
*/
|
|
21
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
22
|
+
/** More than this and the list is noise to a reader and a cost to an agent. */
|
|
23
|
+
const MAX_FINDINGS_PER_CHECK = 200;
|
|
24
|
+
const scope = new AsyncLocalStorage();
|
|
25
|
+
const keyOf = (label, sample) => `${label}\u0000${sample}`;
|
|
26
|
+
/** Run `work` collecting every finding filed inside it. */
|
|
27
|
+
export async function collectFindings(work) {
|
|
28
|
+
const store = { found: [], pending: new Map() };
|
|
29
|
+
const result = await scope.run(store, work);
|
|
30
|
+
return { result, findings: store.found };
|
|
31
|
+
}
|
|
32
|
+
/** A scanner saw `sample` under `label` at `file:line`. Claimed later by {@link fileFindings}. */
|
|
33
|
+
export function noteLocation(label, sample, file, line) {
|
|
34
|
+
const store = scope.getStore();
|
|
35
|
+
if (!store)
|
|
36
|
+
return;
|
|
37
|
+
const key = keyOf(label, sample);
|
|
38
|
+
const queue = store.pending.get(key) ?? [];
|
|
39
|
+
queue.push({ file, line });
|
|
40
|
+
store.pending.set(key, queue);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Turn the findings that survived filtering into located ones. Each sample claims
|
|
44
|
+
* the oldest location noted for it, so the same line appearing twice resolves to
|
|
45
|
+
* two places rather than one place twice.
|
|
46
|
+
*/
|
|
47
|
+
export function fileFindings(bucket, severity) {
|
|
48
|
+
const store = scope.getStore();
|
|
49
|
+
if (!store)
|
|
50
|
+
return;
|
|
51
|
+
for (const [label, samples] of Object.entries(bucket)) {
|
|
52
|
+
for (const sample of samples) {
|
|
53
|
+
if (store.found.length >= MAX_FINDINGS_PER_CHECK)
|
|
54
|
+
return;
|
|
55
|
+
const where = store.pending.get(keyOf(label, sample))?.shift();
|
|
56
|
+
if (!where)
|
|
57
|
+
continue;
|
|
58
|
+
store.found.push({ ...where, rule: label, snippet: sample, severity });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** File a finding directly, for scanners that already know exactly where it is. */
|
|
63
|
+
export function addFinding(finding) {
|
|
64
|
+
const store = scope.getStore();
|
|
65
|
+
if (store && store.found.length < MAX_FINDINGS_PER_CHECK)
|
|
66
|
+
store.found.push(finding);
|
|
67
|
+
}
|
|
@@ -38,6 +38,7 @@ import { packageSizeCheck } from './checks/quality/package-size.js';
|
|
|
38
38
|
import { spellcheckCheck } from './checks/quality/spellcheck.js';
|
|
39
39
|
import { symlinksCheck } from './checks/quality/symlinks.js';
|
|
40
40
|
import { yamlLintCheck } from './checks/quality/yaml-lint.js';
|
|
41
|
+
import { agentInstructionsCheck } from './checks/security/agent-instructions.js';
|
|
41
42
|
import { diffSecurityCheck } from './checks/security/diff-security.js';
|
|
42
43
|
import { dockerfileSecurityCheck } from './checks/security/dockerfile-security.js';
|
|
43
44
|
import { migrationSafetyCheck } from './checks/security/migration-safety.js';
|
|
@@ -63,6 +64,7 @@ export const REGISTRY = [
|
|
|
63
64
|
diffSecurityCheck,
|
|
64
65
|
workflowSecurityCheck,
|
|
65
66
|
dockerfileSecurityCheck,
|
|
67
|
+
agentInstructionsCheck,
|
|
66
68
|
caseCollisionCheck,
|
|
67
69
|
vulnScanCheck,
|
|
68
70
|
grypeScanCheck,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { isMcpRegistered } from '../agent/registration.js';
|
|
3
|
+
import { AGENT_SETUP_COMMAND } from '../config/constants.js';
|
|
4
|
+
import { readMcpSettings } from '../mcp/settings.js';
|
|
2
5
|
import { paginate, request } from '../utils/http.js';
|
|
3
6
|
import { logger } from '../utils/index.js';
|
|
4
7
|
import { packageVersion } from '../utils/package-version.js';
|
|
@@ -100,6 +103,12 @@ function printStdout(report, cfg) {
|
|
|
100
103
|
if (cfg.include?.docsLink !== false) {
|
|
101
104
|
// The docs cover every check and config key.
|
|
102
105
|
logger.print(' DOCUMENTATION: https://pr-checkmate-docs.pages.dev/');
|
|
106
|
+
// Said once per project, and never in CI, where nobody is there to act on it.
|
|
107
|
+
if (!process.env.CI &&
|
|
108
|
+
readMcpSettings(process.cwd()).enabled &&
|
|
109
|
+
!isMcpRegistered(process.cwd())) {
|
|
110
|
+
logger.print(` AI AGENT: ${AGENT_SETUP_COMMAND} — your agent then writes code that passes.`);
|
|
111
|
+
}
|
|
103
112
|
}
|
|
104
113
|
}
|
|
105
114
|
/** Network settings shared by every platform call. */
|
|
@@ -4,6 +4,7 @@ import { resolveConcurrency, runPool, Semaphore, withTimeout } from '../utils/po
|
|
|
4
4
|
import { clearToolPaths } from '../utils/resolve-bin.js';
|
|
5
5
|
import { ResultCollector } from '../utils/result-collector.js';
|
|
6
6
|
import { buildContext } from './context.js';
|
|
7
|
+
import { collectFindings } from './finding-store.js';
|
|
7
8
|
import { noTooling, prepareTooling } from './install/prepare.js';
|
|
8
9
|
import { REGISTRY } from './registry.js';
|
|
9
10
|
import { report } from './reporters.js';
|
|
@@ -169,15 +170,24 @@ async function runOne(check, ctx, collector, heavy, tooling) {
|
|
|
169
170
|
}
|
|
170
171
|
const started = Date.now();
|
|
171
172
|
const timeoutMs = ctx.config.run?.checkTimeoutMs ?? 300000;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
// Locations noted while this check runs, collected per check: the phase runs
|
|
174
|
+
// its checks in parallel, so a shared list would mix their findings.
|
|
175
|
+
let located = [];
|
|
176
|
+
const execute = async () => {
|
|
177
|
+
const { result, findings } = await collectFindings(() => withTimeout(check.run(ctx), timeoutMs, () => {
|
|
178
|
+
logger.warn(`[runChecks]: ⏱️ ${check.name} exceeded ${timeoutMs}ms`);
|
|
179
|
+
return { status: 'warn', details: `timed out after ${timeoutMs}ms` };
|
|
180
|
+
}));
|
|
181
|
+
located = findings;
|
|
182
|
+
return result;
|
|
183
|
+
};
|
|
176
184
|
const outcome = check.weight === 'heavy' ? await heavy.run(execute) : await execute();
|
|
177
185
|
if (ctx.config.run?.timings) {
|
|
178
186
|
logger.info(`[runChecks]: ${check.name} took ${Date.now() - started}ms`);
|
|
179
187
|
}
|
|
180
|
-
collector.register(check.name, applySeverity(check.name, outcome.status, ctx), outcome.details
|
|
188
|
+
collector.register(check.name, applySeverity(check.name, outcome.status, ctx), outcome.details,
|
|
189
|
+
// A clean result has nothing to locate, even when `failOn: never` filed some.
|
|
190
|
+
outcome.status === 'pass' ? undefined : (outcome.findings ?? located));
|
|
181
191
|
}
|
|
182
192
|
catch (err) {
|
|
183
193
|
// An unexpected throw in a blocking check fails the run; in informational/format
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A report as SARIF 2.1.0 — the format GitHub code scanning, VS Code and most
|
|
3
|
+
* security dashboards read, so a finding shows up inline on the line it names
|
|
4
|
+
* instead of only in a summary table.
|
|
5
|
+
*
|
|
6
|
+
* Only what a consumer can use is written. A failed or warned check that located
|
|
7
|
+
* its problems yields one result per location; one that could not yields a single
|
|
8
|
+
* result with no location, because a finding with nowhere to point is still a
|
|
9
|
+
* finding. Passes and skips are not results.
|
|
10
|
+
*/
|
|
11
|
+
import type { CheckReport } from './types.js';
|
|
12
|
+
/** Convert a report to a SARIF log object, ready for `JSON.stringify`. */
|
|
13
|
+
export declare function toSarif(report: CheckReport, toolVersion: string): Record<string, unknown>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const SARIF_SCHEMA = 'https://json.schemastore.org/sarif-2.1.0.json';
|
|
2
|
+
const ruleIdFor = (check, rule) => (rule ? `${check}/${rule}` : check);
|
|
3
|
+
function locatedResult(check, f) {
|
|
4
|
+
return {
|
|
5
|
+
ruleId: ruleIdFor(check.name, f.rule),
|
|
6
|
+
level: f.severity === 'error' ? 'error' : 'warning',
|
|
7
|
+
message: { text: f.snippet ? `${f.rule}: ${f.snippet}` : f.rule },
|
|
8
|
+
locations: [
|
|
9
|
+
{
|
|
10
|
+
physicalLocation: {
|
|
11
|
+
artifactLocation: { uri: f.file },
|
|
12
|
+
...(f.line !== undefined && { region: { startLine: f.line } }),
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function summaryResult(check) {
|
|
19
|
+
return {
|
|
20
|
+
ruleId: ruleIdFor(check.name),
|
|
21
|
+
level: check.status === 'fail' ? 'error' : 'warning',
|
|
22
|
+
message: { text: check.details ?? `${check.name} reported a problem` },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Convert a report to a SARIF log object, ready for `JSON.stringify`. */
|
|
26
|
+
export function toSarif(report, toolVersion) {
|
|
27
|
+
const results = [];
|
|
28
|
+
const rules = new Map();
|
|
29
|
+
for (const check of report.results) {
|
|
30
|
+
if (check.status !== 'fail' && check.status !== 'warn')
|
|
31
|
+
continue;
|
|
32
|
+
const found = check.findings && check.findings.length > 0
|
|
33
|
+
? check.findings.map(f => locatedResult(check, f))
|
|
34
|
+
: [summaryResult(check)];
|
|
35
|
+
for (const r of found) {
|
|
36
|
+
results.push(r);
|
|
37
|
+
rules.set(r.ruleId, { id: r.ruleId, name: r.ruleId });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
$schema: SARIF_SCHEMA,
|
|
42
|
+
version: '2.1.0',
|
|
43
|
+
runs: [
|
|
44
|
+
{
|
|
45
|
+
tool: {
|
|
46
|
+
driver: {
|
|
47
|
+
name: 'pr-checkmate',
|
|
48
|
+
version: toolVersion,
|
|
49
|
+
informationUri: 'https://www.npmjs.com/package/pr-checkmate',
|
|
50
|
+
rules: [...rules.values()],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
results,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
}
|
package/dist/esm/core/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PRCheckMateConfig, ReporterConfig } from '../interfaces/index.js';
|
|
2
2
|
import type { Language, LanguageUsage } from '../utils/detect-language.js';
|
|
3
|
-
import type { CheckResult, CheckStatus } from '../utils/result-collector.js';
|
|
4
|
-
export type { CheckResult, CheckStatus, Language, LanguageUsage };
|
|
3
|
+
import type { CheckResult, CheckStatus, Finding } from '../utils/result-collector.js';
|
|
4
|
+
export type { CheckResult, CheckStatus, Finding, Language, LanguageUsage };
|
|
5
5
|
/**
|
|
6
6
|
* Execution phases, run strictly in order. Within a phase checks run in parallel.
|
|
7
7
|
* `format` runs last because formatters (prettier) may write files.
|
|
@@ -67,6 +67,11 @@ export interface CheckContext {
|
|
|
67
67
|
export interface CheckOutcome {
|
|
68
68
|
status: CheckStatus;
|
|
69
69
|
details?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Where the problems are. Left to the orchestrator for pattern-based checks,
|
|
72
|
+
* which note locations as they scan; a check that knows better may set it.
|
|
73
|
+
*/
|
|
74
|
+
findings?: Finding[];
|
|
70
75
|
}
|
|
71
76
|
/**
|
|
72
77
|
* A single quality gate. `applies` decides relevance (language/marker gating);
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* @module pr-checkmate
|
|
5
5
|
*/
|
|
6
6
|
export { runChecks, buildContext, REGISTRY } from './core/index.js';
|
|
7
|
+
export { toSarif } from './core/sarif.js';
|
|
7
8
|
export { pass, warn, fail, skip, defineCheck } from './core/index.js';
|
|
8
|
-
export type { Check, CheckContext, CheckOutcome, CheckReport, CheckResult, CheckStatus, Language, Phase, ReporterFn, ReporterKind, RunOptions, } from './core/index.js';
|
|
9
|
+
export type { Check, CheckContext, CheckOutcome, CheckReport, CheckResult, CheckStatus, Finding, Language, Phase, ReporterFn, ReporterKind, RunOptions, } from './core/index.js';
|
|
9
10
|
export { getSourcePath, getSourcePaths, getIgnoredDirs, getPackageConfigPath, hasLocalConfig, } from './config/index.js';
|
|
10
11
|
export type { PRCheckMateConfig } from './interfaces/index.js';
|
|
11
12
|
export { Logger } from './utils/logger.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// Primary programmatic API: run checks and receive a structured report.
|
|
7
7
|
// Never throws for violations, never calls process.exit — the caller decides.
|
|
8
8
|
export { runChecks, buildContext, REGISTRY } from './core/index.js';
|
|
9
|
+
// SARIF 2.1.0 export of a report, for code-scanning dashboards and editors.
|
|
10
|
+
export { toSarif } from './core/sarif.js';
|
|
9
11
|
// Authoring helpers for consumers writing their own checks (plugins).
|
|
10
12
|
export { pass, warn, fail, skip, defineCheck } from './core/index.js';
|
|
11
13
|
// Config helpers for consumers embedding pr-checkmate.
|
package/dist/esm/init.js
CHANGED
|
@@ -4,7 +4,7 @@ const __dirname = __dirnameFn(__fileURLToPath(import.meta.url));
|
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import path from 'path';
|
|
7
|
-
import { DEAD_CODE_IGNORE_DEFAULTS, DEFAULT_COMMANDS, DIFF_SECURITY_IGNORE_DEFAULTS, DOCS_URL, documentation, DUPLICATE_IGNORE_DEFAULTS, FULL_CONFIG_URL, GENERATED_DOC_FILES, IGNORED_DIRS, } from './config/index.js';
|
|
7
|
+
import { AGENT_SETUP_COMMAND, DEAD_CODE_IGNORE_DEFAULTS, DEFAULT_COMMANDS, DIFF_SECURITY_IGNORE_DEFAULTS, DOCS_URL, documentation, DUPLICATE_IGNORE_DEFAULTS, FULL_CONFIG_URL, GENERATED_DOC_FILES, IGNORED_DIRS, } from './config/index.js';
|
|
8
8
|
import { DEFAULT_DATAFLOW_WINDOW } from './core/checks/findings.js';
|
|
9
9
|
import { detectLanguages, findOwnPackageRoot, logger } from './utils/index.js';
|
|
10
10
|
const CONFIG_FILE = path.resolve(process.cwd(), 'pr-checkmate.json');
|
|
@@ -413,6 +413,16 @@ async function createConfig(root) {
|
|
|
413
413
|
* update notice insisted `init` must not be skipped after an upgrade. Defaults
|
|
414
414
|
* now apply when the config is loaded, so there is nothing to top up.
|
|
415
415
|
*/
|
|
416
|
+
/** Adds the agent-setup command to a client's `commands` block when it is missing; nothing else in it is touched. */
|
|
417
|
+
function withAgentCommand(commands) {
|
|
418
|
+
if (commands === undefined)
|
|
419
|
+
return undefined;
|
|
420
|
+
if (typeof commands !== 'object' || commands === null || Array.isArray(commands))
|
|
421
|
+
return commands;
|
|
422
|
+
if (AGENT_SETUP_COMMAND in commands)
|
|
423
|
+
return commands;
|
|
424
|
+
return { ...commands, [AGENT_SETUP_COMMAND]: DEFAULT_COMMANDS[AGENT_SETUP_COMMAND] };
|
|
425
|
+
}
|
|
416
426
|
async function updateConfig(root) {
|
|
417
427
|
logger.info('[init]: pr-checkmate.json already exists — updating it.');
|
|
418
428
|
try {
|
|
@@ -443,6 +453,7 @@ async function updateConfig(root) {
|
|
|
443
453
|
// when missing; a client's own block is never touched.
|
|
444
454
|
grypeScan: json.grypeScan ?? structuredClone(GRYPE_SCAN_SEED),
|
|
445
455
|
...templateExtras({ ...json, grypeScan: true }),
|
|
456
|
+
commands: withAgentCommand(json.commands),
|
|
446
457
|
languages,
|
|
447
458
|
};
|
|
448
459
|
fs.writeFileSync(CONFIG_FILE, `${JSON.stringify(updated, null, 2)}\n`);
|
|
@@ -317,6 +317,18 @@ export interface PRCheckMateConfig {
|
|
|
317
317
|
severityOverrides?: Record<string, 'error' | 'warn' | 'off'>;
|
|
318
318
|
ignorePaths?: string[];
|
|
319
319
|
};
|
|
320
|
+
mcp?: {
|
|
321
|
+
enabled?: boolean;
|
|
322
|
+
disabledTools?: string[];
|
|
323
|
+
readOnly?: boolean;
|
|
324
|
+
};
|
|
325
|
+
agentInstructions?: {
|
|
326
|
+
enabled?: boolean;
|
|
327
|
+
ignore?: string[];
|
|
328
|
+
failOn?: 'error' | 'warn' | 'never';
|
|
329
|
+
severityOverrides?: Record<string, 'error' | 'warn' | 'off'>;
|
|
330
|
+
ignorePaths?: string[];
|
|
331
|
+
};
|
|
320
332
|
dockerfileSecurity?: {
|
|
321
333
|
enabled?: boolean;
|
|
322
334
|
ignore?: string[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ToolResult } from './protocol.js';
|
|
2
|
+
type Json = Record<string, unknown>;
|
|
3
|
+
/** What a key means, from the schema that ships with the package. */
|
|
4
|
+
export declare function explainConfig(key?: string): ToolResult;
|
|
5
|
+
/** Problems in a proposed config, phrased so the agent can correct them. Empty means it is acceptable. */
|
|
6
|
+
export declare function validateConfig(config: Json, cwd: string): string[];
|
|
7
|
+
/** Validate and write `config` into the repository's `pr-checkmate.json`. */
|
|
8
|
+
export declare function writeConfig(cwd: string, config: unknown, overwrite: boolean): ToolResult;
|
|
9
|
+
interface Suggestion {
|
|
10
|
+
proposed: Json;
|
|
11
|
+
notes: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Look at the repository and propose a config.
|
|
15
|
+
*
|
|
16
|
+
* The baseline is a whole-repository run of the fast checks. A check that already
|
|
17
|
+
* fails is proposed as `warn`: the gate is on from the first commit, the existing
|
|
18
|
+
* debt is visible, and nothing new can slip in unnoticed — then each check is
|
|
19
|
+
* promoted back to blocking as it reaches zero. Proposing `error` everywhere would
|
|
20
|
+
* make the first CI run red for reasons nobody on the pull request caused, and the
|
|
21
|
+
* usual response to that is to switch the whole tool off.
|
|
22
|
+
*/
|
|
23
|
+
export declare function suggestConfig(cwd: string): Promise<Suggestion & {
|
|
24
|
+
languages: string[];
|
|
25
|
+
}>;
|
|
26
|
+
/** `suggest_config` as the text an agent reads. */
|
|
27
|
+
export declare function formatSuggestion(s: Suggestion & {
|
|
28
|
+
languages: string[];
|
|
29
|
+
}): ToolResult;
|
|
30
|
+
export {};
|