pr-checkmate 3.3.0 → 4.0.1
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 +59 -4
- package/cspell.json +1 -0
- package/dist/cjs/agent/hook.d.ts +38 -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 +21 -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/diff-smell.js +3 -1
- 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 +27 -0
- package/dist/cjs/core/checks/languages/locate.d.ts +7 -0
- package/dist/cjs/core/checks/languages/locate.js +68 -0
- package/dist/cjs/core/checks/languages/prettier.js +15 -2
- package/dist/cjs/core/checks/languages/typecheck.js +4 -1
- 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 +15 -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 +34 -0
- package/dist/cjs/mcp/dependency.js +413 -0
- package/dist/cjs/mcp/protocol.d.ts +91 -0
- package/dist/cjs/mcp/protocol.js +181 -0
- package/dist/cjs/mcp/review.d.ts +64 -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 +618 -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 +38 -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 +21 -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/diff-smell.js +3 -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/lint.js +28 -1
- package/dist/esm/core/checks/languages/locate.d.ts +7 -0
- package/dist/esm/core/checks/languages/locate.js +60 -0
- package/dist/esm/core/checks/languages/prettier.js +15 -2
- package/dist/esm/core/checks/languages/typecheck.js +4 -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 +15 -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 +34 -0
- package/dist/esm/mcp/dependency.js +408 -0
- package/dist/esm/mcp/protocol.d.ts +91 -0
- package/dist/esm/mcp/protocol.js +175 -0
- package/dist/esm/mcp/review.d.ts +64 -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 +611 -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 +7 -3
- package/pr-checkmate.schema.json +35 -0
- package/src/config/cspell.json +1 -0
- package/src/config/pr-checkmate.schema.json +35 -0
package/LICENSE
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
PROPRIETARY SOFTWARE LICENSE
|
|
2
|
+
SPDX-License-Identifier: LicenseRef-PR-Checkmate-Proprietary
|
|
2
3
|
All Rights Reserved
|
|
3
4
|
|
|
4
5
|
Copyright (c) 2025-2026 Artur Polishchuk. All Rights Reserved.
|
|
@@ -62,9 +63,13 @@ This grant does not include, and RESTRICTIONS 1 and 3 through 7 above
|
|
|
62
63
|
continue to apply in full to, any right to: copy or redistribute the
|
|
63
64
|
Distributed Package outside of standard package-manager installation; modify,
|
|
64
65
|
decompile, or reverse engineer it; or access, copy, or create derivative
|
|
65
|
-
works from any source code not included in the published npm tarball.
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
works from any source code not included in the published npm tarball.
|
|
67
|
+
|
|
68
|
+
The grant above is perpetual and irrevocable for every version of the
|
|
69
|
+
Distributed Package published under these terms, including after the Owner
|
|
70
|
+
stops publishing. The Owner may release future versions under different
|
|
71
|
+
terms, including paid terms; such terms apply only to those future versions
|
|
72
|
+
and never to a version already published.
|
|
68
73
|
|
|
69
74
|
================================================================================
|
|
70
75
|
PUBLIC MIRROR EXCEPTION
|
package/README.md
CHANGED
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
> A security-first PR gate for CI: secret scanning, dependency vulnerability checks, and
|
|
16
16
|
> a CycloneDX SBOM plus an independent
|
|
17
17
|
> [VirusTotal scan](https://github.com/Mybono/pr_checkmate_public/releases/latest) of the
|
|
18
|
-
> exact published tarball on every release — plus
|
|
18
|
+
> exact published tarball on every release — plus 54 code-quality checks across 11
|
|
19
19
|
> languages, bundled. One npm package, one `pr-checkmate.json`, no per-language toolchain
|
|
20
|
-
> setup for the bundled ones.
|
|
20
|
+
> setup for the bundled ones. Also an [MCP server](#ai-agents-mcp): your AI coding agent runs the
|
|
21
|
+
> same gate while it writes code.
|
|
21
22
|
|
|
22
23
|
Language is auto-detected. Checks run in parallel phases, results post as one PR comment
|
|
23
24
|
that updates in place, and formatter fixes are committed back to the branch.
|
|
@@ -64,6 +65,59 @@ Commit both and it runs on the next pull request. Both are regenerated on every
|
|
|
64
65
|
line, which three lines are load-bearing, adopting it on a codebase that is not clean
|
|
65
66
|
yet, and the setup step each runner-dependency language needs.
|
|
66
67
|
|
|
68
|
+
## AI agents (MCP)
|
|
69
|
+
|
|
70
|
+
PR CheckMate is also an [MCP](https://modelcontextprotocol.io) server, so Claude Code, Cursor or any
|
|
71
|
+
other MCP client can run the gate **while it writes code**, not after the pull request is open. The
|
|
72
|
+
agent reads the repository's rules first, checks its own changes as it goes — uncommitted files
|
|
73
|
+
included, nothing has to be staged — and runs the full gate once before it says it is done.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install -g pr-checkmate
|
|
77
|
+
claude mcp add --scope user pr-checkmate -- pr-checkmate mcp # once, for every project
|
|
78
|
+
npx pr-checkmate agent-setup # per repository, below
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Then check it: `claude mcp list` shows `pr-checkmate ✔ Connected`; sessions that were already open
|
|
82
|
+
need a restart. `claude` comes from the Claude Code CLI (an editor extension keeps its own copy off
|
|
83
|
+
your `PATH`). To stop Claude Code asking before every call, allow `mcp__pr-checkmate` in
|
|
84
|
+
`permissions.allow`
|
|
85
|
+
([details](https://github.com/Mybono/pr_checkmate_public/blob/main/documentation/ai-agents.md#after-installing)).
|
|
86
|
+
|
|
87
|
+
| Tool | The agent calls it |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `get_rules` | before writing — what blocks a PR, what only warns, your custom rules and banned imports |
|
|
90
|
+
| `check_changes` | while working — fast checks on what it has changed so far |
|
|
91
|
+
| `scan_secrets` | before committing config, environment or credential files |
|
|
92
|
+
| `apply_fixes` | instead of fixing style by hand — formats and autofixes only the files it changed |
|
|
93
|
+
| `check_dependency` | before adding a package — does it exist, advisories (malware included), look-alike names |
|
|
94
|
+
| `verify` | once, when it believes the work is done — every check, the slow ones included |
|
|
95
|
+
| `suggest_config` / `write_config` | set pr-checkmate up for *this* repository: propose, then write once you agree |
|
|
96
|
+
|
|
97
|
+
`agent-setup` registers the server in `.mcp.json`, adds a short instructions block to `AGENTS.md` /
|
|
98
|
+
`CLAUDE.md` telling the agent *when* to call each tool, and — in Claude Code — installs a hook that
|
|
99
|
+
checks staged changes on `git commit` and blocks the commit with the findings. It only adds, and is
|
|
100
|
+
safe to re-run. While a project has no `pr-checkmate` entry in `.mcp.json`, the summary printed by
|
|
101
|
+
`npx pr-checkmate all` ends with an `AI AGENT:` line pointing at the command (not shown in CI), and
|
|
102
|
+
`init` adds it to the `commands` block of an existing `pr-checkmate.json`.
|
|
103
|
+
|
|
104
|
+
**Limits.** An agent can ignore a tool, so nothing here guarantees that `npx pr-checkmate all` finds
|
|
105
|
+
nothing: the CI gate stays the authority. Two tools write files, `apply_fixes` and `write_config`;
|
|
106
|
+
the rest only read. The protocol is implemented in this package, with no MCP SDK or HTTP framework
|
|
107
|
+
behind it.
|
|
108
|
+
|
|
109
|
+
**Turn it off or narrow it.** The `mcp` block of `pr-checkmate.json` controls what the agent sees,
|
|
110
|
+
from its next request and without a restart:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
"mcp": { "enabled": true, "disabledTools": ["check_dependency"], "readOnly": false }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`enabled: false` leaves the server connected with nothing to offer, `disabledTools` hides tools by
|
|
117
|
+
name, and `readOnly: true` hides the two that write files.
|
|
118
|
+
|
|
119
|
+
[AI agents guide →](https://github.com/Mybono/pr_checkmate_public/blob/main/documentation/ai-agents.md)
|
|
120
|
+
|
|
67
121
|
## Language Support
|
|
68
122
|
|
|
69
123
|
Bundled languages need nothing on the runner. Runner-dependency languages need their
|
|
@@ -88,7 +142,7 @@ every language.
|
|
|
88
142
|
|
|
89
143
|
## Checks
|
|
90
144
|
|
|
91
|
-
|
|
145
|
+
55 checks, run in parallel within each phase:
|
|
92
146
|
|
|
93
147
|
| Group | Count | What it covers |
|
|
94
148
|
|---|---|---|
|
|
@@ -143,5 +197,6 @@ affect it.
|
|
|
143
197
|
## License
|
|
144
198
|
|
|
145
199
|
LicenseRef Proprietary © 2025-2026 Artur Polishchuk. Free to install and run via the
|
|
146
|
-
published npm package
|
|
200
|
+
published npm package, and that right is irrevocable for every published version (see
|
|
201
|
+
[LICENSE](LICENSE) for the exact grant); the source is not
|
|
147
202
|
open-source and is not licensed for copying, modification, or redistribution.
|
package/cspell.json
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ReviewScope } from '../mcp/review.js';
|
|
2
|
+
export declare const HOOK_COMMAND = "pr-checkmate hook";
|
|
3
|
+
/**
|
|
4
|
+
* Which commits in a shell line need a check, and what each will record.
|
|
5
|
+
*
|
|
6
|
+
* `git commit -a` / `-am` stages tracked modifications as it commits, so the index
|
|
7
|
+
* at hook time does not yet hold what will be recorded; those are checked as a
|
|
8
|
+
* working-tree snapshot instead, which errs towards reviewing a little more.
|
|
9
|
+
*
|
|
10
|
+
* @returns the scope to review, or `undefined` when the line commits nothing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function commitScopeFor(command: string): ReviewScope | undefined;
|
|
13
|
+
interface HookVerdict {
|
|
14
|
+
/** 2 blocks the tool call, 0 lets it through. */
|
|
15
|
+
exitCode: 0 | 2;
|
|
16
|
+
/** Shown to the model when blocked. */
|
|
17
|
+
stderr?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* What runs after a file edit. Only checks that are a pattern match over the added
|
|
21
|
+
* lines: they finish in well under a second, and a hook that waits on a type
|
|
22
|
+
* checker after every keystroke-sized edit would make the agent unusable.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately not here: Diff Smells and Leftover Debug. Mid-edit, a `console.log`
|
|
25
|
+
* or a TODO is how work is done, and a nag for each one teaches the agent to
|
|
26
|
+
* ignore the hook. They are reported when it asks for `check_changes`, at the point
|
|
27
|
+
* where they would actually be a mistake. Secrets wait for the commit gate for the
|
|
28
|
+
* same reason of cost: the scanner starts a process.
|
|
29
|
+
*/
|
|
30
|
+
export declare const EDIT_CHECKS: readonly string[];
|
|
31
|
+
/**
|
|
32
|
+
* Decide what to do with the call a harness is about to make.
|
|
33
|
+
*
|
|
34
|
+
* @param stdin Raw hook payload, as the harness wrote it.
|
|
35
|
+
* @param fallbackCwd Used when the payload carries no directory.
|
|
36
|
+
*/
|
|
37
|
+
export declare function evaluateHook(stdin: string, fallbackCwd: string): Promise<HookVerdict>;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
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.EDIT_CHECKS = exports.HOOK_COMMAND = void 0;
|
|
7
|
+
exports.commitScopeFor = commitScopeFor;
|
|
8
|
+
exports.evaluateHook = evaluateHook;
|
|
9
|
+
/**
|
|
10
|
+
* The commit gate for AI coding agents — a Claude Code `PreToolUse` hook.
|
|
11
|
+
*
|
|
12
|
+
* An MCP tool is a request: the agent calls `check_changes` if it remembers to.
|
|
13
|
+
* A hook is a fact: the harness runs it whether the agent remembers or not. So the
|
|
14
|
+
* server makes conforming code easy and this makes committing nonconforming code
|
|
15
|
+
* hard — when the agent runs `git commit`, the staged changes are checked first
|
|
16
|
+
* and a failure comes back as the reason the commit did not happen.
|
|
17
|
+
*
|
|
18
|
+
* Exit code 2 is the harness's "block this call and show stderr to the model".
|
|
19
|
+
* Every other outcome exits 0, **including our own failures**: a gate that cannot
|
|
20
|
+
* run must not become a reason the agent cannot work.
|
|
21
|
+
*/
|
|
22
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
23
|
+
const review_js_1 = require("../mcp/review.js");
|
|
24
|
+
const tools_js_1 = require("../mcp/tools.js");
|
|
25
|
+
exports.HOOK_COMMAND = 'pr-checkmate hook';
|
|
26
|
+
/** Separators that begin a new command inside one shell line. */
|
|
27
|
+
const SEGMENT_SPLIT = /&&|\|\||[;|\n]/;
|
|
28
|
+
/** Global git options that take a value as the next token. */
|
|
29
|
+
const OPTIONS_WITH_VALUE = new Set(['-C', '-c', '--git-dir', '--work-tree']);
|
|
30
|
+
/**
|
|
31
|
+
* The git subcommand a shell segment runs, skipping global options
|
|
32
|
+
* (`git -C repo -c user.name=x commit` → `commit`).
|
|
33
|
+
*/
|
|
34
|
+
function gitSubcommand(tokens) {
|
|
35
|
+
if (tokens[0] !== 'git')
|
|
36
|
+
return undefined;
|
|
37
|
+
let i = 1;
|
|
38
|
+
while (i < tokens.length && tokens[i].startsWith('-')) {
|
|
39
|
+
i += OPTIONS_WITH_VALUE.has(tokens[i]) ? 2 : 1;
|
|
40
|
+
}
|
|
41
|
+
return i < tokens.length ? { sub: tokens[i], rest: tokens.slice(i + 1) } : undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Which commits in a shell line need a check, and what each will record.
|
|
45
|
+
*
|
|
46
|
+
* `git commit -a` / `-am` stages tracked modifications as it commits, so the index
|
|
47
|
+
* at hook time does not yet hold what will be recorded; those are checked as a
|
|
48
|
+
* working-tree snapshot instead, which errs towards reviewing a little more.
|
|
49
|
+
*
|
|
50
|
+
* @returns the scope to review, or `undefined` when the line commits nothing.
|
|
51
|
+
*/
|
|
52
|
+
function commitScopeFor(command) {
|
|
53
|
+
let scope;
|
|
54
|
+
for (const segment of command.split(SEGMENT_SPLIT)) {
|
|
55
|
+
const git = gitSubcommand(segment.trim().split(/\s+/));
|
|
56
|
+
if (git?.sub !== 'commit')
|
|
57
|
+
continue;
|
|
58
|
+
const stagesAll = git.rest.some(t => t === '--all' || /^-[A-Za-z]*a[A-Za-z]*$/.test(t));
|
|
59
|
+
if (stagesAll)
|
|
60
|
+
return 'worktree';
|
|
61
|
+
scope = 'staged';
|
|
62
|
+
}
|
|
63
|
+
return scope;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* What runs after a file edit. Only checks that are a pattern match over the added
|
|
67
|
+
* lines: they finish in well under a second, and a hook that waits on a type
|
|
68
|
+
* checker after every keystroke-sized edit would make the agent unusable.
|
|
69
|
+
*
|
|
70
|
+
* Deliberately not here: Diff Smells and Leftover Debug. Mid-edit, a `console.log`
|
|
71
|
+
* or a TODO is how work is done, and a nag for each one teaches the agent to
|
|
72
|
+
* ignore the hook. They are reported when it asks for `check_changes`, at the point
|
|
73
|
+
* where they would actually be a mistake. Secrets wait for the commit gate for the
|
|
74
|
+
* same reason of cost: the scanner starts a process.
|
|
75
|
+
*/
|
|
76
|
+
exports.EDIT_CHECKS = [
|
|
77
|
+
'Merge Conflict',
|
|
78
|
+
'Diff Security',
|
|
79
|
+
'Banned Imports',
|
|
80
|
+
'Custom Rules',
|
|
81
|
+
'Migration Safety',
|
|
82
|
+
'Dockerfile Security',
|
|
83
|
+
'Workflow Security',
|
|
84
|
+
];
|
|
85
|
+
/** Tools that write a file and say which one. */
|
|
86
|
+
const EDIT_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
87
|
+
/** More than this and a slow disk or a huge repository has made the hook a cost, not a help. */
|
|
88
|
+
const EDIT_BUDGET_MS = 10000;
|
|
89
|
+
/** Paths nobody wrote by hand: reviewing them only produces noise. */
|
|
90
|
+
const GENERATED = /(^|\/)(node_modules|dist|build|\.git|vendor)\//;
|
|
91
|
+
/**
|
|
92
|
+
* Review one file the agent just wrote and tell it about problems in *that file*.
|
|
93
|
+
*
|
|
94
|
+
* Runs after the edit, so it cannot block anything: exit 2 here only hands the
|
|
95
|
+
* message to the model. The review sees the whole working tree (the checks are
|
|
96
|
+
* diff-based) but only findings that point into the edited file are returned, so a
|
|
97
|
+
* problem the agent has not touched and cannot be blamed for stays out of its way.
|
|
98
|
+
*/
|
|
99
|
+
async function evaluateEdit(payload, fallbackCwd) {
|
|
100
|
+
const target = payload.tool_input?.file_path;
|
|
101
|
+
if (typeof target !== 'string' || !target)
|
|
102
|
+
return { exitCode: 0 };
|
|
103
|
+
const abs = node_path_1.default.resolve(payload.cwd ?? fallbackCwd, target);
|
|
104
|
+
const root = await (0, review_js_1.resolveRepoRoot)(node_path_1.default.dirname(abs));
|
|
105
|
+
if (!root)
|
|
106
|
+
return { exitCode: 0 };
|
|
107
|
+
const rel = node_path_1.default.relative(root, abs).split(node_path_1.default.sep).join('/');
|
|
108
|
+
if (rel.startsWith('..') || GENERATED.test(rel))
|
|
109
|
+
return { exitCode: 0 };
|
|
110
|
+
const timedOut = Symbol('timeout');
|
|
111
|
+
let timer;
|
|
112
|
+
const outcome = await Promise.race([
|
|
113
|
+
(0, review_js_1.review)({ cwd: root, scope: 'worktree', only: [...exports.EDIT_CHECKS] }),
|
|
114
|
+
new Promise(resolve => {
|
|
115
|
+
timer = setTimeout(() => resolve(timedOut), EDIT_BUDGET_MS);
|
|
116
|
+
}),
|
|
117
|
+
]);
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
if (outcome === timedOut)
|
|
120
|
+
return { exitCode: 0 };
|
|
121
|
+
const lines = [];
|
|
122
|
+
for (const result of outcome.report.results) {
|
|
123
|
+
for (const f of result.findings ?? []) {
|
|
124
|
+
if (f.file !== rel)
|
|
125
|
+
continue;
|
|
126
|
+
lines.push(` ${f.file}${f.line ? `:${f.line}` : ''} [${result.name}] ${f.rule}${f.snippet ? ` — ${f.snippet}` : ''}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (lines.length === 0)
|
|
130
|
+
return { exitCode: 0 };
|
|
131
|
+
return {
|
|
132
|
+
exitCode: 2,
|
|
133
|
+
stderr: `pr-checkmate found problems in ${rel} — fix them in this file:\n${lines.join('\n')}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Decide what to do with the call a harness is about to make.
|
|
138
|
+
*
|
|
139
|
+
* @param stdin Raw hook payload, as the harness wrote it.
|
|
140
|
+
* @param fallbackCwd Used when the payload carries no directory.
|
|
141
|
+
*/
|
|
142
|
+
async function evaluateHook(stdin, fallbackCwd) {
|
|
143
|
+
try {
|
|
144
|
+
const payload = JSON.parse(stdin);
|
|
145
|
+
if (payload.hook_event_name === 'PostToolUse') {
|
|
146
|
+
return payload.tool_name && EDIT_TOOLS.has(payload.tool_name)
|
|
147
|
+
? await evaluateEdit(payload, fallbackCwd)
|
|
148
|
+
: { exitCode: 0 };
|
|
149
|
+
}
|
|
150
|
+
const command = payload.tool_input?.command;
|
|
151
|
+
if ((payload.tool_name && payload.tool_name !== 'Bash') || typeof command !== 'string') {
|
|
152
|
+
return { exitCode: 0 };
|
|
153
|
+
}
|
|
154
|
+
const scope = commitScopeFor(command);
|
|
155
|
+
if (!scope)
|
|
156
|
+
return { exitCode: 0 };
|
|
157
|
+
const root = await (0, review_js_1.resolveRepoRoot)(node_path_1.default.resolve(payload.cwd ?? fallbackCwd));
|
|
158
|
+
if (!root)
|
|
159
|
+
return { exitCode: 0 };
|
|
160
|
+
const outcome = await (0, review_js_1.review)({ cwd: root, scope });
|
|
161
|
+
if (outcome.report.ok)
|
|
162
|
+
return { exitCode: 0 };
|
|
163
|
+
const { text } = (0, tools_js_1.formatReport)(outcome.report, { ...outcome, advice: '' });
|
|
164
|
+
return {
|
|
165
|
+
exitCode: 2,
|
|
166
|
+
stderr: `Commit blocked by pr-checkmate — the pull request gate would fail on these changes.\n${text}\nFix the findings, stage the fixes, and commit again.`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
return {
|
|
171
|
+
exitCode: 0,
|
|
172
|
+
stderr: `pr-checkmate hook skipped, could not complete: ${err instanceof Error ? err.message : String(err)}`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const MCP_SERVER_NAME = "pr-checkmate";
|
|
2
|
+
/**
|
|
3
|
+
* Whether the project's `.mcp.json` already registers the server. A missing or
|
|
4
|
+
* broken file counts as no.
|
|
5
|
+
*
|
|
6
|
+
* Kept apart from `setup.ts` on purpose: the run summary asks this question, and
|
|
7
|
+
* importing `setup.ts` from there closes a loop through the hook and the MCP
|
|
8
|
+
* review back into the run that is printing.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isMcpRegistered(cwd: string): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
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.MCP_SERVER_NAME = void 0;
|
|
7
|
+
exports.isMcpRegistered = isMcpRegistered;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
exports.MCP_SERVER_NAME = 'pr-checkmate';
|
|
11
|
+
/**
|
|
12
|
+
* Whether the project's `.mcp.json` already registers the server. A missing or
|
|
13
|
+
* broken file counts as no.
|
|
14
|
+
*
|
|
15
|
+
* Kept apart from `setup.ts` on purpose: the run summary asks this question, and
|
|
16
|
+
* importing `setup.ts` from there closes a loop through the hook and the MCP
|
|
17
|
+
* review back into the run that is printing.
|
|
18
|
+
*/
|
|
19
|
+
function isMcpRegistered(cwd) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(cwd, '.mcp.json'), 'utf-8'));
|
|
22
|
+
const servers = parsed?.mcpServers;
|
|
23
|
+
return typeof servers === 'object' && servers !== null && exports.MCP_SERVER_NAME in servers;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Whether `name` resolves to an executable on PATH — the question the client will ask when it starts the server. */
|
|
2
|
+
export declare function isOnPath(name: string, env?: NodeJS.ProcessEnv): boolean;
|
|
3
|
+
export declare const BLOCK_START = "<!-- pr-checkmate:agent-rules:start -->";
|
|
4
|
+
export declare const BLOCK_END = "<!-- pr-checkmate:agent-rules:end -->";
|
|
5
|
+
type SetupStatus = 'created' | 'updated' | 'unchanged' | 'skipped';
|
|
6
|
+
interface SetupAction {
|
|
7
|
+
file: string;
|
|
8
|
+
status: SetupStatus;
|
|
9
|
+
detail: string;
|
|
10
|
+
}
|
|
11
|
+
interface SetupOptions {
|
|
12
|
+
/** Install the commit-gate hook. Default true. */
|
|
13
|
+
hook?: boolean;
|
|
14
|
+
/** Whether the CLI is on PATH. Detected when omitted; injectable for tests. */
|
|
15
|
+
cliOnPath?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Configure a repository for agents. Throws, naming the file, if a JSON file cannot be read safely. */
|
|
18
|
+
export declare function setupAgent(cwd: string, opts?: SetupOptions): SetupAction[];
|
|
19
|
+
/** Print what changed and the one step the repository cannot do for the user. */
|
|
20
|
+
export declare function reportSetup(actions: SetupAction[]): void;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,229 @@
|
|
|
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.BLOCK_END = exports.BLOCK_START = void 0;
|
|
7
|
+
exports.isOnPath = isOnPath;
|
|
8
|
+
exports.setupAgent = setupAgent;
|
|
9
|
+
exports.reportSetup = reportSetup;
|
|
10
|
+
// cspell:ignore PATHEXT
|
|
11
|
+
/**
|
|
12
|
+
* `pr-checkmate agent-setup` — wire a repository so AI agents use the gate by default.
|
|
13
|
+
*
|
|
14
|
+
* Three pieces, each covering a failure of the one before:
|
|
15
|
+
*
|
|
16
|
+
* 1. `.mcp.json` registers the server, so the tools exist;
|
|
17
|
+
* 2. an instructions block in `AGENTS.md` / `CLAUDE.md` tells the agent *when* to
|
|
18
|
+
* call them — tools an agent was never told to use mostly go unused;
|
|
19
|
+
* 3. a Claude Code hook checks staged changes on `git commit`, so the gate holds
|
|
20
|
+
* even when the agent forgets steps 1 and 2.
|
|
21
|
+
*
|
|
22
|
+
* Idempotent, and it only ever adds: an existing entry is left as the user wrote
|
|
23
|
+
* it, a block we own is refreshed in place, and a settings file we cannot parse is
|
|
24
|
+
* reported instead of overwritten.
|
|
25
|
+
*/
|
|
26
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
27
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
28
|
+
const index_js_1 = require("../utils/index.js");
|
|
29
|
+
const package_version_js_1 = require("../utils/package-version.js");
|
|
30
|
+
const hook_js_1 = require("./hook.js");
|
|
31
|
+
const registration_js_1 = require("./registration.js");
|
|
32
|
+
const CLI = 'pr-checkmate';
|
|
33
|
+
/** Whether `name` resolves to an executable on PATH — the question the client will ask when it starts the server. */
|
|
34
|
+
function isOnPath(name, env = process.env) {
|
|
35
|
+
const exts = process.platform === 'win32' ? (env.PATHEXT ?? '.EXE;.CMD').split(';') : [''];
|
|
36
|
+
return (env.PATH ?? '')
|
|
37
|
+
.split(node_path_1.default.delimiter)
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.some(dir => exts.some(ext => {
|
|
40
|
+
try {
|
|
41
|
+
node_fs_1.default.accessSync(node_path_1.default.join(dir, name + ext), node_fs_1.default.constants.X_OK);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* How a client should start the server. A bare `pr-checkmate` is only right when the
|
|
51
|
+
* CLI is installed globally; otherwise the entry would be written, look fine, and
|
|
52
|
+
* fail silently the first time a client tried to use it. `npx` always resolves.
|
|
53
|
+
*/
|
|
54
|
+
function mcpEntry(onPath) {
|
|
55
|
+
// Pinned to the version writing the entry: an unpinned `npx -y` runs whatever is
|
|
56
|
+
// published next on every contributor's machine, which is exactly what the Agent
|
|
57
|
+
// Instructions check flags in other people's `.mcp.json`.
|
|
58
|
+
return onPath
|
|
59
|
+
? { command: CLI, args: ['mcp'] }
|
|
60
|
+
: { command: 'npx', args: ['-y', `${CLI}@${(0, package_version_js_1.packageVersion)()}`, 'mcp'] };
|
|
61
|
+
}
|
|
62
|
+
exports.BLOCK_START = '<!-- pr-checkmate:agent-rules:start -->';
|
|
63
|
+
exports.BLOCK_END = '<!-- pr-checkmate:agent-rules:end -->';
|
|
64
|
+
/** What the agent is told. Kept short: it is read on every session. */
|
|
65
|
+
const AGENT_RULES_BLOCK = `${exports.BLOCK_START}
|
|
66
|
+
## Code quality gate (pr-checkmate)
|
|
67
|
+
|
|
68
|
+
Pull requests in this repository are checked by pr-checkmate. Write code that passes the first time:
|
|
69
|
+
|
|
70
|
+
1. Before writing code, call \`get_rules\` on the \`pr-checkmate\` MCP server to see what the gate enforces.
|
|
71
|
+
2. While working, and before every commit, call \`check_changes\`. Fix every ❌ finding and call it again until it passes.
|
|
72
|
+
3. Before adding any package, call \`check_dependency\` — it catches names that do not exist, versions with known advisories and look-alike names. Before committing configuration, environment or credential files, call \`scan_secrets\`.
|
|
73
|
+
4. When you believe the work is done, call \`verify\` once (it also runs the slow checks) and fix what it reports before telling the user you are finished.
|
|
74
|
+
5. Fix findings in the code. Do not silence them with \`eslint-disable\`, \`--no-verify\`, or by editing \`pr-checkmate.json\` unless the user asks you to.
|
|
75
|
+
|
|
76
|
+
If the MCP server is unavailable, run \`pr-checkmate precommit\` before committing.
|
|
77
|
+
${exports.BLOCK_END}
|
|
78
|
+
`;
|
|
79
|
+
function isRecord(value) {
|
|
80
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
81
|
+
}
|
|
82
|
+
/** Read a JSON object, `{}` when the file is absent, and an error that names the file when it is not JSON. */
|
|
83
|
+
function readJsonObject(file) {
|
|
84
|
+
if (!node_fs_1.default.existsSync(file))
|
|
85
|
+
return { data: {}, existed: false };
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(file, 'utf-8'));
|
|
88
|
+
if (isRecord(parsed))
|
|
89
|
+
return { data: parsed, existed: true };
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// fall through to the shared message
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`${file} is not a valid JSON object — fix it by hand, then run agent-setup again`);
|
|
95
|
+
}
|
|
96
|
+
function writeJson(file, data) {
|
|
97
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(file), { recursive: true });
|
|
98
|
+
node_fs_1.default.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
|
|
99
|
+
}
|
|
100
|
+
/** Register the MCP server in the project's `.mcp.json`. */
|
|
101
|
+
function setupMcpConfig(cwd, onPath) {
|
|
102
|
+
const file = node_path_1.default.join(cwd, '.mcp.json');
|
|
103
|
+
const { data, existed } = readJsonObject(file);
|
|
104
|
+
const servers = isRecord(data.mcpServers) ? data.mcpServers : {};
|
|
105
|
+
const entry = mcpEntry(onPath);
|
|
106
|
+
if (registration_js_1.MCP_SERVER_NAME in servers) {
|
|
107
|
+
return {
|
|
108
|
+
file: '.mcp.json',
|
|
109
|
+
status: 'unchanged',
|
|
110
|
+
detail: `"${registration_js_1.MCP_SERVER_NAME}" already registered`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
writeJson(file, { ...data, mcpServers: { ...servers, [registration_js_1.MCP_SERVER_NAME]: entry } });
|
|
114
|
+
return {
|
|
115
|
+
file: '.mcp.json',
|
|
116
|
+
status: existed ? 'updated' : 'created',
|
|
117
|
+
detail: `registered "${registration_js_1.MCP_SERVER_NAME}" (runs: ${entry.command} ${entry.args.join(' ')})`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Insert or refresh our block in one instructions file. */
|
|
121
|
+
function upsertBlock(file, name) {
|
|
122
|
+
const existing = node_fs_1.default.existsSync(file) ? node_fs_1.default.readFileSync(file, 'utf-8') : undefined;
|
|
123
|
+
if (existing === undefined) {
|
|
124
|
+
node_fs_1.default.writeFileSync(file, `${AGENT_RULES_BLOCK}`);
|
|
125
|
+
return { file: name, status: 'created', detail: 'wrote the agent instructions' };
|
|
126
|
+
}
|
|
127
|
+
const start = existing.indexOf(exports.BLOCK_START);
|
|
128
|
+
const end = existing.indexOf(exports.BLOCK_END);
|
|
129
|
+
let next;
|
|
130
|
+
if (start !== -1 && end > start) {
|
|
131
|
+
next =
|
|
132
|
+
existing.slice(0, start) +
|
|
133
|
+
AGENT_RULES_BLOCK.trimEnd() +
|
|
134
|
+
existing.slice(end + exports.BLOCK_END.length);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
next = `${existing.trimEnd()}\n\n${AGENT_RULES_BLOCK}`;
|
|
138
|
+
}
|
|
139
|
+
if (next === existing)
|
|
140
|
+
return { file: name, status: 'unchanged', detail: 'instructions already current' };
|
|
141
|
+
node_fs_1.default.writeFileSync(file, next);
|
|
142
|
+
return { file: name, status: 'updated', detail: 'added the agent instructions' };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Put the instructions where agents read them. `AGENTS.md` is the shared
|
|
146
|
+
* convention; `CLAUDE.md` is what Claude Code reads. Both are updated when both
|
|
147
|
+
* exist, and `AGENTS.md` is created when neither does.
|
|
148
|
+
*/
|
|
149
|
+
function setupInstructions(cwd) {
|
|
150
|
+
const targets = ['AGENTS.md', 'CLAUDE.md'].filter(name => node_fs_1.default.existsSync(node_path_1.default.join(cwd, name)));
|
|
151
|
+
if (targets.length === 0)
|
|
152
|
+
targets.push('AGENTS.md');
|
|
153
|
+
return targets.map(name => upsertBlock(node_path_1.default.join(cwd, name), name));
|
|
154
|
+
}
|
|
155
|
+
/** The two hook entries: the commit gate before `git commit`, the file check after an edit. */
|
|
156
|
+
const HOOK_ENTRIES = [
|
|
157
|
+
{ event: 'PreToolUse', matcher: 'Bash' },
|
|
158
|
+
{ event: 'PostToolUse', matcher: 'Edit|Write|MultiEdit' },
|
|
159
|
+
];
|
|
160
|
+
/** Whether `entries` already holds our hook command. */
|
|
161
|
+
function hasOurHook(entries) {
|
|
162
|
+
return entries.some(entry => isRecord(entry) &&
|
|
163
|
+
Array.isArray(entry.hooks) &&
|
|
164
|
+
entry.hooks.some(h => isRecord(h) && h.command === hook_js_1.HOOK_COMMAND));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Add the commit gate and the per-edit check to `.claude/settings.json`, each only
|
|
168
|
+
* if it is not there yet. Both run the same command; which one fires is decided by
|
|
169
|
+
* the event the harness reports.
|
|
170
|
+
*/
|
|
171
|
+
function setupHook(cwd) {
|
|
172
|
+
const rel = node_path_1.default.join('.claude', 'settings.json');
|
|
173
|
+
const file = node_path_1.default.join(cwd, rel);
|
|
174
|
+
const { data, existed } = readJsonObject(file);
|
|
175
|
+
const hooks = isRecord(data.hooks) ? { ...data.hooks } : {};
|
|
176
|
+
let added = 0;
|
|
177
|
+
for (const { event, matcher } of HOOK_ENTRIES) {
|
|
178
|
+
const current = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
179
|
+
if (hasOurHook(current))
|
|
180
|
+
continue;
|
|
181
|
+
hooks[event] = [...current, { matcher, hooks: [{ type: 'command', command: hook_js_1.HOOK_COMMAND }] }];
|
|
182
|
+
added++;
|
|
183
|
+
}
|
|
184
|
+
if (added === 0)
|
|
185
|
+
return { file: rel, status: 'unchanged', detail: 'hooks already installed' };
|
|
186
|
+
writeJson(file, { ...data, hooks });
|
|
187
|
+
return {
|
|
188
|
+
file: rel,
|
|
189
|
+
status: existed ? 'updated' : 'created',
|
|
190
|
+
detail: 'git commit runs the gate on staged changes; each file edit is checked for security problems',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/** Configure a repository for agents. Throws, naming the file, if a JSON file cannot be read safely. */
|
|
194
|
+
function setupAgent(cwd, opts = {}) {
|
|
195
|
+
const onPath = opts.cliOnPath ?? isOnPath(CLI);
|
|
196
|
+
const settings = node_path_1.default.join('.claude', 'settings.json');
|
|
197
|
+
const actions = [setupMcpConfig(cwd, onPath), ...setupInstructions(cwd)];
|
|
198
|
+
if (opts.hook === false) {
|
|
199
|
+
actions.push({
|
|
200
|
+
file: settings,
|
|
201
|
+
status: 'skipped',
|
|
202
|
+
detail: 'commit gate not requested (--no-hook)',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else if (!onPath) {
|
|
206
|
+
// The hook runs on every Bash call the agent makes. Through `npx` that is a
|
|
207
|
+
// package resolution per call — a cost paid all session for a gate that only
|
|
208
|
+
// matters at commit time — so it waits for an install the hook can rely on.
|
|
209
|
+
actions.push({
|
|
210
|
+
file: settings,
|
|
211
|
+
status: 'skipped',
|
|
212
|
+
detail: `commit gate needs "${CLI}" on PATH — run "npm install -g ${CLI}", then agent-setup again`,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
actions.push(setupHook(cwd));
|
|
217
|
+
}
|
|
218
|
+
return actions;
|
|
219
|
+
}
|
|
220
|
+
/** Print what changed and the one step the repository cannot do for the user. */
|
|
221
|
+
function reportSetup(actions) {
|
|
222
|
+
for (const a of actions)
|
|
223
|
+
index_js_1.logger.info(`[agent-setup]: ${a.status.padEnd(9)} ${a.file} — ${a.detail}`);
|
|
224
|
+
index_js_1.logger.info('');
|
|
225
|
+
index_js_1.logger.info('[agent-setup]: Installing the CLI globally is recommended (faster start, enables the commit gate):');
|
|
226
|
+
index_js_1.logger.info('[agent-setup]: npm install -g pr-checkmate');
|
|
227
|
+
index_js_1.logger.info('[agent-setup]: To make it available in every project for Claude Code, run once:');
|
|
228
|
+
index_js_1.logger.info('[agent-setup]: claude mcp add --scope user pr-checkmate -- pr-checkmate mcp');
|
|
229
|
+
}
|