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.
- package/.eslintrc.js +3 -9
- package/LICENSE +8 -3
- package/README.md +54 -5
- 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 +101 -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 +11 -1
- package/dist/cjs/core/checks/dependencies/grype-scan.js +18 -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/languages/lint.js +12 -0
- package/dist/cjs/core/checks/languages/ruff-shared.js +6 -1
- package/dist/cjs/core/checks/pr/commitlint.js +4 -2
- 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/quality/package-size.d.ts +23 -1
- package/dist/cjs/core/checks/quality/package-size.js +20 -4
- package/dist/cjs/core/checks/quality/spellcheck.js +8 -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/install/doctor.js +12 -0
- package/dist/cjs/core/install/grype-db.d.ts +25 -0
- package/dist/cjs/core/install/grype-db.js +45 -0
- package/dist/cjs/core/install/profiles.d.ts +1 -1
- package/dist/cjs/core/install/profiles.js +1 -1
- package/dist/cjs/core/install/reuse.js +6 -3
- 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.d.ts +0 -8
- package/dist/cjs/init.js +12 -2
- 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/own-deps.js +8 -1
- package/dist/cjs/utils/resolve-bin.d.ts +2 -0
- package/dist/cjs/utils/resolve-bin.js +5 -0
- 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 +100 -2
- 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 +11 -1
- package/dist/esm/core/checks/dependencies/dependency-check.js +2 -2
- package/dist/esm/core/checks/dependencies/grype-scan.js +18 -0
- package/dist/esm/core/checks/dependencies/license-check.js +2 -2
- package/dist/esm/core/checks/dependencies/npm-audit.js +2 -2
- package/dist/esm/core/checks/findings.js +33 -7
- package/dist/esm/core/checks/git/diff-smell.js +1 -1
- 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/languages/cpp-format.js +1 -1
- package/dist/esm/core/checks/languages/lint.js +12 -3
- package/dist/esm/core/checks/languages/prettier.js +3 -3
- package/dist/esm/core/checks/languages/python-format.js +2 -2
- package/dist/esm/core/checks/languages/python-lint.js +2 -2
- package/dist/esm/core/checks/languages/ruff-shared.js +6 -1
- package/dist/esm/core/checks/languages/typecheck.js +1 -1
- package/dist/esm/core/checks/pr/commitlint.js +4 -2
- package/dist/esm/core/checks/pr/pr-body.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 +3 -3
- package/dist/esm/core/checks/pr/pr-title-lint.js +1 -1
- package/dist/esm/core/checks/quality/dead-code.js +1 -1
- package/dist/esm/core/checks/quality/duplicate-check.js +1 -1
- package/dist/esm/core/checks/quality/markdown-lint.js +1 -1
- package/dist/esm/core/checks/quality/package-size.d.ts +23 -1
- package/dist/esm/core/checks/quality/package-size.js +19 -4
- package/dist/esm/core/checks/quality/spellcheck.js +11 -8
- 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 +21 -1
- package/dist/esm/core/checks/security/security.d.ts +0 -13
- package/dist/esm/core/checks/security/security.js +28 -1
- 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/install/doctor.js +12 -0
- package/dist/esm/core/install/grype-db.d.ts +25 -0
- package/dist/esm/core/install/grype-db.js +41 -0
- package/dist/esm/core/install/prepare.js +1 -1
- package/dist/esm/core/install/profiles.d.ts +1 -1
- package/dist/esm/core/install/profiles.js +1 -1
- package/dist/esm/core/install/reuse.js +6 -3
- package/dist/esm/core/registry.js +2 -0
- package/dist/esm/core/reporters.js +9 -0
- package/dist/esm/core/run-checks.js +17 -7
- 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.d.ts +0 -8
- package/dist/esm/init.js +15 -4
- 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/own-deps.js +8 -1
- package/dist/esm/utils/resolve-bin.d.ts +2 -0
- package/dist/esm/utils/resolve-bin.js +4 -0
- package/dist/esm/utils/result-collector.d.ts +17 -1
- package/dist/esm/utils/result-collector.js +7 -2
- package/eslint-default-rules.json +2 -8
- package/eslint.config.mjs +37 -8
- package/install-manifests/commitlint/package-lock.json +5 -4
- package/install-manifests/core/package-lock.json +5 -3
- package/install-manifests/js/package-lock.json +250 -571
- package/install-manifests/js/package.json +10 -8
- package/install-manifests/markdown/package-lock.json +16 -13
- package/install-manifests/markdown/package.json +3 -0
- package/install-manifests/spellcheck/package-lock.json +3 -3
- package/package.json +24 -16
- package/pr-checkmate.schema.json +35 -0
- package/src/config/.eslintrc.js +3 -9
- package/src/config/eslint-default-rules.json +2 -8
- package/src/config/pr-checkmate.schema.json +35 -0
|
@@ -9,6 +9,7 @@ const pool_js_1 = require("../utils/pool.js");
|
|
|
9
9
|
const resolve_bin_js_1 = require("../utils/resolve-bin.js");
|
|
10
10
|
const result_collector_js_1 = require("../utils/result-collector.js");
|
|
11
11
|
const context_js_1 = require("./context.js");
|
|
12
|
+
const finding_store_js_1 = require("./finding-store.js");
|
|
12
13
|
const prepare_js_1 = require("./install/prepare.js");
|
|
13
14
|
const registry_js_1 = require("./registry.js");
|
|
14
15
|
const reporters_js_1 = require("./reporters.js");
|
|
@@ -174,15 +175,24 @@ async function runOne(check, ctx, collector, heavy, tooling) {
|
|
|
174
175
|
}
|
|
175
176
|
const started = Date.now();
|
|
176
177
|
const timeoutMs = ctx.config.run?.checkTimeoutMs ?? 300000;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
// Locations noted while this check runs, collected per check: the phase runs
|
|
179
|
+
// its checks in parallel, so a shared list would mix their findings.
|
|
180
|
+
let located = [];
|
|
181
|
+
const execute = async () => {
|
|
182
|
+
const { result, findings } = await (0, finding_store_js_1.collectFindings)(() => (0, pool_js_1.withTimeout)(check.run(ctx), timeoutMs, () => {
|
|
183
|
+
index_js_1.logger.warn(`[runChecks]: ⏱️ ${check.name} exceeded ${timeoutMs}ms`);
|
|
184
|
+
return { status: 'warn', details: `timed out after ${timeoutMs}ms` };
|
|
185
|
+
}));
|
|
186
|
+
located = findings;
|
|
187
|
+
return result;
|
|
188
|
+
};
|
|
181
189
|
const outcome = check.weight === 'heavy' ? await heavy.run(execute) : await execute();
|
|
182
190
|
if (ctx.config.run?.timings) {
|
|
183
191
|
index_js_1.logger.info(`[runChecks]: ${check.name} took ${Date.now() - started}ms`);
|
|
184
192
|
}
|
|
185
|
-
collector.register(check.name, applySeverity(check.name, outcome.status, ctx), outcome.details
|
|
193
|
+
collector.register(check.name, applySeverity(check.name, outcome.status, ctx), outcome.details,
|
|
194
|
+
// A clean result has nothing to locate, even when `failOn: never` filed some.
|
|
195
|
+
outcome.status === 'pass' ? undefined : (outcome.findings ?? located));
|
|
186
196
|
}
|
|
187
197
|
catch (err) {
|
|
188
198
|
// 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,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toSarif = toSarif;
|
|
4
|
+
const SARIF_SCHEMA = 'https://json.schemastore.org/sarif-2.1.0.json';
|
|
5
|
+
const ruleIdFor = (check, rule) => (rule ? `${check}/${rule}` : check);
|
|
6
|
+
function locatedResult(check, f) {
|
|
7
|
+
return {
|
|
8
|
+
ruleId: ruleIdFor(check.name, f.rule),
|
|
9
|
+
level: f.severity === 'error' ? 'error' : 'warning',
|
|
10
|
+
message: { text: f.snippet ? `${f.rule}: ${f.snippet}` : f.rule },
|
|
11
|
+
locations: [
|
|
12
|
+
{
|
|
13
|
+
physicalLocation: {
|
|
14
|
+
artifactLocation: { uri: f.file },
|
|
15
|
+
...(f.line !== undefined && { region: { startLine: f.line } }),
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function summaryResult(check) {
|
|
22
|
+
return {
|
|
23
|
+
ruleId: ruleIdFor(check.name),
|
|
24
|
+
level: check.status === 'fail' ? 'error' : 'warning',
|
|
25
|
+
message: { text: check.details ?? `${check.name} reported a problem` },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Convert a report to a SARIF log object, ready for `JSON.stringify`. */
|
|
29
|
+
function toSarif(report, toolVersion) {
|
|
30
|
+
const results = [];
|
|
31
|
+
const rules = new Map();
|
|
32
|
+
for (const check of report.results) {
|
|
33
|
+
if (check.status !== 'fail' && check.status !== 'warn')
|
|
34
|
+
continue;
|
|
35
|
+
const found = check.findings && check.findings.length > 0
|
|
36
|
+
? check.findings.map(f => locatedResult(check, f))
|
|
37
|
+
: [summaryResult(check)];
|
|
38
|
+
for (const r of found) {
|
|
39
|
+
results.push(r);
|
|
40
|
+
rules.set(r.ruleId, { id: r.ruleId, name: r.ruleId });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
$schema: SARIF_SCHEMA,
|
|
45
|
+
version: '2.1.0',
|
|
46
|
+
runs: [
|
|
47
|
+
{
|
|
48
|
+
tool: {
|
|
49
|
+
driver: {
|
|
50
|
+
name: 'pr-checkmate',
|
|
51
|
+
version: toolVersion,
|
|
52
|
+
informationUri: 'https://www.npmjs.com/package/pr-checkmate',
|
|
53
|
+
rules: [...rules.values()],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
results,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
}
|
package/dist/cjs/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/cjs/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/cjs/index.js
CHANGED
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
* @module pr-checkmate
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.Logger = exports.hasLocalConfig = exports.getPackageConfigPath = exports.getIgnoredDirs = exports.getSourcePaths = exports.getSourcePath = exports.defineCheck = exports.skip = exports.fail = exports.warn = exports.pass = exports.REGISTRY = exports.buildContext = exports.runChecks = void 0;
|
|
8
|
+
exports.Logger = exports.hasLocalConfig = exports.getPackageConfigPath = exports.getIgnoredDirs = exports.getSourcePaths = exports.getSourcePath = exports.defineCheck = exports.skip = exports.fail = exports.warn = exports.pass = exports.toSarif = exports.REGISTRY = exports.buildContext = exports.runChecks = void 0;
|
|
9
9
|
// Primary programmatic API: run checks and receive a structured report.
|
|
10
10
|
// Never throws for violations, never calls process.exit — the caller decides.
|
|
11
11
|
var index_js_1 = require("./core/index.js");
|
|
12
12
|
Object.defineProperty(exports, "runChecks", { enumerable: true, get: function () { return index_js_1.runChecks; } });
|
|
13
13
|
Object.defineProperty(exports, "buildContext", { enumerable: true, get: function () { return index_js_1.buildContext; } });
|
|
14
14
|
Object.defineProperty(exports, "REGISTRY", { enumerable: true, get: function () { return index_js_1.REGISTRY; } });
|
|
15
|
+
// SARIF 2.1.0 export of a report, for code-scanning dashboards and editors.
|
|
16
|
+
var sarif_js_1 = require("./core/sarif.js");
|
|
17
|
+
Object.defineProperty(exports, "toSarif", { enumerable: true, get: function () { return sarif_js_1.toSarif; } });
|
|
15
18
|
// Authoring helpers for consumers writing their own checks (plugins).
|
|
16
19
|
var index_js_2 = require("./core/index.js");
|
|
17
20
|
Object.defineProperty(exports, "pass", { enumerable: true, get: function () { return index_js_2.pass; } });
|
package/dist/cjs/init.d.ts
CHANGED
|
@@ -30,14 +30,6 @@ export declare function setupWorkflow(root: string, mode: GateMode): void;
|
|
|
30
30
|
* heart about what should block.
|
|
31
31
|
*/
|
|
32
32
|
export declare const BLOCKING_BY_DEFAULT: readonly ["Security Scan", "Sensitive Files", "Diff Security", "Workflow Security", "Merge Conflict", "NPM Audit", "License Check", "Dependencies", "TypeScript", "ESLint", "YAML Lint"];
|
|
33
|
-
/**
|
|
34
|
-
* Every option the template documents that the file does not already carry, at
|
|
35
|
-
* its default. Without this, options such as `vulnScan` (off by default) or
|
|
36
|
-
* `licenseCheck` exist only in the docs, and nobody edits what they cannot see.
|
|
37
|
-
* Comment keys travel with the option they describe, section headers with the
|
|
38
|
-
* first option after them.
|
|
39
|
-
*/
|
|
40
|
-
export declare function templateExtras(existing: Record<string, unknown>): Record<string, unknown>;
|
|
41
33
|
export declare function buildInitialConfig(sourcePath: string, languages: string[]): Record<string, unknown>;
|
|
42
34
|
/**
|
|
43
35
|
* Initialize (or refresh) pr-checkmate configuration in the current project.
|
package/dist/cjs/init.js
CHANGED
|
@@ -5,12 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.BLOCKING_BY_DEFAULT = void 0;
|
|
7
7
|
exports.setupWorkflow = setupWorkflow;
|
|
8
|
-
exports.templateExtras = templateExtras;
|
|
9
8
|
exports.buildInitialConfig = buildInitialConfig;
|
|
10
9
|
exports.init = init;
|
|
11
10
|
const fs_1 = __importDefault(require("fs"));
|
|
12
|
-
const path_1 = __importDefault(require("path"));
|
|
13
11
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
14
13
|
const index_js_1 = require("./config/index.js");
|
|
15
14
|
const findings_js_1 = require("./core/checks/findings.js");
|
|
16
15
|
const index_js_2 = require("./utils/index.js");
|
|
@@ -420,6 +419,16 @@ async function createConfig(root) {
|
|
|
420
419
|
* update notice insisted `init` must not be skipped after an upgrade. Defaults
|
|
421
420
|
* now apply when the config is loaded, so there is nothing to top up.
|
|
422
421
|
*/
|
|
422
|
+
/** Adds the agent-setup command to a client's `commands` block when it is missing; nothing else in it is touched. */
|
|
423
|
+
function withAgentCommand(commands) {
|
|
424
|
+
if (commands === undefined)
|
|
425
|
+
return undefined;
|
|
426
|
+
if (typeof commands !== 'object' || commands === null || Array.isArray(commands))
|
|
427
|
+
return commands;
|
|
428
|
+
if (index_js_1.AGENT_SETUP_COMMAND in commands)
|
|
429
|
+
return commands;
|
|
430
|
+
return { ...commands, [index_js_1.AGENT_SETUP_COMMAND]: index_js_1.DEFAULT_COMMANDS[index_js_1.AGENT_SETUP_COMMAND] };
|
|
431
|
+
}
|
|
423
432
|
async function updateConfig(root) {
|
|
424
433
|
index_js_2.logger.info('[init]: pr-checkmate.json already exists — updating it.');
|
|
425
434
|
try {
|
|
@@ -450,6 +459,7 @@ async function updateConfig(root) {
|
|
|
450
459
|
// when missing; a client's own block is never touched.
|
|
451
460
|
grypeScan: json.grypeScan ?? structuredClone(GRYPE_SCAN_SEED),
|
|
452
461
|
...templateExtras({ ...json, grypeScan: true }),
|
|
462
|
+
commands: withAgentCommand(json.commands),
|
|
453
463
|
languages,
|
|
454
464
|
};
|
|
455
465
|
fs_1.default.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 {};
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.explainConfig = explainConfig;
|
|
7
|
+
exports.validateConfig = validateConfig;
|
|
8
|
+
exports.writeConfig = writeConfig;
|
|
9
|
+
exports.suggestConfig = suggestConfig;
|
|
10
|
+
exports.formatSuggestion = formatSuggestion;
|
|
11
|
+
/**
|
|
12
|
+
* Setting pr-checkmate up for one specific repository, by the agent that is
|
|
13
|
+
* already sitting in it.
|
|
14
|
+
*
|
|
15
|
+
* The slow part of adopting a gate is not turning it on; it is deciding what to do
|
|
16
|
+
* about the findings an existing codebase already has. The documented answer is a
|
|
17
|
+
* strict workflow with a permissive config, then promoting checks one at a time as
|
|
18
|
+
* they go clean. That is a procedure an agent can carry out — if it can see the
|
|
19
|
+
* baseline — so these tools hand it exactly that: what the repository looks like,
|
|
20
|
+
* what it fails today, and a config that makes the gate real from day one without
|
|
21
|
+
* turning the first run into three thousand red lines.
|
|
22
|
+
*
|
|
23
|
+
* Nothing here writes except `write_config`, and that one validates first, merges
|
|
24
|
+
* over what is already there, and never replaces a value the user chose.
|
|
25
|
+
*/
|
|
26
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
27
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
28
|
+
const constants_js_1 = require("../config/constants.js");
|
|
29
|
+
const loader_js_1 = require("../config/loader.js");
|
|
30
|
+
const config_validation_js_1 = require("../core/checks/quality/config-validation.js");
|
|
31
|
+
const registry_js_1 = require("../core/registry.js");
|
|
32
|
+
const detect_language_js_1 = require("../utils/detect-language.js");
|
|
33
|
+
const own_deps_js_1 = require("../utils/own-deps.js");
|
|
34
|
+
const review_js_1 = require("./review.js");
|
|
35
|
+
const CONFIG_FILE = 'pr-checkmate.json';
|
|
36
|
+
const LEVELS = new Set(['error', 'warn', 'off']);
|
|
37
|
+
const FAIL_ON = new Set(['error', 'warn', 'never']);
|
|
38
|
+
/** Directories that hold code nobody on the team wrote by hand. Not in the default ignore list. */
|
|
39
|
+
const GENERATED_DIRS = ['generated', '__generated__', 'vendor', 'third_party', 'third-party'];
|
|
40
|
+
/**
|
|
41
|
+
* Checks whose failure is never "existing debt". A conflict marker is a file that
|
|
42
|
+
* does not run; a leaked secret is already exposed. Proposing `warn` for these
|
|
43
|
+
* would make the gate quietly agree to ship them, so they are reported as things
|
|
44
|
+
* to fix, and left blocking.
|
|
45
|
+
*/
|
|
46
|
+
const NEVER_DOWNGRADE = new Set(['Merge Conflict', 'Security Scan']);
|
|
47
|
+
const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
48
|
+
/** The schema shipped with the package, or `undefined` when this install does not carry it. */
|
|
49
|
+
function loadSchema() {
|
|
50
|
+
const root = (0, own_deps_js_1.findOwnPackageRoot)();
|
|
51
|
+
if (!root)
|
|
52
|
+
return undefined;
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(root, 'pr-checkmate.schema.json'), 'utf-8'));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function resolveRef(node, schema) {
|
|
61
|
+
const ref = node.$ref?.replace('#/$defs/', '');
|
|
62
|
+
return ref && schema.$defs?.[ref] ? { ...schema.$defs[ref], ...node, $ref: undefined } : node;
|
|
63
|
+
}
|
|
64
|
+
/** First sentence, so a listing of sixty keys stays a listing. */
|
|
65
|
+
const firstSentence = (text = '') => {
|
|
66
|
+
const cut = text.search(/(?<=[.!?])\s/);
|
|
67
|
+
return (cut === -1 ? text : text.slice(0, cut)).slice(0, 200);
|
|
68
|
+
};
|
|
69
|
+
/** What a key means, from the schema that ships with the package. */
|
|
70
|
+
function explainConfig(key) {
|
|
71
|
+
const schema = loadSchema();
|
|
72
|
+
if (!schema?.properties) {
|
|
73
|
+
return { text: 'The configuration schema is not available in this install.', isError: true };
|
|
74
|
+
}
|
|
75
|
+
if (!key) {
|
|
76
|
+
const rows = Object.entries(schema.properties)
|
|
77
|
+
.filter(([name]) => name !== '$schema')
|
|
78
|
+
.map(([name, node]) => {
|
|
79
|
+
const resolved = resolveRef(node, schema);
|
|
80
|
+
return `- ${name}${resolved.description ? ` — ${firstSentence(resolved.description)}` : ''}`;
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
text: `Keys of ${CONFIG_FILE} (pass one as "key" for details; nested keys use dots, e.g. "prSize.maxLines"):\n${rows.join('\n')}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
let node = schema;
|
|
87
|
+
for (const part of key.split('.')) {
|
|
88
|
+
node = node?.properties?.[part] ? resolveRef(node.properties[part], schema) : undefined;
|
|
89
|
+
if (!node)
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
if (!node) {
|
|
93
|
+
const near = (0, config_validation_js_1.suggestName)(key.split('.')[0], Object.keys(schema.properties));
|
|
94
|
+
return {
|
|
95
|
+
text: `No such key "${key}".${near ? ` Did you mean "${near}"?` : ''}`,
|
|
96
|
+
isError: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const lines = [`${key}${node.type ? ` (${node.type})` : ''}`];
|
|
100
|
+
if (node.description)
|
|
101
|
+
lines.push(node.description);
|
|
102
|
+
if (node.default !== undefined)
|
|
103
|
+
lines.push(`default: ${JSON.stringify(node.default)}`);
|
|
104
|
+
if (node.enum)
|
|
105
|
+
lines.push(`one of: ${node.enum.map(v => JSON.stringify(v)).join(', ')}`);
|
|
106
|
+
for (const [name, child] of Object.entries(node.properties ?? {})) {
|
|
107
|
+
const resolved = resolveRef(child, schema);
|
|
108
|
+
lines.push(`- ${key}.${name}${resolved.description ? ` — ${firstSentence(resolved.description)}` : ''}`);
|
|
109
|
+
}
|
|
110
|
+
return { text: lines.join('\n') };
|
|
111
|
+
}
|
|
112
|
+
/** Problems in a proposed `severity` map: names that are not checks, levels that are not levels. */
|
|
113
|
+
function validateSeverity(severity) {
|
|
114
|
+
if (severity === undefined)
|
|
115
|
+
return [];
|
|
116
|
+
if (!isRecord(severity)) {
|
|
117
|
+
return ['"severity" must be an object of check name → "error" | "warn" | "off"'];
|
|
118
|
+
}
|
|
119
|
+
const problems = [];
|
|
120
|
+
const names = registry_js_1.REGISTRY.map(c => c.name);
|
|
121
|
+
for (const [check, level] of Object.entries(severity)) {
|
|
122
|
+
if (!names.includes(check)) {
|
|
123
|
+
const near = (0, config_validation_js_1.suggestName)(check, names);
|
|
124
|
+
problems.push(`severity: no check named "${check}"${near ? ` — did you mean "${near}"?` : ''}`);
|
|
125
|
+
}
|
|
126
|
+
if (typeof level !== 'string' || !LEVELS.has(level)) {
|
|
127
|
+
problems.push(`severity.${check}: "${String(level)}" is not one of error, warn, off`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return problems;
|
|
131
|
+
}
|
|
132
|
+
/** Problems in a proposed config, phrased so the agent can correct them. Empty means it is acceptable. */
|
|
133
|
+
function validateConfig(config, cwd) {
|
|
134
|
+
const problems = [];
|
|
135
|
+
const schema = loadSchema();
|
|
136
|
+
const known = Object.keys(schema?.properties ?? {});
|
|
137
|
+
if (known.length > 0) {
|
|
138
|
+
for (const key of Object.keys(config)) {
|
|
139
|
+
if (known.includes(key) || key.startsWith('//'))
|
|
140
|
+
continue;
|
|
141
|
+
const near = (0, config_validation_js_1.suggestName)(key, known);
|
|
142
|
+
problems.push(`unknown key "${key}"${near ? ` — did you mean "${near}"?` : ''}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
problems.push(...validateSeverity(config.severity));
|
|
146
|
+
for (const [key, block] of Object.entries(config)) {
|
|
147
|
+
if (isRecord(block) && typeof block.failOn === 'string' && !FAIL_ON.has(block.failOn)) {
|
|
148
|
+
problems.push(`${key}.failOn: "${block.failOn}" is not one of error, warn, never`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const sources = config.sourcePath === undefined ? [] : [config.sourcePath].flat();
|
|
152
|
+
for (const source of sources) {
|
|
153
|
+
if (typeof source !== 'string' || !node_fs_1.default.existsSync(node_path_1.default.resolve(cwd, source))) {
|
|
154
|
+
problems.push(`sourcePath: "${String(source)}" does not exist in this repository`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return problems;
|
|
158
|
+
}
|
|
159
|
+
/** `{a:{b:1,c:2}}` under `a` → `a.b`, `a.c`: what was added, not just where it hangs. */
|
|
160
|
+
function leafPaths(value, here) {
|
|
161
|
+
return isRecord(value) && Object.keys(value).length > 0
|
|
162
|
+
? Object.entries(value).flatMap(([k, v]) => leafPaths(v, `${here}.${k}`))
|
|
163
|
+
: [here];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Merge `incoming` into `existing`. A value that is already set and differs is
|
|
167
|
+
* kept — the file is the user's — and listed, so the agent can ask rather than
|
|
168
|
+
* overwrite. Objects merge key by key; arrays and scalars are leaves.
|
|
169
|
+
*/
|
|
170
|
+
function mergeKeepingExisting(existing, incoming, trail, overwrite, report) {
|
|
171
|
+
const out = { ...existing };
|
|
172
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
173
|
+
const here = [...trail, key].join('.');
|
|
174
|
+
if (!(key in existing)) {
|
|
175
|
+
out[key] = value;
|
|
176
|
+
report.added.push(...leafPaths(value, here));
|
|
177
|
+
}
|
|
178
|
+
else if (isRecord(existing[key]) && isRecord(value)) {
|
|
179
|
+
out[key] = mergeKeepingExisting(existing[key], value, [...trail, key], overwrite, report);
|
|
180
|
+
}
|
|
181
|
+
else if (JSON.stringify(existing[key]) === JSON.stringify(value)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
else if (overwrite) {
|
|
185
|
+
out[key] = value;
|
|
186
|
+
report.added.push(`${here} (replaced)`);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
report.kept.push(here);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
/** Validate and write `config` into the repository's `pr-checkmate.json`. */
|
|
195
|
+
function writeConfig(cwd, config, overwrite) {
|
|
196
|
+
if (!isRecord(config))
|
|
197
|
+
return { text: '"config" must be a JSON object.', isError: true };
|
|
198
|
+
const problems = validateConfig(config, cwd);
|
|
199
|
+
if (problems.length > 0) {
|
|
200
|
+
return {
|
|
201
|
+
text: `Not written — fix these first:\n${problems.map(p => `- ${p}`).join('\n')}`,
|
|
202
|
+
isError: true,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const file = node_path_1.default.join(cwd, CONFIG_FILE);
|
|
206
|
+
let existing = {};
|
|
207
|
+
if (node_fs_1.default.existsSync(file)) {
|
|
208
|
+
try {
|
|
209
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(file, 'utf-8'));
|
|
210
|
+
if (isRecord(parsed))
|
|
211
|
+
existing = parsed;
|
|
212
|
+
else
|
|
213
|
+
throw new Error('not an object');
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// A file we cannot read is a file we must not replace: the user may be mid-edit.
|
|
217
|
+
return {
|
|
218
|
+
text: `${CONFIG_FILE} exists but is not valid JSON; fix it by hand first.`,
|
|
219
|
+
isError: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const report = { added: [], kept: [] };
|
|
224
|
+
const merged = mergeKeepingExisting(existing, config, [], overwrite, report);
|
|
225
|
+
if (report.added.length === 0) {
|
|
226
|
+
return {
|
|
227
|
+
text: `Nothing to write: ${CONFIG_FILE} already holds these values.${report.kept.length > 0 ? ` Left as the user set them: ${report.kept.join(', ')} (pass overwrite: true only if they asked for the change).` : ''}`,
|
|
228
|
+
structured: { written: false, added: [], kept: report.kept },
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// Point editors at the schema when this repository has the package installed:
|
|
232
|
+
// that line is what makes every other key discoverable by autocomplete.
|
|
233
|
+
const schemaPath = './node_modules/pr-checkmate/pr-checkmate.schema.json';
|
|
234
|
+
const withSchema = !('$schema' in merged) && node_fs_1.default.existsSync(node_path_1.default.join(cwd, schemaPath))
|
|
235
|
+
? { $schema: schemaPath, ...merged }
|
|
236
|
+
: merged;
|
|
237
|
+
node_fs_1.default.writeFileSync(file, `${JSON.stringify(withSchema, null, 2)}\n`);
|
|
238
|
+
return {
|
|
239
|
+
text: [
|
|
240
|
+
`${existing && Object.keys(existing).length > 0 ? 'Updated' : 'Created'} ${CONFIG_FILE}:`,
|
|
241
|
+
...report.added.map(k => ` + ${k}`),
|
|
242
|
+
...(report.kept.length > 0
|
|
243
|
+
? [
|
|
244
|
+
`Left as the user set them: ${report.kept.join(', ')} (overwrite: true replaces them — only if asked).`,
|
|
245
|
+
]
|
|
246
|
+
: []),
|
|
247
|
+
].join('\n'),
|
|
248
|
+
structured: { written: true, added: report.added, kept: report.kept },
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Look at the repository and propose a config.
|
|
253
|
+
*
|
|
254
|
+
* The baseline is a whole-repository run of the fast checks. A check that already
|
|
255
|
+
* fails is proposed as `warn`: the gate is on from the first commit, the existing
|
|
256
|
+
* debt is visible, and nothing new can slip in unnoticed — then each check is
|
|
257
|
+
* promoted back to blocking as it reaches zero. Proposing `error` everywhere would
|
|
258
|
+
* make the first CI run red for reasons nobody on the pull request caused, and the
|
|
259
|
+
* usual response to that is to switch the whole tool off.
|
|
260
|
+
*/
|
|
261
|
+
async function suggestConfig(cwd) {
|
|
262
|
+
const current = (0, loader_js_1.loadProjectConfig)(cwd);
|
|
263
|
+
const usage = await (0, detect_language_js_1.detectLanguageUsage)(cwd, current.ignoreDirs ?? constants_js_1.IGNORED_DIRS, current.detect);
|
|
264
|
+
const languages = usage.map(u => `${u.lang} (${u.files} file${u.files === 1 ? '' : 's'})`);
|
|
265
|
+
const hasConfig = node_fs_1.default.existsSync(node_path_1.default.join(cwd, CONFIG_FILE));
|
|
266
|
+
const proposed = {};
|
|
267
|
+
const notes = [];
|
|
268
|
+
const generated = GENERATED_DIRS.filter(d => node_fs_1.default.existsSync(node_path_1.default.join(cwd, d)) && !(current.ignoreDirs ?? constants_js_1.IGNORED_DIRS).includes(d));
|
|
269
|
+
if (generated.length > 0) {
|
|
270
|
+
proposed.excludePaths = generated.map(d => `${d}/**`);
|
|
271
|
+
notes.push(`Excluded ${generated.join(', ')}: code nobody maintains by hand only produces findings no one can act on.`);
|
|
272
|
+
}
|
|
273
|
+
const baseline = await (0, review_js_1.review)({ cwd, scope: 'full' });
|
|
274
|
+
const severity = {};
|
|
275
|
+
const skipped = [];
|
|
276
|
+
for (const r of baseline.report.results) {
|
|
277
|
+
if (r.status === 'fail' && NEVER_DOWNGRADE.has(r.name)) {
|
|
278
|
+
notes.push(`${r.name} fails today (${r.details ?? 'findings'}) and is NOT proposed as a warning: fix it${r.name === 'Security Scan' ? ' and rotate whatever was exposed' : ''} instead of silencing it.`);
|
|
279
|
+
}
|
|
280
|
+
else if (r.status === 'fail') {
|
|
281
|
+
severity[r.name] = 'warn';
|
|
282
|
+
notes.push(`${r.name} fails today (${r.details ?? 'findings'}) → warn, so existing debt does not block every PR. Promote it back to "error" once it is clean.`);
|
|
283
|
+
}
|
|
284
|
+
else if (r.status === 'skip' && /tooling unavailable/.test(r.details ?? '')) {
|
|
285
|
+
skipped.push(r.name);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (Object.keys(severity).length > 0)
|
|
289
|
+
proposed.severity = severity;
|
|
290
|
+
if (skipped.length > 0) {
|
|
291
|
+
notes.push(`Could not run ${skipped.join(', ')}: tooling not available here (run "pr-checkmate install" and ask again for a complete baseline).`);
|
|
292
|
+
}
|
|
293
|
+
const clean = baseline.report.results.filter(r => r.status === 'pass').length;
|
|
294
|
+
notes.push(`Baseline: ${clean} check(s) pass, ${baseline.report.summary.failed} fail, ${baseline.report.summary.warned} warn (fast checks only; "verify" adds the slow ones).`);
|
|
295
|
+
if (hasConfig)
|
|
296
|
+
notes.push(`${CONFIG_FILE} already exists: write_config keeps every value already set and only adds the rest.`);
|
|
297
|
+
if (Object.keys(proposed).length === 0) {
|
|
298
|
+
notes.push('The defaults already fit this repository: no config file is needed. Everything is on and blocking where it should be.');
|
|
299
|
+
}
|
|
300
|
+
return { proposed, notes, languages };
|
|
301
|
+
}
|
|
302
|
+
/** `suggest_config` as the text an agent reads. */
|
|
303
|
+
function formatSuggestion(s) {
|
|
304
|
+
const body = Object.keys(s.proposed).length > 0
|
|
305
|
+
? `\nProposed ${CONFIG_FILE}:\n${JSON.stringify(s.proposed, null, 2)}\n`
|
|
306
|
+
: '\n';
|
|
307
|
+
return {
|
|
308
|
+
text: [
|
|
309
|
+
`Languages: ${s.languages.join(', ') || 'none detected'}`,
|
|
310
|
+
...s.notes.map(n => `- ${n}`),
|
|
311
|
+
body,
|
|
312
|
+
Object.keys(s.proposed).length > 0
|
|
313
|
+
? 'Show this to the user and, if they agree, call write_config with it. Do not write it unasked.'
|
|
314
|
+
: '',
|
|
315
|
+
]
|
|
316
|
+
.filter(Boolean)
|
|
317
|
+
.join('\n'),
|
|
318
|
+
structured: { languages: s.languages, proposed: s.proposed, notes: s.notes },
|
|
319
|
+
};
|
|
320
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type Ecosystem = 'npm' | 'pypi';
|
|
2
|
+
/**
|
|
3
|
+
* `avoid` — do not add it (does not exist, no such version, known serious advisory)
|
|
4
|
+
* `review` — a person should look first (new, rarely used, deprecated, install script…)
|
|
5
|
+
* `ok` — nothing found
|
|
6
|
+
* `unknown` — a lookup failed, so nothing can be said
|
|
7
|
+
*/
|
|
8
|
+
export type Verdict = 'avoid' | 'review' | 'ok' | 'unknown';
|
|
9
|
+
export interface DependencyReport {
|
|
10
|
+
name: string;
|
|
11
|
+
ecosystem: Ecosystem;
|
|
12
|
+
version?: string;
|
|
13
|
+
verdict: Verdict;
|
|
14
|
+
reasons: string[];
|
|
15
|
+
facts: Record<string, string | number | boolean>;
|
|
16
|
+
}
|
|
17
|
+
/** The slice of a fetch the checks need; injectable so tests never touch the network. */
|
|
18
|
+
export type FetchJson = (url: string, init?: RequestInit) => Promise<{
|
|
19
|
+
ok: boolean;
|
|
20
|
+
status: number;
|
|
21
|
+
body?: unknown;
|
|
22
|
+
error?: string;
|
|
23
|
+
}>;
|
|
24
|
+
/** The famous package `name` is suspiciously close to, if any. A name that *is* the famous one is not a match. */
|
|
25
|
+
export declare function lookalikeOf(name: string, ecosystem: Ecosystem): string | undefined;
|
|
26
|
+
/** Is `name` safe to add? Never throws: every failure becomes part of the verdict. */
|
|
27
|
+
export declare function checkDependency(name: string, opts?: {
|
|
28
|
+
version?: string;
|
|
29
|
+
ecosystem?: Ecosystem;
|
|
30
|
+
fetchJson?: FetchJson;
|
|
31
|
+
}): Promise<DependencyReport>;
|
|
32
|
+
/** The report as the text an agent reads. */
|
|
33
|
+
export declare function formatDependency(r: DependencyReport): string;
|