coderifts 3.1.0 → 3.3.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/README.md +235 -2
- package/bin/coderifts.js +77 -4
- package/dist/cli.js +4422 -469
- package/package.json +4 -3
- package/scripts/assert-guard-major.js +125 -0
package/README.md
CHANGED
|
@@ -94,6 +94,57 @@ is **never** treated as a silent NEW_ARTIFACT pass.
|
|
|
94
94
|
|
|
95
95
|
On success the command prints a **receipt reference** when the preflight path issues one.
|
|
96
96
|
|
|
97
|
+
### `coderifts deploy-gate`
|
|
98
|
+
|
|
99
|
+
Gate a **deploy** on the current `{ environment, artifact }` using a preflight
|
|
100
|
+
receipt (phase-1 advisory). The CD sibling of `publish-gate` / the merge-gate
|
|
101
|
+
App check. Composes the validated `deployGate` from `@coderifts/agent-guard` —
|
|
102
|
+
the CLI only binds env + artifact + receipt + observed CD enforcement. No
|
|
103
|
+
scoring is reimplemented here.
|
|
104
|
+
|
|
105
|
+
Phase 1 **reports** and exits **0** even on a gate deny, unless the step is
|
|
106
|
+
marked enforcing (`--enforce` or `CODERIFTS_DEPLOY_ENFORCE=true`), in which
|
|
107
|
+
case a failure exits **1**. Missing `--env` / `--artifact` is a usage error
|
|
108
|
+
(exit **1**). No receipt → pending (never a fabricated allow).
|
|
109
|
+
|
|
110
|
+
| Flag | Description | Default |
|
|
111
|
+
|------|-------------|---------|
|
|
112
|
+
| `--env <environment>` | Target environment (e.g. `production`, `staging`) | required |
|
|
113
|
+
| `--artifact <artifact_id>` | Immutable artifact identity (content digest or commit SHA) | required |
|
|
114
|
+
| `--receipt <file>` | Path to the deploy-scoped receipt JSON from preflight | none |
|
|
115
|
+
| `--json` | Binding result as JSON | `false` |
|
|
116
|
+
| `--enforce` | Attest ENFORCING and exit non-zero on a gate failure | `false` |
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
coderifts deploy-gate --env production --artifact "$SHA" --receipt receipt.json
|
|
120
|
+
coderifts deploy-gate --env production --artifact "$SHA" --receipt receipt.json --enforce
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `coderifts outcome <kind>`
|
|
124
|
+
|
|
125
|
+
Report a **post-hoc** observed outcome for a past `decision_id` to
|
|
126
|
+
`POST /api/v1/outcomes`. Records the **caller's assertion** (your deploy job
|
|
127
|
+
knows success/failure) — the command does **not** itself verify the deploy.
|
|
128
|
+
|
|
129
|
+
`kind` is a closed set: `deploy_succeeded`, `deploy_failed`, `rolled_back`,
|
|
130
|
+
`consumer_break_reported`, `remediation_verified_working`,
|
|
131
|
+
`false_positive_reported`, `other_reported`.
|
|
132
|
+
|
|
133
|
+
Requires a cloud API key (`coderifts login` or `CODERIFTS_API_KEY`). Reporter
|
|
134
|
+
is derived from the key. Exit **0** on success, **1** on usage / API error.
|
|
135
|
+
|
|
136
|
+
| Flag | Description | Default |
|
|
137
|
+
|------|-------------|---------|
|
|
138
|
+
| `--decision <decision_id>` | `decision_result.decision_id` (required) | none |
|
|
139
|
+
| `--observed-at <iso>` | When the outcome was observed (ISO-8601) | now |
|
|
140
|
+
| `--details <json>` | Optional JSON object/array bound to the row | none |
|
|
141
|
+
| `--json` | Print the API response JSON | `false` |
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
coderifts outcome deploy_succeeded --decision "$DECISION_ID"
|
|
145
|
+
coderifts outcome deploy_failed --decision "$DECISION_ID" --json
|
|
146
|
+
```
|
|
147
|
+
|
|
97
148
|
### `coderifts registry-gate [dir]`
|
|
98
149
|
|
|
99
150
|
Local **registry admission gate** for a directory of OpenAPI/Swagger specs
|
|
@@ -138,6 +189,111 @@ After upgrading the `coderifts` CLI, re-run `coderifts hook install` so the
|
|
|
138
189
|
installed hook matches the package (already-installed hooks are not
|
|
139
190
|
auto-updated).
|
|
140
191
|
|
|
192
|
+
### Claude Code PreToolUse (`coderifts claude-hook`) — ID824
|
|
193
|
+
|
|
194
|
+
Tool-call-time gate for **Claude Code**: blocks contract-touching `Write` /
|
|
195
|
+
`Edit` / `MultiEdit` when authorize preflight returns **BLOCK/STOP**.
|
|
196
|
+
|
|
197
|
+
**Exit map (Claude Code semantics — fixed):**
|
|
198
|
+
|
|
199
|
+
| Exit | Meaning |
|
|
200
|
+
|------|---------|
|
|
201
|
+
| **2** | **BLOCK** — tool call cancelled; stderr is shown to the model |
|
|
202
|
+
| **0** | Allow, or soft-skip (no key / wrong file / parse gap / API down) |
|
|
203
|
+
| **1** | **Never used for deny** — Claude treats exit 1 as non-blocking (action proceeds) |
|
|
204
|
+
|
|
205
|
+
Push-time equivalent (git exit **1** on BLOCK): `coderifts hook install`.
|
|
206
|
+
|
|
207
|
+
**Install path:** `npm i -g coderifts` then set key + spec (same as the git hook):
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
git config coderifts.apiKey 'cr_live_…' # or: coderifts login / CODERIFTS_API_KEY
|
|
211
|
+
git config coderifts.specPath api/openapi.yaml # default if omitted
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Recipe — project** (`.claude/settings.json`) **or user** (`~/.claude/settings.json`):
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"hooks": {
|
|
219
|
+
"PreToolUse": [
|
|
220
|
+
{
|
|
221
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
222
|
+
"hooks": [
|
|
223
|
+
{
|
|
224
|
+
"type": "command",
|
|
225
|
+
"command": "coderifts claude-hook",
|
|
226
|
+
"timeout": 60
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
- **Project** `.claude/settings.json` — shared with the repo (commit if the team wants the gate).
|
|
236
|
+
- **User** `~/.claude/settings.json` — personal; applies to all projects on this machine.
|
|
237
|
+
- Matcher is case-sensitive tool names/regex. Exit-2 blocking is **Claude Code** semantics; the same decision discipline at push-time is the git pre-push hook above.
|
|
238
|
+
|
|
239
|
+
**`CODERIFTS_STRICT` (opt-in, not a package guarantee):** env `1` or `true`
|
|
240
|
+
(case-insensitive) hardens **enforcement-relevant** soft sites into **exit 2
|
|
241
|
+
(BLOCK)** — “governance did not run” must not read as ALLOW. Hardened: missing
|
|
242
|
+
API key; API unreachable (network/timeout/error); disk unreadable on the
|
|
243
|
+
contract path; Edit/MultiEdit apply failure on the contract path. Still soft
|
|
244
|
+
even in strict: non-spec path, identical content, unparseable stdin, missing
|
|
245
|
+
`file_path`, WARN/REQUIRE_APPROVAL (governance ran), real ALLOW. Unset/empty →
|
|
246
|
+
identical to the default soft UX. Hosts choose strict for CI/prod.
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
CODERIFTS_STRICT=1 coderifts claude-hook
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### `coderifts status [repo]`
|
|
253
|
+
|
|
254
|
+
**Read-only** cross-layer enforcement report (Runtime / Merge / Deploy, plus
|
|
255
|
+
Content). Calls `GET /api/v1/enforcement-status` and prints the statuses the
|
|
256
|
+
endpoint measured — no GitHub writes, no branch-protection changes. Pair with
|
|
257
|
+
`coderifts enforce` (that command **acts**; this one only **shows**).
|
|
258
|
+
|
|
259
|
+
Requires a cloud API key. Distinct from `coderifts hook status` (local
|
|
260
|
+
pre-push hook install state). Exit **0** on a successful read, **1** on
|
|
261
|
+
missing/invalid repo, missing key, or API error.
|
|
262
|
+
|
|
263
|
+
| Flag | Description | Default |
|
|
264
|
+
|------|-------------|---------|
|
|
265
|
+
| `[repo]` / `--repo <owner/repo>` | Repository | none (required) |
|
|
266
|
+
| `--json` | Machine-readable JSON (raw API body) | `false` |
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
coderifts status owner/repo
|
|
270
|
+
coderifts status --repo owner/repo --json
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### `coderifts enforce [repo]`
|
|
274
|
+
|
|
275
|
+
Close enforcement gaps by chaining existing setup commands: Merge →
|
|
276
|
+
`setup-required-check`; Runtime → `hook install` (apply only) + agent-guard
|
|
277
|
+
guidance; Deploy → `deploy-gate` CD-step guidance; Content → guidance only.
|
|
278
|
+
**Dry-run by default** (`--apply` to mutate). Does not bypass underlying
|
|
279
|
+
command safety. Does not label Runtime/Content as server ENFORCING. Does not
|
|
280
|
+
upgrade UNKNOWN.
|
|
281
|
+
|
|
282
|
+
Requires a cloud API key for the status read. Merge `--apply` uses your local
|
|
283
|
+
`gh` credentials, not the CodeRifts API key. Exit **0** when no chained step
|
|
284
|
+
failed, **1** on missing repo/key, API error, or any apply failure.
|
|
285
|
+
|
|
286
|
+
| Flag | Description | Default |
|
|
287
|
+
|------|-------------|---------|
|
|
288
|
+
| `[repo]` / `--repo <owner/repo>` | Repository | none (required) |
|
|
289
|
+
| `--apply` | Run underlying setup commands | dry-run |
|
|
290
|
+
| `--json` | Machine-readable JSON result | `false` |
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
coderifts enforce owner/repo
|
|
294
|
+
coderifts enforce --repo owner/repo --apply
|
|
295
|
+
```
|
|
296
|
+
|
|
141
297
|
### Agent host files (`coderifts agent-setup`)
|
|
142
298
|
|
|
143
299
|
Writes the six CodeRifts agent-host rule files into the current repo (or `--out <dir>`):
|
|
@@ -163,6 +319,28 @@ re-sync with `node scripts/sync-cli-agent-host-embed.js` when the rule source ch
|
|
|
163
319
|
|
|
164
320
|
Policy template for agent/MCP APIs: `coderifts init ai-agent` (aliases: `agent`, `mcp`).
|
|
165
321
|
|
|
322
|
+
### Copilot MCP configs (`coderifts copilot-setup`)
|
|
323
|
+
|
|
324
|
+
Writes GitHub Copilot MCP configs from the canonical tool names. Root keys
|
|
325
|
+
differ: VS Code uses `servers`; Copilot cloud Settings paste uses `mcpServers`.
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
coderifts copilot-setup # write (skip existing files)
|
|
329
|
+
coderifts copilot-setup --force # overwrite
|
|
330
|
+
coderifts copilot-setup --check # drift vs embedded content (exit 1 on drift)
|
|
331
|
+
coderifts copilot-setup --out ./docs # custom root
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
| Flag | Description | Default |
|
|
335
|
+
|------|-------------|---------|
|
|
336
|
+
| `--out <dir>` | Target directory | cwd |
|
|
337
|
+
| `--check` | Exit 0 if on-disk files match embedded content; exit 1 on drift | `false` |
|
|
338
|
+
| `--force` | Overwrite existing files | skip collisions |
|
|
339
|
+
|
|
340
|
+
Writes: `.vscode/mcp.json`, `copilot-cloud-agent-mcp.json`,
|
|
341
|
+
`copilot-custom-agent-mcp.frontmatter.md`, `docs/copilot-mcp.md`. Unknown flags
|
|
342
|
+
exit 1 (never silently ignored).
|
|
343
|
+
|
|
166
344
|
### Required check wizard (`coderifts setup-required-check`)
|
|
167
345
|
|
|
168
346
|
Guided setup so **CodeRifts / contract-gate** is a **required status check** on
|
|
@@ -198,6 +376,57 @@ coderifts setup-required-check --apply
|
|
|
198
376
|
coderifts setup-required-check --branch main
|
|
199
377
|
```
|
|
200
378
|
|
|
379
|
+
### `coderifts lock [repo]`
|
|
380
|
+
|
|
381
|
+
Fetch the observed agent-contract lockfile (`coderifts.lock` v1) from
|
|
382
|
+
`GET /api/v1/lock`, **or** `--check` MCP drift of a locked manifest against a
|
|
383
|
+
live one (`scoreMcpRisk`). `--check` is **monitor only** — it does not block.
|
|
384
|
+
Unreachable live → status `unreachable` (not drift). Lock v1 records
|
|
385
|
+
agents/ops, not MCP tool bodies; pass `--locked-manifest` (or additive
|
|
386
|
+
`mcp_manifests` on the lockfile) for `--check`.
|
|
387
|
+
|
|
388
|
+
Requires a cloud API key for write mode.
|
|
389
|
+
|
|
390
|
+
| Flag | Description | Default |
|
|
391
|
+
|------|-------------|---------|
|
|
392
|
+
| `[repo]` / `--repo <owner/repo>` | Repository | none (write mode) |
|
|
393
|
+
| `--out <path>` | Output path | `coderifts.lock` |
|
|
394
|
+
| `--json` | stdout-only JSON (write: lock doc; check: drift report) | `false` |
|
|
395
|
+
| `--check` | Compare locked vs live MCP manifest | `false` |
|
|
396
|
+
| `--locked-manifest <path>` | Baseline MCP manifest JSON (`tools[]`) for `--check` | none |
|
|
397
|
+
| `--live-manifest <path>` | Live MCP manifest JSON for `--check` | none |
|
|
398
|
+
| `--live-url <url>` | Fetch live MCP manifest JSON from URL for `--check` | none |
|
|
399
|
+
| `--lockfile <path>` | Read `coderifts.lock` for additive `mcp_manifests` | none |
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
coderifts lock owner/repo
|
|
403
|
+
coderifts lock --check --locked-manifest mcp.json --live-manifest live.json
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**`--check` exit codes** (from `lock.js`): `0` unchanged; `3` drift; `2`
|
|
407
|
+
unreachable / missing locked or live (not clean); `1` usage/error.
|
|
408
|
+
|
|
409
|
+
### `coderifts adopt`
|
|
410
|
+
|
|
411
|
+
Counterfactual report: what CodeRifts **would have done** on contract-artifact
|
|
412
|
+
changes in **local git** history. Extracts spec-only paths (not source files
|
|
413
|
+
whose names happen to contain “openapi”), then runs
|
|
414
|
+
`buildCounterfactualReport`. Step B (GitHub API + outcome correlation) is not
|
|
415
|
+
this command. Not a git repository → fail-closed (`GIT_ERROR`, exit **1**).
|
|
416
|
+
Success → exit **0**.
|
|
417
|
+
|
|
418
|
+
| Flag | Description | Default |
|
|
419
|
+
|------|-------------|---------|
|
|
420
|
+
| `--days <n>` | Look back N days (`git log --since`) | last 50 commits if neither flag set |
|
|
421
|
+
| `--commits <n>` | Last N commits | last 50 commits if neither flag set |
|
|
422
|
+
| `--json` | Machine-readable JSON report (analyzer logs silenced) | `false` |
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
coderifts adopt
|
|
426
|
+
coderifts adopt --days 30
|
|
427
|
+
coderifts adopt --commits 20 --json
|
|
428
|
+
```
|
|
429
|
+
|
|
201
430
|
## CI/CD Integration
|
|
202
431
|
|
|
203
432
|
### GitHub Actions
|
|
@@ -256,14 +485,18 @@ Two honest modes:
|
|
|
256
485
|
|----------|-------------|
|
|
257
486
|
| `CODERIFTS_API_KEY` | API key (alternative to `coderifts login`) |
|
|
258
487
|
| `CODERIFTS_OASDIFF_BIN` | Explicit path to the `oasdiff` binary (overrides auto-detect) |
|
|
488
|
+
| `CODERIFTS_STRICT` | `claude-hook` only: `1` or `true` (case-insensitive) turns enforcement-relevant soft sites into exit **2** (BLOCK). Unset/empty → default soft allow. Not a package-level guarantee. |
|
|
489
|
+
| `CODERIFTS_DEPLOY_ENFORCE` | `deploy-gate`: `true` marks the step enforcing (same as `--enforce`); `unknown` is explicitly unobservable (never assume ENFORCING) |
|
|
259
490
|
| `NO_COLOR` | Disable colored output |
|
|
260
491
|
|
|
261
492
|
## Exit Codes
|
|
262
493
|
|
|
263
494
|
| Code | Meaning |
|
|
264
495
|
|------|---------|
|
|
265
|
-
| `0` |
|
|
266
|
-
| `1` | Breaking changes
|
|
496
|
+
| `0` | Success: no breaking changes (or below threshold); advisory `deploy-gate`; `lock --check` unchanged; `adopt` / `status` / `outcome` success |
|
|
497
|
+
| `1` | Breaking changes (CI mode), analysis/usage/API failure, or `deploy-gate` deny **when enforcing** |
|
|
498
|
+
| `2` | `claude-hook` BLOCK (Claude Code cancel); `lock --check` unreachable / missing locked or live |
|
|
499
|
+
| `3` | `lock --check` drift detected (monitor only — does not block the package) |
|
|
267
500
|
|
|
268
501
|
## Links
|
|
269
502
|
|
package/bin/coderifts.js
CHANGED
|
@@ -109,6 +109,7 @@ program
|
|
|
109
109
|
.option('--branch <name>', 'Branch to protect (default: repo default branch)')
|
|
110
110
|
.option('--repo <owner/repo>', 'Override owner/repo (default: git remote origin)')
|
|
111
111
|
.option('--apply', 'Apply the protection change (default: print the exact gh command only)')
|
|
112
|
+
.option('--enforce-admins', 'Set enforce_admins:true on create (default: false — admins can bypass; gate reports admin_bypass_open)')
|
|
112
113
|
.option('--json', 'Machine-readable JSON result')
|
|
113
114
|
.action(async (options) => {
|
|
114
115
|
const { runSetupRequiredCheck } = require('../src/commands/setup-required-check');
|
|
@@ -182,14 +183,21 @@ program
|
|
|
182
183
|
runCopilotSetup(options, { exit: true });
|
|
183
184
|
});
|
|
184
185
|
|
|
185
|
-
// ── lock — agent contract lockfile v1 (observed usage; ID847) ──
|
|
186
|
-
// GET /api/v1/lock?repo= →
|
|
186
|
+
// ── lock — agent contract lockfile v1 (observed usage; ID847) + MCP drift --check (896) ──
|
|
187
|
+
// Write: GET /api/v1/lock?repo= → coderifts.lock. Check: locked MCP vs live via scoreMcpRisk.
|
|
187
188
|
program
|
|
188
189
|
.command('lock [repo]')
|
|
189
|
-
.description(
|
|
190
|
+
.description(
|
|
191
|
+
'Fetch observed agent-contract lockfile (coderifts.lock v1), or --check MCP drift vs live manifest',
|
|
192
|
+
)
|
|
190
193
|
.option('--repo <owner/repo>', 'Repository (owner/repo); also accepted as a positional argument')
|
|
191
194
|
.option('--out <path>', 'Output path (default: coderifts.lock in cwd)')
|
|
192
|
-
.option('--json', 'Print
|
|
195
|
+
.option('--json', 'Print JSON to stdout only (write: lock doc; check: drift report)')
|
|
196
|
+
.option('--check', 'MCP supply-chain monitor: compare locked vs live manifest (scoreMcpRisk)')
|
|
197
|
+
.option('--locked-manifest <path>', 'Baseline MCP manifest JSON (tools[]) for --check')
|
|
198
|
+
.option('--live-manifest <path>', 'Live MCP manifest JSON for --check')
|
|
199
|
+
.option('--live-url <url>', 'Fetch live MCP manifest JSON from URL for --check')
|
|
200
|
+
.option('--lockfile <path>', 'Read coderifts.lock for additive mcp_manifests (optional)')
|
|
193
201
|
.action(async (repoPositional, options) => {
|
|
194
202
|
const { runLock } = require('../src/commands/lock');
|
|
195
203
|
const result = await runLock({
|
|
@@ -201,6 +209,71 @@ program
|
|
|
201
209
|
}
|
|
202
210
|
});
|
|
203
211
|
|
|
212
|
+
// ── adopt — counterfactual "what would CodeRifts have done" from local git (roadmap 893 Step A) ──
|
|
213
|
+
// Stateless core in app src/core/counterfactual-adopt.js; CLI extracts change sets via git
|
|
214
|
+
// (mirrors publish-gate gitImpl). Not named "shadow" (shadowApis is taken).
|
|
215
|
+
program
|
|
216
|
+
.command('adopt')
|
|
217
|
+
.description(
|
|
218
|
+
'Counterfactual report: what CodeRifts would have done on contract-artifact changes in local git history',
|
|
219
|
+
)
|
|
220
|
+
.option('--days <n>', 'Look back N days of git history', (v) => parseInt(v, 10))
|
|
221
|
+
.option('--commits <n>', 'Look at the last N commits', (v) => parseInt(v, 10))
|
|
222
|
+
.option('--json', 'Machine-readable JSON report')
|
|
223
|
+
.action(async (options) => {
|
|
224
|
+
const { runAdopt } = require('../src/commands/adopt');
|
|
225
|
+
const result = await runAdopt({
|
|
226
|
+
days: options.days,
|
|
227
|
+
commits: options.commits,
|
|
228
|
+
json: !!options.json,
|
|
229
|
+
});
|
|
230
|
+
if (result && typeof result.exitCode === 'number') {
|
|
231
|
+
process.exitCode = result.exitCode;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ── outcome — POST /api/v1/outcomes (caller assertion; does not verify the deploy) ──
|
|
236
|
+
program
|
|
237
|
+
.command('outcome <kind>')
|
|
238
|
+
.description(
|
|
239
|
+
'Report a post-hoc outcome for a decision_id (deploy_succeeded/deploy_failed/…). '
|
|
240
|
+
+ 'Records the caller\'s assertion — does not verify the deploy itself.',
|
|
241
|
+
)
|
|
242
|
+
.option('--decision <decision_id>', 'Decision id from decision_result.decision_id (required)')
|
|
243
|
+
.option('--observed-at <iso>', 'When the outcome was observed (ISO-8601; default: now)')
|
|
244
|
+
.option('--details <json>', 'Optional JSON object/array bound to the outcome row')
|
|
245
|
+
.option('--json', 'Print the API response JSON to stdout')
|
|
246
|
+
.addHelpText('after', () => {
|
|
247
|
+
const { USAGE } = require('../src/commands/outcome');
|
|
248
|
+
return `\n${USAGE}\n`;
|
|
249
|
+
})
|
|
250
|
+
.action(async (kind, options) => {
|
|
251
|
+
const { runOutcome } = require('../src/commands/outcome');
|
|
252
|
+
const result = await runOutcome(kind, options);
|
|
253
|
+
if (result && typeof result.exitCode === 'number') {
|
|
254
|
+
process.exitCode = result.exitCode;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// ── claude-hook — Claude Code PreToolUse (exit 2 = BLOCK; never exit 1 for deny) ──
|
|
259
|
+
program
|
|
260
|
+
.command('claude-hook')
|
|
261
|
+
.description(
|
|
262
|
+
'Claude Code PreToolUse gate: block contract-touching Write|Edit|MultiEdit on BLOCK/STOP '
|
|
263
|
+
+ '(exit 2). Soft-allow when unconfigured/unparseable/API down.',
|
|
264
|
+
)
|
|
265
|
+
.addHelpText('after', () => {
|
|
266
|
+
const { USAGE } = require('../src/commands/claude-hook');
|
|
267
|
+
return `\n${USAGE}\n`;
|
|
268
|
+
})
|
|
269
|
+
.action(async () => {
|
|
270
|
+
const { runClaudeHook } = require('../src/commands/claude-hook');
|
|
271
|
+
const result = await runClaudeHook();
|
|
272
|
+
if (result && typeof result.exitCode === 'number') {
|
|
273
|
+
process.exitCode = result.exitCode;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
204
277
|
// ── hook command group ──
|
|
205
278
|
const hookCmd = program
|
|
206
279
|
.command('hook')
|