coderifts 3.4.0 → 4.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ ## 4.1.0
4
+
5
+ ### Added
6
+
7
+ - **`coderifts claude-hook` JSONL GuardEvent log.** When `CODERIFTS_GUARD_EVENT_LOG` is
8
+ set to an absolute file path, the hook appends one JSON object per line:
9
+ `preflight_start` / `preflight_result` / `preflight_unavailable` / `detection_skip`
10
+ plus terminal `hook_blocked` (exit 2) / `hook_continue` (exit 0) /
11
+ `hook_advisory_passthrough` (ADVISORY exit 0). Extra fields: `tool`, `path`,
12
+ `trigger_source:"claude_hook"`, `fingerprint` when the authorize envelope
13
+ returns one, `sessionId` (`CODERIFTS_SESSION_ID` or per-process). Unset env
14
+ → no file. Log write errors are swallowed and printed to stderr; **exit
15
+ codes are unchanged**. The hook does not claim `enforced` (no
16
+ receipt-verified execution path).
17
+
18
+ ## 4.0.0
19
+
20
+ **Breaking** — `coderifts claude-hook` is fail-closed by default. The CLI now
21
+ depends on `@coderifts/agent-guard` ^8.1.1.
22
+
23
+ ### Breaking
24
+ - **`claude-hook` default is fail-closed (P0-3/P0-4).** No API key, API
25
+ unreachable, disk unreadable, or edit-apply failure on the spec path →
26
+ **exit 2** (`enforce_indeterminate`), not exit 0. `REQUEST_APPROVAL` →
27
+ exit 2 (`approval_required`). `CONTINUE_WITH_MONITORING` without a
28
+ host-asserted monitoring sink → exit 2. Empty verdict is no longer
29
+ mapped to ALLOW (P0 #5).
30
+ - **Migration:** hosts that ran the hook without a key / without
31
+ `CODERIFTS_STRICT` will now **block** tool calls. Explicit opt-out:
32
+ `CODERIFTS_ADVISORY=1` restores the old soft-allow on "governance could
33
+ not run" sites. `CODERIFTS_STRICT=1` remains fail-closed and cannot be
34
+ weakened by ADVISORY.
35
+
36
+ ### Changed
37
+ - **`@coderifts/agent-guard` `^6.4.0` → `^8.1.1`.** CLI still calls only
38
+ `deployGate` / `matchGlob` (not the wrap/TOCTOU path). `inescapable_deploy`
39
+ on 8.x is `ENFORCING ∧ ¬bypass ∧ expected_fingerprint` rebound.
40
+ - **`deployReportResiduals`:** rebound-missing is `change_set_not_rebound`,
41
+ never `bypass_open`. Enforcement-axis residuals still named first (same
42
+ order as guard `residuals[]`). CLI AND on the inescapable flag is kept
43
+ (never widen).
44
+ - **`MIN_GUARD_MAJOR`:** 6 → 8 (`scripts/assert-guard-major.js`).
45
+
46
+ ### Added
47
+ - **`coderifts agent-setup` writes `.claude/settings.json`.** PreToolUse hook
48
+ (`matcher: Write|Edit|MultiEdit` → `coderifts claude-hook`, timeout 60)
49
+ beside the six rule files. Fail-closed default (P0-3/P0-4). Explicit
50
+ opt-out documented on stdout: `CODERIFTS_ADVISORY=1`. `--check` is
51
+ byte-stable against the embed (same drift-gate as the six rule files).
52
+ Existing `.claude/settings.json` is skipped (no merge); `--force`
53
+ replaces the **entire** file.
54
+
55
+ ### Fixed
56
+ - **Installer copy (P0 #9 / e76e846):** `agent-setup` / `init` teach
57
+ guard@8 execution-state **DEFAULT ON**, not the old OFF.
58
+
59
+ ## 3.4.0
60
+
61
+ ### Agent-setup
62
+
63
+ - Rule files now state that receipts are operation-scoped (a merge receipt never authorizes deploy/publish; re-preflight with the new `context.operation` — do not decline) and that a stale receipt on a changed head needs a new preflight (`verify_receipt` cannot re-diff).
64
+ - Hosts with existing agent-setup files: `coderifts agent-setup --force` to pick up the new paragraphs; `--check` will report drift until then.
package/README.md CHANGED
@@ -236,17 +236,22 @@ git config coderifts.specPath api/openapi.yaml # default if omitted
236
236
  - **User** `~/.claude/settings.json` — personal; applies to all projects on this machine.
237
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
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.
239
+ **Fail-closed default:** missing API key, API unreachable, disk unreadable, or
240
+ edit-apply failure on the spec path is **exit 2** (`enforce_indeterminate`) —
241
+ absence of a key is not ALLOW. `REQUEST_APPROVAL` is **exit 2**
242
+ (`approval_required`). `CONTINUE_WITH_MONITORING` is **exit 2**
243
+ (`monitoring_unwired`) unless the host asserts a sink
244
+ (`CODERIFTS_MONITORING_SINK_WIRED=1` or `git config coderifts.monitoringSinkWired true`)
245
+ a host claim, not delivery proof.
246
+
247
+ **`CODERIFTS_ADVISORY=1|true`:** explicit opt-out that restores soft-allow on
248
+ “governance could not run” sites. Not implied by a missing key.
249
+
250
+ **`CODERIFTS_STRICT=1|true`:** same fail-closed as the default; cannot be
251
+ weakened by `CODERIFTS_ADVISORY`.
247
252
 
248
253
  ```bash
249
- CODERIFTS_STRICT=1 coderifts claude-hook
254
+ CODERIFTS_ADVISORY=1 coderifts claude-hook
250
255
  ```
251
256
 
252
257
  ### `coderifts status [repo]`
@@ -296,7 +301,8 @@ coderifts enforce --repo owner/repo --apply
296
301
 
297
302
  ### Agent host files (`coderifts agent-setup`)
298
303
 
299
- Writes the six CodeRifts agent-host rule files into the current repo (or `--out <dir>`):
304
+ Writes the six CodeRifts agent-host rule files **plus** the Claude Code
305
+ PreToolUse hook settings into the current repo (or `--out <dir>`):
300
306
 
301
307
  | File | Host |
302
308
  |------|------|
@@ -306,6 +312,16 @@ Writes the six CodeRifts agent-host rule files into the current repo (or `--out
306
312
  | `.github/copilot-instructions.md` | GitHub Copilot |
307
313
  | `coderifts-langgraph-policy.js` | LangGraph |
308
314
  | `openai-agent-instructions.md` | OpenAI Agents SDK |
315
+ | `.claude/settings.json` | Claude Code PreToolUse → `coderifts claude-hook` (fail-closed) |
316
+
317
+ The generated `.claude/settings.json` matches the recipe above (`matcher`
318
+ `Write|Edit|MultiEdit`, `command` `coderifts claude-hook`, `timeout` 60).
319
+ Default is **fail-closed** (missing key / API down → exit 2). Explicit
320
+ opt-out: `CODERIFTS_ADVISORY=1|true`. `CODERIFTS_STRICT=1` cannot be
321
+ weakened by ADVISORY. Existing files are skipped unless `--force`.
322
+ `--force` **replaces the entire** `.claude/settings.json` (no merge into
323
+ permissions / other hooks). If the file already exists, add the PreToolUse
324
+ recipe by hand or accept a full replace.
309
325
 
310
326
  ```bash
311
327
  coderifts agent-setup # write (skip existing files)
@@ -485,7 +501,9 @@ Two honest modes:
485
501
  |----------|-------------|
486
502
  | `CODERIFTS_API_KEY` | API key (alternative to `coderifts login`) |
487
503
  | `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. |
504
+ | `CODERIFTS_STRICT` | `claude-hook`: `1` or `true` pins fail-closed (cannot be weakened by `CODERIFTS_ADVISORY`). Default is already fail-closed. |
505
+ | `CODERIFTS_ADVISORY` | `claude-hook`: `1` or `true` is the **explicit** soft-allow opt-out on “governance could not run” sites. Absence of a key is not this flag. |
506
+ | `CODERIFTS_MONITORING_SINK_WIRED` | `claude-hook`: `1` or `true` host-asserts a monitoring sink so `CONTINUE_WITH_MONITORING` may exit 0. Not delivery proof. |
489
507
  | `CODERIFTS_DEPLOY_ENFORCE` | `deploy-gate`: `true` marks the step enforcing (same as `--enforce`); `unknown` is explicitly unobservable (never assume ENFORCING) |
490
508
  | `NO_COLOR` | Disable colored output |
491
509
 
package/bin/coderifts.js CHANGED
@@ -259,8 +259,9 @@ program
259
259
  program
260
260
  .command('claude-hook')
261
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.',
262
+ 'Claude Code PreToolUse gate: block contract-touching Write|Edit|MultiEdit on BLOCK/STOP/'
263
+ + 'REQUEST_APPROVAL (exit 2). Fail-closed when governance cannot run; '
264
+ + 'CODERIFTS_ADVISORY=1 is the explicit soft opt-out.',
264
265
  )
265
266
  .addHelpText('after', () => {
266
267
  const { USAGE } = require('../src/commands/claude-hook');
package/dist/cli.js CHANGED
@@ -3007,7 +3007,7 @@ var require_package = __commonJS({
3007
3007
  "package.json"(exports2, module2) {
3008
3008
  module2.exports = {
3009
3009
  name: "coderifts",
3010
- version: "3.4.0",
3010
+ version: "4.1.0",
3011
3011
  description: "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
3012
3012
  author: "CodeRifts <hello@coderifts.com>",
3013
3013
  license: "MIT",
@@ -3020,6 +3020,7 @@ var require_package = __commonJS({
3020
3020
  "bin/",
3021
3021
  "scripts/",
3022
3022
  "README.md",
3023
+ "CHANGELOG.md",
3023
3024
  "corpus/"
3024
3025
  ],
3025
3026
  keywords: [
@@ -3051,7 +3052,7 @@ var require_package = __commonJS({
3051
3052
  test: "node --test test/*.test.js"
3052
3053
  },
3053
3054
  dependencies: {
3054
- "@coderifts/agent-guard": "^6.4.0",
3055
+ "@coderifts/agent-guard": "^8.1.1",
3055
3056
  chalk: "^4.1.2",
3056
3057
  "cli-table3": "^0.6.4",
3057
3058
  commander: "^12.0.0",
@@ -67465,43 +67466,53 @@ var require_guard = __commonJS({
67465
67466
  }
67466
67467
  const enforceable = receiptVerified && (kind === "ALLOW" || sinkWired);
67467
67468
  if (enforceable) {
67468
- const execStateMode = config.requireExecutionStateMatch;
67469
- if (execStateMode === true || execStateMode === "warn") {
67470
- const et = (0, execution_time_fingerprint_js_1.checkExecutionTimeFingerprint)({
67471
- artifacts: detection.artifacts,
67472
- // Same context slots the guard sent on preflight (server folds these into crbundle.v1).
67473
- context: {
67474
- operation: config.operation ?? "tool_call",
67475
- environment: config.environment
67476
- },
67477
- envelope
67469
+ const execStateMode = config.requireExecutionStateMatch === void 0 ? true : config.requireExecutionStateMatch;
67470
+ if (execStateMode === false) {
67471
+ emit(config, {
67472
+ type: "execution_state_check_disabled",
67473
+ at: iso(),
67474
+ decisionId: envelope.decision_id,
67475
+ cause: "requireExecutionStateMatch_false"
67478
67476
  });
67479
- if (!et.match) {
67480
- if (execStateMode === true) {
67481
- breakerRecord(config);
67482
- return closedIntegrity(config, "EXECUTION_STATE_DRIFT", failPolicy, fctx, cwctx, redacted, detection.artifacts);
67483
- }
67484
- if ((0, execution_time_fingerprint_js_1.isUnmeasurableExecutionStateReason)(et.reason)) {
67485
- emit(config, {
67486
- type: "execution_state_unmeasurable",
67487
- at: iso(),
67488
- decisionId: envelope.decision_id,
67489
- current_fingerprint: et.current_fingerprint,
67490
- authorized_fingerprint: et.authorized_fingerprint,
67491
- reason: et.reason,
67492
- note: execution_time_fingerprint_js_1.EXECUTION_STATE_UNMEASURABLE_NOTE
67493
- });
67494
- } else {
67495
- emit(config, {
67496
- type: "execution_state_drift_observed",
67497
- at: iso(),
67498
- decisionId: envelope.decision_id,
67499
- current_fingerprint: et.current_fingerprint,
67500
- authorized_fingerprint: et.authorized_fingerprint,
67501
- reason: et.reason
67502
- });
67503
- }
67477
+ const offVerdict = kind === "ALLOW" ? { kind: "ALLOW", action: "CONTINUE", envelope, receiptVerified } : { kind: "MONITOR", action: "CONTINUE_WITH_MONITORING", envelope, receiptVerified };
67478
+ return runUnenforced(config, executeFactory, envelope, offVerdict, true, redacted, freshBasis, cwBasis);
67479
+ }
67480
+ const et = (0, execution_time_fingerprint_js_1.checkExecutionTimeFingerprint)({
67481
+ artifacts: detection.artifacts,
67482
+ context: {
67483
+ operation: config.operation ?? "tool_call",
67484
+ environment: config.environment
67485
+ },
67486
+ envelope
67487
+ });
67488
+ if (!et.match) {
67489
+ const unmeasurable = (0, execution_time_fingerprint_js_1.isUnmeasurableExecutionStateReason)(et.reason);
67490
+ if (execStateMode === true) {
67491
+ breakerRecord(config);
67492
+ return closedIntegrity(config, unmeasurable ? "EXECUTION_STATE_UNMEASURABLE" : "EXECUTION_STATE_DRIFT", failPolicy, fctx, cwctx, redacted, detection.artifacts);
67493
+ }
67494
+ if (unmeasurable) {
67495
+ emit(config, {
67496
+ type: "execution_state_unmeasurable",
67497
+ at: iso(),
67498
+ decisionId: envelope.decision_id,
67499
+ current_fingerprint: et.current_fingerprint,
67500
+ authorized_fingerprint: et.authorized_fingerprint,
67501
+ reason: et.reason,
67502
+ note: execution_time_fingerprint_js_1.EXECUTION_STATE_UNMEASURABLE_NOTE
67503
+ });
67504
+ } else {
67505
+ emit(config, {
67506
+ type: "execution_state_drift_observed",
67507
+ at: iso(),
67508
+ decisionId: envelope.decision_id,
67509
+ current_fingerprint: et.current_fingerprint,
67510
+ authorized_fingerprint: et.authorized_fingerprint,
67511
+ reason: et.reason
67512
+ });
67504
67513
  }
67514
+ const warnVerdict = kind === "ALLOW" ? { kind: "ALLOW", action: "CONTINUE", envelope, receiptVerified } : { kind: "MONITOR", action: "CONTINUE_WITH_MONITORING", envelope, receiptVerified };
67515
+ return runUnenforced(config, executeFactory, envelope, warnVerdict, true, redacted, freshBasis, cwBasis);
67505
67516
  }
67506
67517
  const approved = kind === "ALLOW" ? { kind: "ALLOW", action: "CONTINUE", envelope, receiptVerified: true } : { kind: "MONITOR", action: "CONTINUE_WITH_MONITORING", envelope, receiptVerified: true };
67507
67518
  return runEnforced(config, executeFactory, approved, redacted, freshBasis, cwBasis);
@@ -69861,24 +69872,25 @@ var require_merge_gate = __commonJS({
69861
69872
  if (!isAllowClass(receipt, allowWarnMerge)) {
69862
69873
  return fail("failure", "decision_not_allow");
69863
69874
  }
69864
- const inescapable_merge = enforcement_state === "ENFORCING" && protection.admin_bypass_possible === false;
69875
+ const protectionOk = enforcement_state === "ENFORCING" && protection.admin_bypass_possible === false;
69876
+ const appBound = protection.required_check_app_bound === true;
69877
+ const rebound = rc.expected_fingerprint != null && String(rc.expected_fingerprint).length > 0;
69878
+ const inescapable_merge = protectionOk && appBound && rebound;
69865
69879
  const residuals = [];
69866
- if (!inescapable_merge) {
69880
+ if (!protectionOk) {
69867
69881
  if (enforcement_state === "ENFORCING")
69868
69882
  residuals.push("admin_bypass_open");
69869
69883
  else if (enforcement_state === "ADVISORY")
69870
69884
  residuals.push("protection_advisory_only");
69871
69885
  else
69872
69886
  residuals.push("protection_not_configured");
69887
+ } else if (protection.required_check_app_bound === true) {
69888
+ } else if (protection.required_check_app_bound === false) {
69889
+ residuals.push("required_check_app_not_bound");
69873
69890
  } else {
69874
- if (protection.required_check_app_bound === true) {
69875
- } else if (protection.required_check_app_bound === false) {
69876
- residuals.push("required_check_app_not_bound");
69877
- } else {
69878
- residuals.push("required_check_app_binding_unknown");
69879
- }
69891
+ residuals.push("required_check_app_binding_unknown");
69880
69892
  }
69881
- if (rc.expected_fingerprint == null) {
69893
+ if (!rebound) {
69882
69894
  residuals.push("change_set_not_rebound");
69883
69895
  }
69884
69896
  const residual = residuals.length > 0 ? residuals[0] : void 0;
@@ -70001,15 +70013,17 @@ var require_deploy_gate = __commonJS({
70001
70013
  if (!isAllowClass(receipt, allowWarnDeploy)) {
70002
70014
  return deny("failure", "decision_not_allow");
70003
70015
  }
70004
- const inescapable_deploy = enforcement_state === "ENFORCING" && enf.bypass_possible === false;
70016
+ const enforcementOk = enforcement_state === "ENFORCING" && enf.bypass_possible === false;
70017
+ const rebound = rc.expected_fingerprint != null && String(rc.expected_fingerprint).length > 0;
70018
+ const inescapable_deploy = enforcementOk && rebound;
70005
70019
  const residuals = [];
70006
- if (!inescapable_deploy) {
70020
+ if (!enforcementOk) {
70007
70021
  if (enforcement_state === "ENFORCING")
70008
70022
  residuals.push("bypass_open");
70009
70023
  else
70010
70024
  residuals.push("enforcement_not_configured");
70011
70025
  }
70012
- if (rc.expected_fingerprint == null) {
70026
+ if (!rebound) {
70013
70027
  residuals.push("change_set_not_rebound");
70014
70028
  }
70015
70029
  const residual = residuals.length > 0 ? residuals[0] : void 0;
@@ -70298,6 +70312,7 @@ var require_with_coderifts = __commonJS({
70298
70312
  var COMPOSITION_CALL_POLICY_COMPLETE = false;
70299
70313
  var RESIDUAL_CALL_POLICY_INCOMPLETE = "composition_call_policy_incomplete";
70300
70314
  var RESIDUAL_FRESHNESS_NOT_CONFIGURED = "composition_freshness_not_configured";
70315
+ var RESIDUAL_CALLS_OUTSIDE_GUARDED_PATH = "calls_outside_guarded_path_invisible";
70301
70316
  var RESIDUAL_FORCED_READONLY = "composition_forced_readonly_on_heuristic_mutator";
70302
70317
  var RESIDUAL_UNKNOWN_READONLY = "composition_unknown_treated_as_readonly";
70303
70318
  var COVERAGE_STRENGTH = {
@@ -70309,6 +70324,30 @@ var require_with_coderifts = __commonJS({
70309
70324
  function coverageRank(coverage) {
70310
70325
  return Object.prototype.hasOwnProperty.call(COVERAGE_STRENGTH, coverage) ? COVERAGE_STRENGTH[coverage] : void 0;
70311
70326
  }
70327
+ function isEnforcingStrict(input) {
70328
+ return input.profile === "ENFORCING_STRICT";
70329
+ }
70330
+ function enforcingStrictWeakenFlags(input) {
70331
+ const flags = [];
70332
+ if (input.requireCoverage !== void 0) {
70333
+ const rank = coverageRank(input.requireCoverage);
70334
+ if (rank !== void 0 && rank < COVERAGE_STRENGTH.COMPLETE)
70335
+ flags.push("requireCoverage");
70336
+ }
70337
+ if (input.requireFreshness === false)
70338
+ flags.push("requireFreshness");
70339
+ if (input.requireExecutionStateMatch === false || input.requireExecutionStateMatch === "warn") {
70340
+ flags.push("requireExecutionStateMatch");
70341
+ }
70342
+ if (input.requireConditionalWrite === false)
70343
+ flags.push("requireConditionalWrite");
70344
+ const reg = input.registry ?? {};
70345
+ if (reg.failOnUnguardedMutator === false)
70346
+ flags.push("failOnUnguardedMutator");
70347
+ if (reg.unknownToolPolicy === "readonly")
70348
+ flags.push("unknownToolPolicy");
70349
+ return flags;
70350
+ }
70312
70351
  async function safeOnSettledCall(onSettledCall, payload) {
70313
70352
  try {
70314
70353
  await Promise.resolve(onSettledCall(payload));
@@ -70495,6 +70534,18 @@ var require_with_coderifts = __commonJS({
70495
70534
  if (input.requireCoverage !== void 0 && coverageRank(input.requireCoverage) === void 0) {
70496
70535
  problems.push(`\`requireCoverage\` must be one of COMPLETE | PARTIAL | BYPASSED | UNKNOWN (got ${JSON.stringify(input.requireCoverage)})`);
70497
70536
  }
70537
+ if (input.profile !== void 0 && input.profile !== "ENFORCING_STRICT") {
70538
+ problems.push(`\`profile\` must be 'ENFORCING_STRICT' when set (got ${JSON.stringify(input.profile)})`);
70539
+ }
70540
+ if (isEnforcingStrict(input)) {
70541
+ const weaken = enforcingStrictWeakenFlags(input);
70542
+ if (weaken.length > 0) {
70543
+ problems.push(`ENFORCING_STRICT cannot be weakened: ${weaken.join(", ")} conflicts`);
70544
+ }
70545
+ if (typeof input.resolvePriorContent !== "function") {
70546
+ problems.push("ENFORCING_STRICT cannot be weakened: resolvePriorContent conflicts");
70547
+ }
70548
+ }
70498
70549
  if (problems.length > 0) {
70499
70550
  throw new Error(`withCodeRifts: construction aborted \u2014 ${problems.length} condition(s):
70500
70551
  ` + problems.map((p) => ` - ${p}`).join("\n"));
@@ -70533,20 +70584,27 @@ var require_with_coderifts = __commonJS({
70533
70584
  if (input.requireExecutionStateMatch !== void 0) {
70534
70585
  guard.requireExecutionStateMatch = input.requireExecutionStateMatch;
70535
70586
  }
70587
+ const strict = isEnforcingStrict(input);
70588
+ if (strict) {
70589
+ guard.requireFreshness = true;
70590
+ guard.requireConditionalWrite = true;
70591
+ guard.requireExecutionStateMatch = true;
70592
+ }
70536
70593
  const config = {
70537
70594
  guard,
70538
- unknownToolPolicy: reg.unknownToolPolicy ?? "mutating",
70595
+ unknownToolPolicy: strict ? reg.unknownToolPolicy === "reject" ? "reject" : "mutating" : reg.unknownToolPolicy ?? "mutating",
70539
70596
  classify: reg.classify,
70540
70597
  binders: reg.binders,
70541
70598
  forceReadonly: reg.forceReadonly,
70542
- failOnUnguardedMutator: reg.failOnUnguardedMutator
70599
+ failOnUnguardedMutator: strict ? true : reg.failOnUnguardedMutator
70543
70600
  };
70601
+ const requireCoverage = strict ? "COMPLETE" : input.requireCoverage;
70544
70602
  const { tools, report } = (0, tool_registry_js_1.guardToolRegistry)(input.tools, config);
70545
- if (input.requireCoverage !== void 0) {
70546
- const requiredRank = coverageRank(input.requireCoverage);
70603
+ if (requireCoverage !== void 0) {
70604
+ const requiredRank = coverageRank(requireCoverage);
70547
70605
  const actualRank = coverageRank(report.coverage) ?? -1;
70548
70606
  if (requiredRank !== void 0 && actualRank < requiredRank) {
70549
- throw new Error(`withCodeRifts: requireCoverage not met \u2014 registry coverage '${report.coverage}' is weaker than required '${input.requireCoverage}' (strength ordering COMPLETE > PARTIAL > BYPASSED > UNKNOWN). requireCoverage constrains the REGISTRY tool-boundary surface ONLY; a green construction here is NOT a product-level runtime-inescapability guarantee \u2014 composition_assurance.inescapable_runtime stays false until receipt carry-forward and a freshness-safe prior for write-style calls land.`);
70607
+ throw new Error(`withCodeRifts: requireCoverage not met \u2014 registry coverage '${report.coverage}' is weaker than required '${requireCoverage}' (strength ordering COMPLETE > PARTIAL > BYPASSED > UNKNOWN). requireCoverage constrains the REGISTRY tool-boundary surface ONLY; a green construction here is NOT a product-level runtime-inescapability guarantee \u2014 composition_assurance.inescapable_runtime stays false until receipt carry-forward and a freshness-safe prior for write-style calls land.`);
70550
70608
  }
70551
70609
  }
70552
70610
  const compositionInescapableRuntime = report.claim.inescapable_runtime && COMPOSITION_CALL_POLICY_COMPLETE;
@@ -70561,9 +70619,12 @@ var require_with_coderifts = __commonJS({
70561
70619
  if (!freshness_resolver_wired) {
70562
70620
  residuals.push(RESIDUAL_FRESHNESS_NOT_CONFIGURED);
70563
70621
  }
70564
- if (input.requireConditionalWrite === true) {
70622
+ if (input.requireConditionalWrite === true || strict) {
70565
70623
  residuals.push("composition_unconditional_write_under_policy");
70566
70624
  }
70625
+ if (strict) {
70626
+ residuals.push(RESIDUAL_CALLS_OUTSIDE_GUARDED_PATH);
70627
+ }
70567
70628
  const composition_assurance = {
70568
70629
  coverage: "PARTIAL",
70569
70630
  inescapable_runtime: compositionInescapableRuntime,
@@ -71755,7 +71816,8 @@ var require_deploy_gate2 = __commonJS({
71755
71816
  if (enforcement === "ENFORCING") out.push("bypass_open");
71756
71817
  else if (enforcement === "ADVISORY") out.push("deploy_gate_advisory");
71757
71818
  else if (enforcement === "ABSENT") out.push("deploy_path_ungated");
71758
- } else if (change_set_rebound !== true) {
71819
+ }
71820
+ if (change_set_rebound !== true) {
71759
71821
  out.push("change_set_not_rebound");
71760
71822
  }
71761
71823
  return out;
@@ -71793,8 +71855,8 @@ var require_deploy_gate2 = __commonJS({
71793
71855
  if (expected_body_hash != null) requiredContext.expected_body_hash = expected_body_hash;
71794
71856
  }
71795
71857
  const gate = deployGate({ deployTarget: { environment, artifact_id }, receipt, requiredContext });
71796
- const enforcement_inescapable = gate.inescapable_deploy === true;
71797
- const inescapable_deploy = enforcement_inescapable && change_set_rebound === true;
71858
+ const enforcement_inescapable = attested_enforcement === "ENFORCING" && !!(observed_cd_enforcement && observed_cd_enforcement.bypass_possible === false);
71859
+ const inescapable_deploy = gate.inescapable_deploy === true && change_set_rebound === true;
71798
71860
  return {
71799
71861
  deploy_check_status: gate.state,
71800
71862
  reason: gate.reason,
@@ -73285,9 +73347,9 @@ generator_detection: true
73285
73347
  chalk.dim(" (uses YOUR gh credentials; CodeRifts never takes admin rights)."),
73286
73348
  chalk.dim(" To attest cross-layer ENFORCING after wiring: coderifts enforce --apply"),
73287
73349
  chalk.dim(" (dry-run without --apply; does not auto-flip defaults)."),
73288
- chalk.dim(" For agent tool-call guarding with execution-state telemetry: set"),
73289
- chalk.dim(" requireExecutionStateMatch: 'warn' in your guard config"),
73290
- chalk.dim(" (observes drift, does not block) \u2014 see: coderifts agent-setup"),
73350
+ chalk.dim(" For agent tool-call guarding: @coderifts/agent-guard requireExecutionStateMatch"),
73351
+ chalk.dim(" defaults ON (absent\u2192true) \u2014 observed drift STOPS. Opt-down: 'warn' (emit, proceed)"),
73352
+ chalk.dim(" or false (proceed-on-drift) \u2014 see: coderifts agent-setup"),
73291
73353
  chalk.dim(" Agent-using repo? Run: coderifts agent-setup")
73292
73354
  ]);
73293
73355
  module2.exports = {
@@ -99997,7 +100059,8 @@ var require_agent_host_files_embedded = __commonJS({
99997
100059
  ".cursor/rules/coderifts.mdc",
99998
100060
  ".github/copilot-instructions.md",
99999
100061
  "coderifts-langgraph-policy.js",
100000
- "openai-agent-instructions.md"
100062
+ "openai-agent-instructions.md",
100063
+ ".claude/settings.json"
100001
100064
  ]);
100002
100065
  var AGENT_HOST_FILES = Object.freeze({
100003
100066
  "AGENTS.md": '<!-- GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js -->\n\n# CodeRifts agent rules\n\nCall `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\n\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\n\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\n\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\n\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\n\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\n\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\n\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\n\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.\n',
@@ -100005,7 +100068,8 @@ var require_agent_host_files_embedded = __commonJS({
100005
100068
  ".cursor/rules/coderifts.mdc": '---\ndescription: CodeRifts API governance \u2014 when to preflight and how to branch\nglobs:\nalwaysApply: true\n---\n\n<!-- GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js -->\n\n# CodeRifts\n\nCall `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\n\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\n\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\n\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\n\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\n\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\n\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\n\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\n\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.\n',
100006
100069
  ".github/copilot-instructions.md": '<!-- GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js -->\n\n# CodeRifts instructions for GitHub Copilot\n\nCall `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\n\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\n\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\n\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\n\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\n\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\n\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\n\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\n\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.\n',
100007
100070
  "coderifts-langgraph-policy.js": '// GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js\n// System policy string for a LangGraph (or similar) agent. Content is generated;\n// identical rule sentences to AGENTS.md / other formats.\n\'use strict\';\n\nmodule.exports = "Call `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\\n\\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\\n\\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\\n\\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\\n\\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not \\"proceed with caution\\" without monitoring.\\n\\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\\n\\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\\n\\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\\n\\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\\n\\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\\n\\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.";\n',
100008
- "openai-agent-instructions.md": '<!-- GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js -->\n\n# CodeRifts agent instructions\n\nCall `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\n\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\n\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\n\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\n\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\n\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\n\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\n\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\n\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.\n'
100071
+ "openai-agent-instructions.md": '<!-- GENERATED from CodeRifts agent-host-rule (single source). Do not edit rule sentences by hand; run: node scripts/generate-agent-host-files.js -->\n\n# CodeRifts agent instructions\n\nCall `preflight_change_set` before merge, deploy, publish, or tool registration when a contract artifact changed (OpenAPI/Swagger, GraphQL, gRPC/protobuf, AsyncAPI, MCP manifest, or agent tool schemas).\n\nSend the complete base-to-head change set: every changed contract artifact with full before and after content. Do not send a single-file subset when other contract files also change.\n\nBranch on `execution_action` only. Do not branch on `decision` and do not branch on `safe_for_agent` (not_for_control_flow_use_execution_action). Canonical `execution_action` values: CONTINUE, CONTINUE_WITH_MONITORING, REQUEST_APPROVAL, STOP.\n\nAn unrecognised `execution_action` is not permission: fail closed (halt or re-preflight). Well-known code: `not_permission_fail_closed`.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call CodeRifts tools for a documentation-only change (README, guides, comments) with no contract artifact content change.\n\nIf you already hold a chain receipt and only need authenticity/lifecycle: `verify_receipt`. If you need a past decision by id: `get_decision_details`. Neither replaces preflight for a new change set.\n\nA receipt is operation-scoped: a merge receipt never authorizes a deploy or publish. When the operation changes, you MUST call `preflight_change_set` again with the new `context.operation` \u2014 do not decline, re-preflight.\n\nA stale or superseded receipt on a changed head requires a NEW preflight \u2014 `verify_receipt` cannot re-diff.\n\nFor mutating tools, put only the guarded version in the agent\'s tool table; keep the raw handler host-only and unreachable from that table. How you name tools is yours \u2014 this is a reachability property, not a product rename of host tools. CodeRifts cannot see or stop a raw call the host makes outside the table it returns; adopt this as a host convention, not as a guarantee from the package.\n\nCodeRifts reports a governance decision and `execution_action`; it does not by itself block merges. Blocking requires separate repository configuration (required status checks, enforcement) that this rule file does not set.\n',
100072
+ ".claude/settings.json": '{\n "hooks": {\n "PreToolUse": [\n {\n "matcher": "Write|Edit|MultiEdit",\n "hooks": [\n {\n "type": "command",\n "command": "coderifts claude-hook",\n "timeout": 60\n }\n ]\n }\n ]\n }\n}\n'
100009
100073
  });
100010
100074
  module2.exports = { AGENT_HOST_FILES, AGENT_HOST_PATHS };
100011
100075
  }
@@ -100145,27 +100209,52 @@ ${USAGE}` };
100145
100209
  for (const line of AGENT_SETUP_WARN_TELEMETRY_LINES) {
100146
100210
  log(line);
100147
100211
  }
100212
+ if (summary.skipped.includes(".claude/settings.json")) {
100213
+ for (const line of AGENT_SETUP_HOOK_SKIP_LINES) {
100214
+ log(line);
100215
+ }
100216
+ } else if (summary.written.includes(".claude/settings.json")) {
100217
+ for (const line of AGENT_SETUP_HOOK_WIRE_LINES) {
100218
+ log(line);
100219
+ }
100220
+ }
100148
100221
  }
100149
100222
  if (doExit) process.exit(0);
100150
100223
  return { exitCode: 0, code: "OK", outDir, ...summary };
100151
100224
  }
100152
100225
  var AGENT_SETUP_WARN_TELEMETRY_LINES = Object.freeze([
100153
- chalk.bold(" Opt-in: execution-state warn telemetry (does not block)"),
100154
- chalk.dim(" Set requireExecutionStateMatch: 'warn' in @coderifts/agent-guard"),
100155
- chalk.dim(" (or withCodeRifts) config. Default remains OFF \u2014 this is opt-in only."),
100156
- chalk.dim(" What it emits (then PROCEEDS \u2014 never blocks in warn mode):"),
100226
+ chalk.bold(" Execution-state recheck (guard@8): DEFAULT ON"),
100227
+ chalk.dim(" requireExecutionStateMatch absent/true \u2192 enforce: mismatch \u2192 EXECUTION_STATE_DRIFT"),
100228
+ chalk.dim(" (factory does not run); missing fingerprint/artifacts \u2192 EXECUTION_STATE_UNMEASURABLE (STOP)."),
100229
+ chalk.dim(" Opt-down: requireExecutionStateMatch: 'warn' (emit, then run unenforced on mismatch)"),
100230
+ chalk.dim(" or false (no recheck \u2014 v7 proceed-on-drift). Must be set explicitly."),
100231
+ chalk.dim(" Warn emits (then PROCEEDS \u2014 never blocks in warn mode):"),
100157
100232
  chalk.dim(" \xB7 real drift \u2192 execution_state_drift_observed (loud)"),
100158
100233
  chalk.dim(" \xB7 missing artifacts/fingerprint \u2192 execution_state_unmeasurable (quiet)"),
100159
- chalk.dim(" Enforce later with requireExecutionStateMatch: true only when ready."),
100160
100234
  chalk.dim(" Merge gate block is separate: coderifts setup-required-check --apply")
100161
100235
  ]);
100236
+ var AGENT_SETUP_HOOK_WIRE_LINES = Object.freeze([
100237
+ chalk.bold(" Claude Code hook (.claude/settings.json): fail-closed PreToolUse"),
100238
+ chalk.dim(" matcher Write|Edit|MultiEdit \u2192 coderifts claude-hook (exit 2 = BLOCK)."),
100239
+ chalk.dim(" Missing key / API down / unreadable spec \u2192 exit 2 (enforce_indeterminate), not ALLOW."),
100240
+ chalk.dim(" Opt-out: CODERIFTS_ADVISORY=1|true restores soft-allow when governance could not run."),
100241
+ chalk.dim(" CODERIFTS_STRICT=1 cannot be weakened by ADVISORY.")
100242
+ ]);
100243
+ var AGENT_SETUP_HOOK_SKIP_LINES = Object.freeze([
100244
+ chalk.yellow(" .claude/settings.json exists \u2014 left unchanged (installer does not merge)."),
100245
+ chalk.dim(" --force replaces the ENTIRE file (not just the PreToolUse hook)."),
100246
+ chalk.dim(" To wire fail-closed: add PreToolUse \u2192 coderifts claude-hook by hand, or --force."),
100247
+ chalk.dim(" Opt-out if wired: CODERIFTS_ADVISORY=1|true. CODERIFTS_STRICT cannot weaken.")
100248
+ ]);
100162
100249
  module2.exports = {
100163
100250
  runAgentSetup,
100164
100251
  parseAgentSetupArgs,
100165
100252
  AGENT_HOST_FILES,
100166
100253
  AGENT_HOST_PATHS,
100167
100254
  USAGE,
100168
- AGENT_SETUP_WARN_TELEMETRY_LINES
100255
+ AGENT_SETUP_WARN_TELEMETRY_LINES,
100256
+ AGENT_SETUP_HOOK_WIRE_LINES,
100257
+ AGENT_SETUP_HOOK_SKIP_LINES
100169
100258
  };
100170
100259
  }
100171
100260
  });
@@ -101133,21 +101222,23 @@ var require_claude_hook = __commonJS({
101133
101222
  "configured contract spec path. Reads JSON context from STDIN.",
101134
101223
  "",
101135
101224
  "Exit map (Claude Code semantics \u2014 get this exactly right):",
101136
- " 2 BLOCK \u2014 tool call cancelled (decision BLOCK/STOP or unrecognised execution_action)",
101137
- " 0 allow \u2014 or soft-skip (see soft contracts)",
101225
+ " 2 BLOCK \u2014 tool call cancelled (BLOCK/STOP, REQUEST_APPROVAL, unrecognised action,",
101226
+ " governance could not run, CONTINUE_WITH_MONITORING without a wired sink)",
101227
+ " 0 allow \u2014 CONTINUE, or CWM with a host-asserted monitoring sink, or non-contract skip",
101138
101228
  " 1 NEVER used for security deny (Claude treats exit 1 as non-blocking; action proceeds)",
101139
101229
  "",
101140
- "Soft contracts DEFAULT (exit 0 + one-line stderr \u2014 do not brick the session):",
101141
- " no API key (git config coderifts.apiKey / coderifts login / CODERIFTS_API_KEY)",
101142
- " unparseable/unknown stdin shape",
101143
- " file_path is not the configured spec path",
101144
- " Edit/MultiEdit cannot be applied cleanly to disk content",
101145
- " API unreachable / network error / disk unreadable",
101230
+ "Fail-closed DEFAULT (exit 2, reason enforce_indeterminate) when governance could not run:",
101231
+ " no API key / API unreachable / disk unreadable / edit-apply failure on the spec path",
101232
+ " Absence of a key is not permission. Explicit opt-out: CODERIFTS_ADVISORY=1|true.",
101233
+ "",
101234
+ "Still exit 0 (nothing to govern): unparseable stdin, missing file_path, non-spec path,",
101235
+ "identical content.",
101146
101236
  "",
101147
- "CODERIFTS_STRICT=1|true (opt-in): enforcement-relevant soft sites \u2192 EXIT 2 (BLOCK).",
101148
- " Hardens: no API key, API unreachable, disk unreadable, edit-apply failure on the spec.",
101149
- " Still soft: non-spec path, identical content, bad stdin, WARN (governance already ran).",
101150
- " Host-side choice for CI/prod \u2014 not a package-level guarantee.",
101237
+ "CONTINUE_WITH_MONITORING: allow only if the host asserts a sink",
101238
+ " (CODERIFTS_MONITORING_SINK_WIRED=1|true or git config coderifts.monitoringSinkWired).",
101239
+ " Host claim \u2014 not delivery proof (same class as GuardConfig.monitoringSinkWired).",
101240
+ "",
101241
+ "CODERIFTS_STRICT=1|true: same fail-closed as default; cannot be weakened by ADVISORY.",
101151
101242
  "",
101152
101243
  "Spec path (same source as git pre-push hook):",
101153
101244
  " git config coderifts.specPath (default: api/openapi.yaml)",
@@ -101158,21 +101249,83 @@ var require_claude_hook = __commonJS({
101158
101249
  "",
101159
101250
  "Push-time equivalent: coderifts hook install (git exit 1 on BLOCK)."
101160
101251
  ].join("\n");
101161
- function isStrictMode(env = process.env) {
101162
- const v = env && env.CODERIFTS_STRICT;
101252
+ function isEnvFlag(env, name) {
101253
+ const v = env && env[name];
101163
101254
  if (v == null || v === "") return false;
101164
101255
  const s = String(v).trim().toLowerCase();
101165
101256
  return s === "1" || s === "true";
101166
101257
  }
101167
- function softOrStrictBlock({ strict, reason, softMsg, why, errLog }) {
101258
+ function isStrictMode(env = process.env) {
101259
+ return isEnvFlag(env, "CODERIFTS_STRICT");
101260
+ }
101261
+ function isAdvisoryMode(env = process.env) {
101262
+ return isEnvFlag(env, "CODERIFTS_ADVISORY");
101263
+ }
101264
+ function isMonitoringSinkWired({ env = process.env, deps = {}, result = null } = {}) {
101265
+ if (deps && deps.monitoringSinkWired === true) return true;
101266
+ if (isEnvFlag(env, "CODERIFTS_MONITORING_SINK_WIRED")) return true;
101267
+ const git = readGitConfig("coderifts.monitoringSinkWired", deps);
101268
+ if (git) {
101269
+ const s = String(git).trim().toLowerCase();
101270
+ if (s === "1" || s === "true") return true;
101271
+ }
101272
+ if (result && result.monitoringSinkWired === true) return true;
101273
+ const dr = result && result.decision_result;
101274
+ if (dr && typeof dr === "object" && dr.monitoringSinkWired === true) return true;
101275
+ return false;
101276
+ }
101277
+ var HOOK_TRIGGER_SOURCE = "claude_hook";
101278
+ function extractFingerprint(result) {
101279
+ const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
101280
+ const dr = d.decision_result && typeof d.decision_result === "object" ? d.decision_result : {};
101281
+ const fp = dr.fingerprint || d.fingerprint || d.verdict_fingerprint || d.input_fingerprint;
101282
+ return typeof fp === "string" && fp.length > 0 ? fp : void 0;
101283
+ }
101284
+ function hookSessionId(env) {
101285
+ const fromEnv = env && env.CODERIFTS_SESSION_ID && String(env.CODERIFTS_SESSION_ID).trim();
101286
+ if (fromEnv) return fromEnv;
101287
+ if (!hookSessionId._gen) hookSessionId._gen = `hook-${process.pid}-${Date.now()}`;
101288
+ return hookSessionId._gen;
101289
+ }
101290
+ function appendGuardEventLog(env, event, deps = {}) {
101291
+ const p = env && env.CODERIFTS_GUARD_EVENT_LOG;
101292
+ if (p == null || String(p).trim() === "") return;
101293
+ const errLog = deps.errLog || ((m) => console.error(String(m)));
101294
+ const append = deps.appendFileSync || ((file, data) => fs.appendFileSync(file, data));
101295
+ try {
101296
+ append(String(p).trim(), JSON.stringify(event) + "\n");
101297
+ } catch (err) {
101298
+ try {
101299
+ errLog(`CodeRifts claude-hook: CODERIFTS_GUARD_EVENT_LOG write failed (${err && err.message})`);
101300
+ } catch {
101301
+ }
101302
+ }
101303
+ }
101304
+ function failClosedOrAdvisory({ advisory, strict, site, softMsg, why, errLog }) {
101305
+ if (advisory && !strict) {
101306
+ errLog(`${softMsg} (CODERIFTS_ADVISORY)`);
101307
+ return { exitCode: 0, reason: site, site };
101308
+ }
101168
101309
  if (strict) {
101169
101310
  errLog(
101170
101311
  `CodeRifts claude-hook: CODERIFTS_STRICT: governance could not run (${why}) \u2014 blocking. Set a key / restore network, or unset CODERIFTS_STRICT.`
101171
101312
  );
101172
- return { exitCode: 2, reason, strictBlocked: true };
101313
+ return { exitCode: 2, reason: "enforce_indeterminate", site, strictBlocked: true };
101173
101314
  }
101174
- errLog(softMsg);
101175
- return { exitCode: 0, reason };
101315
+ errLog(
101316
+ `CodeRifts claude-hook: governance could not run (${why}) \u2014 blocking. Set CODERIFTS_ADVISORY=1 to allow without governance (explicit opt-out).`
101317
+ );
101318
+ return { exitCode: 2, reason: "enforce_indeterminate", site };
101319
+ }
101320
+ function softOrStrictBlock({ strict, reason, softMsg, why, errLog }) {
101321
+ return failClosedOrAdvisory({
101322
+ advisory: false,
101323
+ strict: !!strict,
101324
+ site: reason,
101325
+ softMsg,
101326
+ why,
101327
+ errLog
101328
+ });
101176
101329
  }
101177
101330
  function readGitConfig(key, deps = {}) {
101178
101331
  const run = deps.execSync || execSync;
@@ -101279,7 +101432,7 @@ var require_claude_hook = __commonJS({
101279
101432
  return { ok: false, reason: `unsupported tool_name for content derive: ${name}` };
101280
101433
  }
101281
101434
  function mapDecisionSeverity(result) {
101282
- const d = result && typeof result === "object" ? result : {};
101435
+ const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
101283
101436
  let ea = null;
101284
101437
  const dr = d.decision_result;
101285
101438
  if (dr && typeof dr === "object" && typeof dr.execution_action === "string") {
@@ -101287,20 +101440,33 @@ var require_claude_hook = __commonJS({
101287
101440
  } else if (typeof d.execution_action === "string") {
101288
101441
  ea = d.execution_action;
101289
101442
  }
101443
+ const hasDecision = d.omega_decision != null && d.omega_decision !== "" || d.decision != null && d.decision !== "" || dr && typeof dr === "object" && dr.execution_action;
101444
+ if (d.error && !hasDecision && (ea == null || ea === "")) {
101445
+ return {
101446
+ severity: "INDETERMINATE",
101447
+ decision: null,
101448
+ decisionId: null,
101449
+ executionAction: null
101450
+ };
101451
+ }
101290
101452
  let severity;
101291
101453
  if (ea != null && ea !== "") {
101292
101454
  if (!CLOSED_ACTIONS.has(ea)) {
101293
101455
  severity = "UNKNOWN";
101294
- } else if (ea === "CONTINUE" || ea === "CONTINUE_WITH_MONITORING") {
101456
+ } else if (ea === "CONTINUE") {
101295
101457
  severity = "ALLOW";
101458
+ } else if (ea === "CONTINUE_WITH_MONITORING") {
101459
+ severity = "MONITOR";
101296
101460
  } else if (ea === "STOP") {
101297
101461
  severity = "BLOCK";
101298
101462
  } else {
101299
101463
  severity = "REQUIRE_APPROVAL";
101300
101464
  }
101301
101465
  } else {
101302
- const od = d.omega_decision || d.decision || "ALLOW";
101303
- if (od === "BLOCK") severity = "BLOCK";
101466
+ const od = d.omega_decision || d.decision;
101467
+ if (od == null || od === "") {
101468
+ severity = "INDETERMINATE";
101469
+ } else if (od === "BLOCK") severity = "BLOCK";
101304
101470
  else if (od === "REQUIRE_APPROVAL") severity = "REQUIRE_APPROVAL";
101305
101471
  else if (od === "WARN") severity = "WARN";
101306
101472
  else severity = "ALLOW";
@@ -101309,7 +101475,7 @@ var require_claude_hook = __commonJS({
101309
101475
  const decisionId = dr && dr.decision_id || d.decision_id || null;
101310
101476
  return {
101311
101477
  severity,
101312
- decision: decision != null ? String(decision) : null,
101478
+ decision: decision != null && decision !== "" ? String(decision) : null,
101313
101479
  decisionId: decisionId != null ? String(decisionId) : null,
101314
101480
  executionAction: ea
101315
101481
  };
@@ -101329,31 +101495,66 @@ var require_claude_hook = __commonJS({
101329
101495
  const cwd = deps.cwd || process.cwd();
101330
101496
  const env = deps.env || process.env;
101331
101497
  const strict = isStrictMode(env);
101498
+ const advisory = isAdvisoryMode(env);
101499
+ const sessionId = hookSessionId(env);
101500
+ const logEv = (partial) => {
101501
+ appendGuardEventLog(env, {
101502
+ at: (/* @__PURE__ */ new Date()).toISOString(),
101503
+ sessionId,
101504
+ trigger_source: HOOK_TRIGGER_SOURCE,
101505
+ ...partial
101506
+ }, { errLog, appendFileSync: deps.appendFileSync });
101507
+ };
101508
+ let tool = null;
101509
+ let filePathKnown = null;
101510
+ const emitTerminal = (r) => {
101511
+ const extra = {
101512
+ tool: tool || void 0,
101513
+ path: filePathKnown || void 0,
101514
+ decision: r.decision != null ? r.decision : void 0,
101515
+ decisionId: r.decisionId != null ? r.decisionId : void 0
101516
+ };
101517
+ if (r.exitCode === 2) {
101518
+ logEv({ type: "hook_blocked", exit: 2, ...extra, cause: r.reason || r.site });
101519
+ } else if (r.exitCode === 0 && r.site && advisory && !strict) {
101520
+ logEv({ type: "hook_advisory_passthrough", exit: 0, decision: r.decision || null, ...extra, cause: r.site });
101521
+ } else {
101522
+ logEv({ type: "hook_continue", exit: 0, ...extra, cause: r.reason });
101523
+ }
101524
+ return r;
101525
+ };
101332
101526
  const apiKey = resolveApiKey({ ...deps, cwd, env });
101333
101527
  if (!apiKey) {
101334
- return softOrStrictBlock({
101528
+ logEv({ type: "preflight_unavailable", cause: "missing_api_key" });
101529
+ return emitTerminal(failClosedOrAdvisory({
101530
+ advisory,
101335
101531
  strict,
101336
- reason: "missing_api_key",
101532
+ site: "missing_api_key",
101337
101533
  why: "no API key",
101338
101534
  softMsg: "CodeRifts claude-hook: no API key (coderifts login / CODERIFTS_API_KEY / git config coderifts.apiKey) \u2014 allowing",
101339
101535
  errLog
101340
- });
101536
+ }));
101341
101537
  }
101342
101538
  const raw = typeof options.stdin === "string" ? options.stdin : readStdin();
101343
101539
  const parsed = parseStdinJson(raw);
101344
101540
  if (!parsed.ok) {
101345
101541
  errLog(`CodeRifts claude-hook: ${parsed.reason} \u2014 allowing (soft; never block on parse gap)`);
101346
- return { exitCode: 0, reason: "stdin_unparseable" };
101542
+ logEv({ type: "detection_skip", signals: ["stdin_unparseable"] });
101543
+ return emitTerminal({ exitCode: 0, reason: "stdin_unparseable" });
101347
101544
  }
101348
101545
  const { toolName, toolInput } = parsed;
101546
+ tool = toolName;
101349
101547
  const filePath = toolInput.file_path || toolInput.filePath || toolInput.path;
101350
101548
  if (!filePath || typeof filePath !== "string") {
101351
101549
  errLog("CodeRifts claude-hook: tool_input.file_path missing \u2014 allowing (soft)");
101352
- return { exitCode: 0, reason: "missing_file_path" };
101550
+ logEv({ type: "detection_skip", signals: ["missing_file_path"], tool });
101551
+ return emitTerminal({ exitCode: 0, reason: "missing_file_path" });
101353
101552
  }
101553
+ filePathKnown = filePath;
101354
101554
  const specPath = resolveSpecPath({ ...deps, cwd });
101355
101555
  if (!isSpecPath(filePath, specPath)) {
101356
- return { exitCode: 0, reason: "not_spec_path" };
101556
+ logEv({ type: "detection_skip", signals: ["not_spec_path"], tool, path: filePath });
101557
+ return emitTerminal({ exitCode: 0, reason: "not_spec_path" });
101357
101558
  }
101358
101559
  const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
101359
101560
  let diskBefore = "";
@@ -101361,28 +101562,34 @@ var require_claude_hook = __commonJS({
101361
101562
  try {
101362
101563
  diskBefore = readFile(absPath, "utf8");
101363
101564
  } catch (e) {
101364
- return softOrStrictBlock({
101565
+ logEv({ type: "preflight_unavailable", cause: "disk_unreadable", tool, path: filePath });
101566
+ return emitTerminal(failClosedOrAdvisory({
101567
+ advisory,
101365
101568
  strict,
101366
- reason: "disk_unreadable",
101569
+ site: "disk_unreadable",
101367
101570
  why: `cannot read contract file ${absPath}`,
101368
101571
  softMsg: `CodeRifts claude-hook: cannot read ${absPath} \u2014 allowing (soft)`,
101369
101572
  errLog
101370
- });
101573
+ }));
101371
101574
  }
101372
101575
  }
101373
101576
  const derived = deriveAfterContent(toolName, toolInput, diskBefore);
101374
101577
  if (!derived.ok) {
101375
- return softOrStrictBlock({
101578
+ logEv({ type: "preflight_unavailable", cause: "edit_apply_failed", tool, path: filePath });
101579
+ return emitTerminal(failClosedOrAdvisory({
101580
+ advisory,
101376
101581
  strict,
101377
- reason: "edit_apply_failed",
101582
+ site: "edit_apply_failed",
101378
101583
  why: derived.reason,
101379
101584
  softMsg: `CodeRifts claude-hook: ${derived.reason} \u2014 allowing (soft; never guess edit apply)`,
101380
101585
  errLog
101381
- });
101586
+ }));
101382
101587
  }
101383
101588
  if (diskBefore === derived.after) {
101384
- return { exitCode: 0, reason: "identical" };
101589
+ logEv({ type: "detection_skip", signals: ["identical"], tool, path: filePath });
101590
+ return emitTerminal({ exitCode: 0, reason: "identical" });
101385
101591
  }
101592
+ logEv({ type: "preflight_start", tool, path: filePath });
101386
101593
  let result;
101387
101594
  try {
101388
101595
  result = await authorize(diskBefore, derived.after, apiKey, {
@@ -101391,47 +101598,95 @@ var require_claude_hook = __commonJS({
101391
101598
  });
101392
101599
  } catch (e) {
101393
101600
  const msg = e && e.message ? String(e.message) : "request failed";
101394
- return softOrStrictBlock({
101601
+ logEv({ type: "preflight_unavailable", cause: "api_unreachable", tool, path: filePath });
101602
+ return emitTerminal(failClosedOrAdvisory({
101603
+ advisory,
101395
101604
  strict,
101396
- reason: "api_unreachable",
101605
+ site: "api_unreachable",
101397
101606
  why: `API unreachable: ${msg}`,
101398
101607
  softMsg: `CodeRifts claude-hook: API unreachable (${msg}) \u2014 allowing (soft; availability must not brick the editor)`,
101399
101608
  errLog
101400
- });
101609
+ }));
101401
101610
  }
101402
101611
  const mapped = mapDecisionSeverity(result);
101612
+ const fingerprint = extractFingerprint(result);
101403
101613
  const idPart = mapped.decisionId ? ` decision_id=${mapped.decisionId}` : "";
101404
101614
  const decPart = mapped.decision ? ` decision=${mapped.decision}` : "";
101405
101615
  const eaPart = mapped.executionAction ? ` execution_action=${mapped.executionAction}` : "";
101616
+ const actionForLog = mapped.executionAction || (mapped.severity === "BLOCK" || mapped.severity === "UNKNOWN" || mapped.severity === "INDETERMINATE" ? "STOP" : mapped.severity === "REQUIRE_APPROVAL" ? "REQUEST_APPROVAL" : mapped.severity === "MONITOR" || mapped.severity === "WARN" ? "CONTINUE_WITH_MONITORING" : "CONTINUE");
101617
+ logEv({
101618
+ type: "preflight_result",
101619
+ action: actionForLog,
101620
+ decisionId: mapped.decisionId || void 0,
101621
+ fingerprint,
101622
+ tool,
101623
+ path: filePath
101624
+ });
101625
+ if (mapped.severity === "INDETERMINATE") {
101626
+ errLog(
101627
+ "CodeRifts claude-hook: BLOCKED (indeterminate response \u2014 no execution_action and no decision; not permission)"
101628
+ );
101629
+ return emitTerminal({
101630
+ exitCode: 2,
101631
+ reason: "indeterminate",
101632
+ severity: "INDETERMINATE",
101633
+ decision: mapped.decision,
101634
+ decisionId: mapped.decisionId
101635
+ });
101636
+ }
101406
101637
  if (mapped.severity === "BLOCK" || mapped.severity === "UNKNOWN") {
101407
101638
  errLog(
101408
101639
  `CodeRifts claude-hook: BLOCKED${decPart}${eaPart}${idPart}` + (mapped.severity === "UNKNOWN" ? " (unrecognised execution_action)" : "")
101409
101640
  );
101410
- return {
101641
+ return emitTerminal({
101411
101642
  exitCode: 2,
101412
101643
  reason: mapped.severity === "UNKNOWN" ? "unknown_action" : "block",
101413
101644
  severity: mapped.severity,
101414
101645
  decision: mapped.decision,
101415
101646
  decisionId: mapped.decisionId
101416
- };
101647
+ });
101417
101648
  }
101418
- if (mapped.severity === "REQUIRE_APPROVAL" || mapped.severity === "WARN") {
101419
- errLog(`CodeRifts claude-hook: WARNING ${mapped.severity}${decPart}${eaPart}${idPart}`);
101420
- return {
101421
- exitCode: 0,
101422
- reason: "warn",
101649
+ if (mapped.severity === "REQUIRE_APPROVAL") {
101650
+ errLog(
101651
+ `CodeRifts claude-hook: BLOCKED approval_required${decPart}${eaPart}${idPart}`
101652
+ );
101653
+ return emitTerminal({
101654
+ exitCode: 2,
101655
+ reason: "approval_required",
101423
101656
  severity: mapped.severity,
101424
101657
  decision: mapped.decision,
101425
101658
  decisionId: mapped.decisionId
101426
- };
101659
+ });
101427
101660
  }
101428
- return {
101661
+ if (mapped.severity === "MONITOR" || mapped.severity === "WARN") {
101662
+ const sinkWired = isMonitoringSinkWired({ env, deps, result });
101663
+ if (!sinkWired) {
101664
+ errLog(
101665
+ `CodeRifts claude-hook: BLOCKED monitoring_unwired${decPart}${eaPart}${idPart} \u2014 CONTINUE_WITH_MONITORING requires CODERIFTS_MONITORING_SINK_WIRED=1 (host assertion; not delivery proof)`
101666
+ );
101667
+ return emitTerminal({
101668
+ exitCode: 2,
101669
+ reason: "monitoring_unwired",
101670
+ severity: mapped.severity === "WARN" ? "MONITOR" : mapped.severity,
101671
+ decision: mapped.decision,
101672
+ decisionId: mapped.decisionId
101673
+ });
101674
+ }
101675
+ return emitTerminal({
101676
+ exitCode: 0,
101677
+ reason: "allow",
101678
+ severity: mapped.severity === "WARN" ? "MONITOR" : mapped.severity,
101679
+ decision: mapped.decision,
101680
+ decisionId: mapped.decisionId
101681
+ });
101682
+ }
101683
+ return emitTerminal({
101429
101684
  exitCode: 0,
101430
101685
  reason: "allow",
101431
101686
  severity: "ALLOW",
101432
101687
  decision: mapped.decision,
101433
101688
  decisionId: mapped.decisionId
101434
- };
101689
+ });
101435
101690
  }
101436
101691
  module2.exports = {
101437
101692
  runClaudeHook,
@@ -101442,11 +101697,18 @@ var require_claude_hook = __commonJS({
101442
101697
  resolveApiKey,
101443
101698
  resolveSpecPath,
101444
101699
  readGitConfig,
101700
+ isEnvFlag,
101445
101701
  isStrictMode,
101702
+ isAdvisoryMode,
101703
+ isMonitoringSinkWired,
101704
+ failClosedOrAdvisory,
101446
101705
  softOrStrictBlock,
101447
101706
  DEFAULT_SPEC_PATH,
101448
101707
  CLOSED_ACTIONS,
101449
- USAGE
101708
+ USAGE,
101709
+ appendGuardEventLog,
101710
+ extractFingerprint,
101711
+ HOOK_TRIGGER_SOURCE
101450
101712
  };
101451
101713
  }
101452
101714
  });
@@ -103446,7 +103708,7 @@ ${USAGE}
103446
103708
  }
103447
103709
  });
103448
103710
  program.command("claude-hook").description(
103449
- "Claude Code PreToolUse gate: block contract-touching Write|Edit|MultiEdit on BLOCK/STOP (exit 2). Soft-allow when unconfigured/unparseable/API down."
103711
+ "Claude Code PreToolUse gate: block contract-touching Write|Edit|MultiEdit on BLOCK/STOP/REQUEST_APPROVAL (exit 2). Fail-closed when governance cannot run; CODERIFTS_ADVISORY=1 is the explicit soft opt-out."
103450
103712
  ).addHelpText("after", () => {
103451
103713
  const { USAGE } = require_claude_hook();
103452
103714
  return `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderifts",
3
- "version": "3.4.0",
3
+ "version": "4.1.0",
4
4
  "description": "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
5
5
  "author": "CodeRifts <hello@coderifts.com>",
6
6
  "license": "MIT",
@@ -13,6 +13,7 @@
13
13
  "bin/",
14
14
  "scripts/",
15
15
  "README.md",
16
+ "CHANGELOG.md",
16
17
  "corpus/"
17
18
  ],
18
19
  "keywords": [
@@ -44,7 +45,7 @@
44
45
  "test": "node --test test/*.test.js"
45
46
  },
46
47
  "dependencies": {
47
- "@coderifts/agent-guard": "^6.4.0",
48
+ "@coderifts/agent-guard": "^8.1.1",
48
49
  "chalk": "^4.1.2",
49
50
  "cli-table3": "^0.6.4",
50
51
  "commander": "^12.0.0",
@@ -5,18 +5,19 @@
5
5
  * CLI release gate (prepublishOnly): refuse to publish if the declared
6
6
  * @coderifts/agent-guard range floors below the minimum acceptable major.
7
7
  *
8
- * Honest scope: compares the dependency *range floor* (e.g. ^6.4.06) to a
8
+ * Honest scope: compares the dependency *range floor* (e.g. ^8.1.18) to a
9
9
  * pinned constant. Does not call npm registry. Optional: if node_modules has a
10
10
  * resolved package, also fail when its major is below the floor (stale install).
11
11
  *
12
12
  * Why: evening audit 2.4 — CLI sat on ^1.6.0 while published guard is 6.x.
13
+ * 4.0.0: floor raised to 8 (deployGate inescapable_deploy includes fingerprint rebound).
13
14
  */
14
15
 
15
16
  const fs = require('fs');
16
17
  const path = require('path');
17
18
 
18
19
  /** Minimum acceptable major for @coderifts/agent-guard (published line). */
19
- const MIN_GUARD_MAJOR = 6;
20
+ const MIN_GUARD_MAJOR = 8;
20
21
  const DEP = '@coderifts/agent-guard';
21
22
 
22
23
  function fail(msg) {