coderifts 4.1.0 → 4.2.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 CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.1.1
4
+
5
+ ### Fixed
6
+
7
+ - **P0-2: `mapDecisionSeverity` never maps missing `execution_action` to ALLOW.**
8
+ Missing EA in any body shape → `INDETERMINATE` (exit 2). Legacy decision→severity
9
+ map is allowed only when `decision_spec_version === "1.0"` on a non-v2 body
10
+ (no `decision_result`, no `preflight_mode`). Top-level-only `{decision:"ALLOW"}`
11
+ without that pin is not permission.
12
+ - **P0-3: parse-gap is fail-closed by default.** Unparseable stdin and missing
13
+ `tool_input.file_path` → **exit 2**, stderr
14
+ `unparseable input — refusing (fail-closed); set CODERIFTS_ADVISORY=1 to soften`,
15
+ JSONL `hook_blocked` with `cause: stdin_unparseable|missing_file_path`.
16
+ `CODERIFTS_ADVISORY=1` restores exit 0. API-key-before-parse order is unchanged
17
+ (garbage stdin + no key → `missing_api_key`). Non-spec Write stays exit 0.
18
+ Unknown-tool-on-spec was already exit 2.
19
+
3
20
  ## 4.1.0
4
21
 
5
22
  ### Added
package/README.md CHANGED
@@ -54,11 +54,12 @@ A `coderifts-report.json` file is always saved to the current directory.
54
54
 
55
55
  Interactive configuration generator. Creates a `.coderifts.yml` file with industry-specific presets:
56
56
 
57
- - Default (recommended)
58
- - Fintech / Payments
59
- - Healthcare / HIPAA
60
- - Platform / API-First
61
- - E-commerce
57
+ - `startup-lean`
58
+ - `growth-balanced`
59
+ - `fintech-strict`
60
+ - `public-api-safe`
61
+ - `microservices-internal`
62
+ - `ai-agent-platform`
62
63
 
63
64
  ### `coderifts login`
64
65
 
@@ -189,6 +190,13 @@ After upgrading the `coderifts` CLI, re-run `coderifts hook install` so the
189
190
  installed hook matches the package (already-installed hooks are not
190
191
  auto-updated).
191
192
 
193
+ 2-minute Claude Code path: `coderifts hook install --claude` writes a
194
+ PreToolUse hook (`matcher: Write|Edit|MultiEdit` → `coderifts claude-hook`)
195
+ into `.claude/settings.json` (JSON-merge, backup first, idempotent).
196
+ `--global` writes `~/.claude/settings.json`. `--log <path>` embeds
197
+ `CODERIFTS_GUARD_EVENT_LOG` and `CODERIFTS_SESSION_ID` in the command.
198
+ Unparseable settings JSON → refuse, no write.
199
+
192
200
  ### Claude Code PreToolUse (`coderifts claude-hook`) — ID824
193
201
 
194
202
  Tool-call-time gate for **Claude Code**: blocks contract-touching `Write` /
@@ -198,13 +206,13 @@ Tool-call-time gate for **Claude Code**: blocks contract-touching `Write` /
198
206
 
199
207
  | Exit | Meaning |
200
208
  |------|---------|
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) |
209
+ | **2** | **BLOCK** — tool call cancelled; stderr is shown to the model. Default fail-closed: no key, API down, unreadable spec, parse-gap, missing `file_path` |
210
+ | **0** | Allow (`CONTINUE`), or skip (non-spec path / identical content). `CODERIFTS_ADVISORY=1` restores soft-allow on could-not-run sites |
203
211
  | **1** | **Never used for deny** — Claude treats exit 1 as non-blocking (action proceeds) |
204
212
 
205
213
  Push-time equivalent (git exit **1** on BLOCK): `coderifts hook install`.
206
214
 
207
- **Install path:** `npm i -g coderifts` then set key + spec (same as the git hook):
215
+ **Install path:** `coderifts hook install --claude` (or `agent-setup` writes project `.claude/settings.json` when absent). Then set key + spec:
208
216
 
209
217
  ```bash
210
218
  git config coderifts.apiKey 'cr_live_…' # or: coderifts login / CODERIFTS_API_KEY
package/bin/coderifts.js CHANGED
@@ -283,9 +283,16 @@ const hookCmd = program
283
283
  hookCmd
284
284
  .command('install')
285
285
  .description('Install the CodeRifts pre-push hook in the current Git repo')
286
- .action(() => {
286
+ .option('--claude', 'Write Claude Code PreToolUse hook to .claude/settings.json (JSON-merge)')
287
+ .option('--global', 'With --claude: write ~/.claude/settings.json instead of the project file')
288
+ .option('--log <path>', 'With --claude: embed CODERIFTS_GUARD_EVENT_LOG and CODERIFTS_SESSION_ID in the command')
289
+ .action((options) => {
287
290
  const { install } = require('../src/commands/hook');
288
- install();
291
+ install({
292
+ claude: !!options.claude,
293
+ global: !!options.global,
294
+ log: options.log,
295
+ });
289
296
  });
290
297
 
291
298
  hookCmd
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: "4.1.0",
3010
+ version: "4.2.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",
@@ -72976,6 +72976,10 @@ var require_init = __commonJS({
72976
72976
  yaml: `# CodeRifts Policy: Startup Lean
72977
72977
  # For fast-moving teams that want safety without friction.
72978
72978
  # Catches critical breaking changes but stays out of your way.
72979
+ #
72980
+ # Decision Spec 2.0 (comment only \u2014 this file does not set agent protocol):
72981
+ # branch on execution_action (CONTINUE | CONTINUE_WITH_MONITORING | REQUEST_APPROVAL | STOP).
72982
+ # Unknown execution_action \u2192 STOP. Analyze is not permission; authorize is required to act.
72979
72983
 
72980
72984
  failOnBreaking: true
72981
72985
 
@@ -73350,7 +73354,9 @@ generator_detection: true
73350
73354
  chalk.dim(" For agent tool-call guarding: @coderifts/agent-guard requireExecutionStateMatch"),
73351
73355
  chalk.dim(" defaults ON (absent\u2192true) \u2014 observed drift STOPS. Opt-down: 'warn' (emit, proceed)"),
73352
73356
  chalk.dim(" or false (proceed-on-drift) \u2014 see: coderifts agent-setup"),
73353
- chalk.dim(" Agent-using repo? Run: coderifts agent-setup")
73357
+ chalk.dim(" Agent-using repo? Run: coderifts agent-setup"),
73358
+ chalk.dim(" Decision Spec 2.0: branch on execution_action (CONTINUE | CONTINUE_WITH_MONITORING |"),
73359
+ chalk.dim(" REQUEST_APPROVAL | STOP). Unknown execution_action \u2192 STOP. Analyze is not permission.")
73354
73360
  ]);
73355
73361
  module2.exports = {
73356
73362
  init,
@@ -99438,6 +99444,7 @@ var require_hook = __commonJS({
99438
99444
  "use strict";
99439
99445
  var fs = require("fs");
99440
99446
  var path = require("path");
99447
+ var os = require("os");
99441
99448
  var { execSync } = require("child_process");
99442
99449
  var HOOK_MARKER = "# CodeRifts pre-push hook";
99443
99450
  var PRE_PUSH_SCRIPT = `#!/bin/sh
@@ -99698,12 +99705,111 @@ exit 0
99698
99705
  const content = fs.readFileSync(hookPath, "utf-8");
99699
99706
  return content.includes(HOOK_MARKER);
99700
99707
  }
99701
- function install() {
99702
- const gitDir = findGitDir();
99708
+ var CLAUDE_MATCHER = "Write|Edit|MultiEdit";
99709
+ var CLAUDE_HOOK_BIN = "coderifts claude-hook";
99710
+ function claudeSettingsPath({ global: isGlobal, cwd } = {}) {
99711
+ if (isGlobal) return path.join(os.homedir(), ".claude", "settings.json");
99712
+ return path.join(cwd || process.cwd(), ".claude", "settings.json");
99713
+ }
99714
+ function claudeHookCommand(logPath) {
99715
+ if (!logPath) return CLAUDE_HOOK_BIN;
99716
+ const abs = path.resolve(String(logPath));
99717
+ return `CODERIFTS_GUARD_EVENT_LOG=${abs} CODERIFTS_SESSION_ID=coderifts-claude-hook ${CLAUDE_HOOK_BIN}`;
99718
+ }
99719
+ function isOurPreToolUseEntry(entry, command) {
99720
+ if (!entry || typeof entry !== "object") return false;
99721
+ if (entry.matcher !== CLAUDE_MATCHER) return false;
99722
+ const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
99723
+ return hooks.some((h) => h && h.type === "command" && String(h.command || "") === command);
99724
+ }
99725
+ function installClaudeHook(opts = {}) {
99726
+ const cwd = opts.cwd || process.cwd();
99727
+ const home = opts.homeDir || os.homedir();
99728
+ const dest = opts.global ? path.join(home, ".claude", "settings.json") : path.join(cwd, ".claude", "settings.json");
99729
+ const command = claudeHookCommand(opts.logPath);
99730
+ const block = {
99731
+ matcher: CLAUDE_MATCHER,
99732
+ hooks: [{ type: "command", command, timeout: 60 }]
99733
+ };
99734
+ let existing = {};
99735
+ let hadFile = false;
99736
+ if (fs.existsSync(dest)) {
99737
+ hadFile = true;
99738
+ let raw;
99739
+ try {
99740
+ raw = fs.readFileSync(dest, "utf8");
99741
+ } catch (err) {
99742
+ const message = `Error: cannot read ${dest} (${err && err.message}) \u2014 refusing to write.`;
99743
+ console.error(message);
99744
+ process.exitCode = 1;
99745
+ return { exitCode: 1, code: "READ_FAILED", path: dest, message };
99746
+ }
99747
+ if (String(raw).trim() !== "") {
99748
+ try {
99749
+ existing = JSON.parse(raw);
99750
+ } catch (err) {
99751
+ const message = `Error: ${dest} is not valid JSON \u2014 refusing (no write). Fix or remove the file, then re-run.`;
99752
+ console.error(message);
99753
+ process.exitCode = 1;
99754
+ return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
99755
+ }
99756
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
99757
+ const message = `Error: ${dest} JSON root is not an object \u2014 refusing (no write).`;
99758
+ console.error(message);
99759
+ process.exitCode = 1;
99760
+ return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
99761
+ }
99762
+ }
99763
+ }
99764
+ const hooksRoot = existing.hooks && typeof existing.hooks === "object" && !Array.isArray(existing.hooks) ? { ...existing.hooks } : {};
99765
+ const pre = Array.isArray(hooksRoot.PreToolUse) ? hooksRoot.PreToolUse.slice() : [];
99766
+ if (pre.some((e) => isOurPreToolUseEntry(e, command))) {
99767
+ console.log(`Claude Code hook already installed at ${dest} (no-op).`);
99768
+ return { exitCode: 0, code: "NOOP", path: dest, wrote: false };
99769
+ }
99770
+ const nextPre = pre.concat([block]);
99771
+ const next = {
99772
+ ...existing,
99773
+ hooks: { ...hooksRoot, PreToolUse: nextPre }
99774
+ };
99775
+ const serialized = `${JSON.stringify(next, null, 2)}
99776
+ `;
99777
+ let backedUp;
99778
+ if (hadFile) {
99779
+ backedUp = `${dest}.bak`;
99780
+ fs.copyFileSync(dest, backedUp);
99781
+ }
99782
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
99783
+ fs.writeFileSync(dest, serialized, "utf8");
99784
+ console.log(`Claude Code PreToolUse hook written to ${dest}`);
99785
+ if (backedUp) console.log(`Backup: ${backedUp}`);
99786
+ console.log("matcher Write|Edit|MultiEdit \u2192 coderifts claude-hook (exit 2 = BLOCK).");
99787
+ return { exitCode: 0, code: "OK", path: dest, backedUp, wrote: true };
99788
+ }
99789
+ function install(options = {}) {
99790
+ if (options.global && !options.claude) {
99791
+ console.error("Error: --global requires --claude (writes ~/.claude/settings.json).");
99792
+ process.exitCode = 1;
99793
+ return { exitCode: 1, code: "USAGE" };
99794
+ }
99795
+ if (options.claude) {
99796
+ const r = installClaudeHook({
99797
+ global: !!options.global,
99798
+ logPath: options.log,
99799
+ cwd: options.cwd,
99800
+ homeDir: options.homeDir
99801
+ });
99802
+ if (r.exitCode !== 0) return r;
99803
+ }
99804
+ const gitDir = findGitDir(options.cwd);
99703
99805
  if (!gitDir) {
99806
+ if (options.claude) {
99807
+ console.log("Not inside a Git repository \u2014 skipped pre-push hook.");
99808
+ return { exitCode: 0, code: "OK" };
99809
+ }
99704
99810
  console.error("Error: Not inside a Git repository.");
99705
99811
  process.exitCode = 1;
99706
- return;
99812
+ return { exitCode: 1, code: "NO_GIT" };
99707
99813
  }
99708
99814
  const hooksDir = path.join(gitDir, "hooks");
99709
99815
  if (!fs.existsSync(hooksDir)) {
@@ -99782,7 +99888,19 @@ exit 0
99782
99888
  }
99783
99889
  }
99784
99890
  }
99785
- module2.exports = { install, uninstall, status, PRE_PUSH_SCRIPT, HOOK_MARKER, findGitDir, isCodeRiftsHook };
99891
+ module2.exports = {
99892
+ install,
99893
+ uninstall,
99894
+ status,
99895
+ PRE_PUSH_SCRIPT,
99896
+ HOOK_MARKER,
99897
+ findGitDir,
99898
+ isCodeRiftsHook,
99899
+ installClaudeHook,
99900
+ claudeSettingsPath,
99901
+ claudeHookCommand,
99902
+ CLAUDE_MATCHER
99903
+ };
99786
99904
  }
99787
99905
  });
99788
99906
 
@@ -100063,12 +100181,12 @@ var require_agent_host_files_embedded = __commonJS({
100063
100181
  ".claude/settings.json"
100064
100182
  ]);
100065
100183
  var AGENT_HOST_FILES = Object.freeze({
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',
100067
- "CLAUDE.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\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',
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',
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',
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',
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',
100184
+ "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\nTo act (mutate a contract, merge, deploy, or publish): call `preflight_change_set` with `preflight_mode` authorize. Analyze is informational (`may_execute` is always false) and is not permission. Read `execution_action` on the `decision_result` envelope.\n\nBefore acting under a held receipt: call `verify_receipt` with the intended `context` (operation, environment, repository, branch, pull_request) for THIS attempt. Do not act on a receipt whose scope does not match.\n\nAct only when `currently_authorized` is true (`control_envelope.receipt_view.currently_authorized`). A valid-looking token is not permission if `currently_authorized` is false or omitted.\n\nCommit / CAS evidence is a separate measurement (`commit_observation` on GuardOutcome). It is not a substitute for authorize + `currently_authorized`. Production hosts that want the fail-closed conjunction lock it with `profile: ENFORCING_STRICT` on withCodeRifts.\n',
100185
+ "CLAUDE.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\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\nTo act (mutate a contract, merge, deploy, or publish): call `preflight_change_set` with `preflight_mode` authorize. Analyze is informational (`may_execute` is always false) and is not permission. Read `execution_action` on the `decision_result` envelope.\n\nBefore acting under a held receipt: call `verify_receipt` with the intended `context` (operation, environment, repository, branch, pull_request) for THIS attempt. Do not act on a receipt whose scope does not match.\n\nAct only when `currently_authorized` is true (`control_envelope.receipt_view.currently_authorized`). A valid-looking token is not permission if `currently_authorized` is false or omitted.\n\nCommit / CAS evidence is a separate measurement (`commit_observation` on GuardOutcome). It is not a substitute for authorize + `currently_authorized`. Production hosts that want the fail-closed conjunction lock it with `profile: ENFORCING_STRICT` on withCodeRifts.\n',
100186
+ ".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\nTo act (mutate a contract, merge, deploy, or publish): call `preflight_change_set` with `preflight_mode` authorize. Analyze is informational (`may_execute` is always false) and is not permission. Read `execution_action` on the `decision_result` envelope.\n\nBefore acting under a held receipt: call `verify_receipt` with the intended `context` (operation, environment, repository, branch, pull_request) for THIS attempt. Do not act on a receipt whose scope does not match.\n\nAct only when `currently_authorized` is true (`control_envelope.receipt_view.currently_authorized`). A valid-looking token is not permission if `currently_authorized` is false or omitted.\n\nCommit / CAS evidence is a separate measurement (`commit_observation` on GuardOutcome). It is not a substitute for authorize + `currently_authorized`. Production hosts that want the fail-closed conjunction lock it with `profile: ENFORCING_STRICT` on withCodeRifts.\n',
100187
+ ".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\nTo act (mutate a contract, merge, deploy, or publish): call `preflight_change_set` with `preflight_mode` authorize. Analyze is informational (`may_execute` is always false) and is not permission. Read `execution_action` on the `decision_result` envelope.\n\nBefore acting under a held receipt: call `verify_receipt` with the intended `context` (operation, environment, repository, branch, pull_request) for THIS attempt. Do not act on a receipt whose scope does not match.\n\nAct only when `currently_authorized` is true (`control_envelope.receipt_view.currently_authorized`). A valid-looking token is not permission if `currently_authorized` is false or omitted.\n\nCommit / CAS evidence is a separate measurement (`commit_observation` on GuardOutcome). It is not a substitute for authorize + `currently_authorized`. Production hosts that want the fail-closed conjunction lock it with `profile: ENFORCING_STRICT` on withCodeRifts.\n',
100188
+ "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\\nTo act (mutate a contract, merge, deploy, or publish): call `preflight_change_set` with `preflight_mode` authorize. Analyze is informational (`may_execute` is always false) and is not permission. Read `execution_action` on the `decision_result` envelope.\\n\\nBefore acting under a held receipt: call `verify_receipt` with the intended `context` (operation, environment, repository, branch, pull_request) for THIS attempt. Do not act on a receipt whose scope does not match.\\n\\nAct only when `currently_authorized` is true (`control_envelope.receipt_view.currently_authorized`). A valid-looking token is not permission if `currently_authorized` is false or omitted.\\n\\nCommit / CAS evidence is a separate measurement (`commit_observation` on GuardOutcome). It is not a substitute for authorize + `currently_authorized`. Production hosts that want the fail-closed conjunction lock it with `profile: ENFORCING_STRICT` on withCodeRifts.";\n',
100189
+ "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\nTo act (mutate a contract, merge, deploy, or publish): call `preflight_change_set` with `preflight_mode` authorize. Analyze is informational (`may_execute` is always false) and is not permission. Read `execution_action` on the `decision_result` envelope.\n\nBefore acting under a held receipt: call `verify_receipt` with the intended `context` (operation, environment, repository, branch, pull_request) for THIS attempt. Do not act on a receipt whose scope does not match.\n\nAct only when `currently_authorized` is true (`control_envelope.receipt_view.currently_authorized`). A valid-looking token is not permission if `currently_authorized` is false or omitted.\n\nCommit / CAS evidence is a separate measurement (`commit_observation` on GuardOutcome). It is not a substitute for authorize + `currently_authorized`. Production hosts that want the fail-closed conjunction lock it with `profile: ENFORCING_STRICT` on withCodeRifts.\n',
100072
100190
  ".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'
100073
100191
  });
100074
100192
  module2.exports = { AGENT_HOST_FILES, AGENT_HOST_PATHS };
@@ -101231,8 +101349,10 @@ var require_claude_hook = __commonJS({
101231
101349
  " no API key / API unreachable / disk unreadable / edit-apply failure on the spec path",
101232
101350
  " Absence of a key is not permission. Explicit opt-out: CODERIFTS_ADVISORY=1|true.",
101233
101351
  "",
101234
- "Still exit 0 (nothing to govern): unparseable stdin, missing file_path, non-spec path,",
101235
- "identical content.",
101352
+ "Parse-gap DEFAULT (exit 2): unparseable stdin / missing file_path \u2014 refusing (fail-closed);",
101353
+ " set CODERIFTS_ADVISORY=1 to soften. JSONL hook_blocked cause stdin_unparseable|missing_file_path.",
101354
+ "",
101355
+ "Still exit 0 (nothing to govern): non-spec path, identical content.",
101236
101356
  "",
101237
101357
  "CONTINUE_WITH_MONITORING: allow only if the host asserts a sink",
101238
101358
  " (CODERIFTS_MONITORING_SINK_WIRED=1|true or git config coderifts.monitoringSinkWired).",
@@ -101431,13 +101551,22 @@ var require_claude_hook = __commonJS({
101431
101551
  }
101432
101552
  return { ok: false, reason: `unsupported tool_name for content derive: ${name}` };
101433
101553
  }
101554
+ function isV2DecisionBody(d, dr) {
101555
+ if (dr && typeof dr === "object") return true;
101556
+ if (d.preflight_mode != null && d.preflight_mode !== "") return true;
101557
+ const ver = d.decision_spec_version;
101558
+ return typeof ver === "string" && ver.startsWith("2.");
101559
+ }
101560
+ function allowLegacyDecisionMap(d, dr) {
101561
+ return d.decision_spec_version === "1.0" && !isV2DecisionBody(d, dr);
101562
+ }
101434
101563
  function mapDecisionSeverity(result) {
101435
101564
  const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
101436
101565
  let ea = null;
101437
101566
  const dr = d.decision_result;
101438
- if (dr && typeof dr === "object" && typeof dr.execution_action === "string") {
101567
+ if (dr && typeof dr === "object" && typeof dr.execution_action === "string" && dr.execution_action !== "") {
101439
101568
  ea = dr.execution_action;
101440
- } else if (typeof d.execution_action === "string") {
101569
+ } else if (typeof d.execution_action === "string" && d.execution_action !== "") {
101441
101570
  ea = d.execution_action;
101442
101571
  }
101443
101572
  const hasDecision = d.omega_decision != null && d.omega_decision !== "" || d.decision != null && d.decision !== "" || dr && typeof dr === "object" && dr.execution_action;
@@ -101462,7 +101591,7 @@ var require_claude_hook = __commonJS({
101462
101591
  } else {
101463
101592
  severity = "REQUIRE_APPROVAL";
101464
101593
  }
101465
- } else {
101594
+ } else if (allowLegacyDecisionMap(d, dr)) {
101466
101595
  const od = d.omega_decision || d.decision;
101467
101596
  if (od == null || od === "") {
101468
101597
  severity = "INDETERMINATE";
@@ -101470,6 +101599,8 @@ var require_claude_hook = __commonJS({
101470
101599
  else if (od === "REQUIRE_APPROVAL") severity = "REQUIRE_APPROVAL";
101471
101600
  else if (od === "WARN") severity = "WARN";
101472
101601
  else severity = "ALLOW";
101602
+ } else {
101603
+ severity = "INDETERMINATE";
101473
101604
  }
101474
101605
  const decision = dr && dr.decision || d.decision || d.omega_decision || null;
101475
101606
  const decisionId = dr && dr.decision_id || d.decision_id || null;
@@ -101480,6 +101611,45 @@ var require_claude_hook = __commonJS({
101480
101611
  executionAction: ea
101481
101612
  };
101482
101613
  }
101614
+ function renderDecisionWhy(result, opts = {}) {
101615
+ const maxFixes = Number.isInteger(opts.maxFixes) ? opts.maxFixes : 5;
101616
+ const maxLines = Number.isInteger(opts.maxLines) ? opts.maxLines : 13;
101617
+ const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
101618
+ const dr = d.decision_result && typeof d.decision_result === "object" ? d.decision_result : {};
101619
+ const pick = (k) => dr[k] !== void 0 ? dr[k] : d[k];
101620
+ const arr = (v) => Array.isArray(v) ? v : [];
101621
+ const str = (v) => typeof v === "string" ? v.trim() : "";
101622
+ const lines = [];
101623
+ const reasonCodes = [];
101624
+ for (const r of arr(pick("blocking_reasons"))) {
101625
+ if (!r || typeof r !== "object") continue;
101626
+ const code = str(r.code);
101627
+ const message = str(r.message);
101628
+ if (code) reasonCodes.push(code);
101629
+ if (!code && !message) continue;
101630
+ lines.push(`- ${code || "REASON"}${message ? `: ${message}` : ""}`);
101631
+ }
101632
+ for (const r of arr(pick("degraded_reasons"))) {
101633
+ if (!r || typeof r !== "object") continue;
101634
+ const code = str(r.code);
101635
+ const message = str(r.message);
101636
+ if (!code && !message) continue;
101637
+ lines.push(`- degraded: ${[code, message].filter(Boolean).join(": ")}`);
101638
+ }
101639
+ const action = str(pick("required_action"));
101640
+ if (action) lines.push(`- action: ${action}`);
101641
+ const rt = pick("remediation_transaction");
101642
+ const changes = arr(rt && typeof rt === "object" ? rt.required_changes : null).filter((c) => c && typeof c === "object");
101643
+ const shown = changes.slice(0, maxFixes);
101644
+ for (const c of shown) {
101645
+ const target = str(c.target);
101646
+ const instruction = str(c.instruction);
101647
+ if (!instruction && !target) continue;
101648
+ lines.push(`- fix${target ? ` (${target})` : ""}: ${instruction || str(c.precise_label)}`);
101649
+ }
101650
+ if (changes.length > shown.length) lines.push(`- fix: +${changes.length - shown.length} more`);
101651
+ return { lines: lines.slice(0, maxLines), reasonCodes };
101652
+ }
101483
101653
  async function runClaudeHook(options = {}, deps = {}) {
101484
101654
  const errLog = deps.errLog || ((m) => console.error(String(m)));
101485
101655
  const readStdin = deps.readStdin || (() => {
@@ -101515,7 +101685,14 @@ var require_claude_hook = __commonJS({
101515
101685
  decisionId: r.decisionId != null ? r.decisionId : void 0
101516
101686
  };
101517
101687
  if (r.exitCode === 2) {
101518
- logEv({ type: "hook_blocked", exit: 2, ...extra, cause: r.reason || r.site });
101688
+ const reasons = Array.isArray(r.reasons) && r.reasons.length ? r.reasons : void 0;
101689
+ logEv({
101690
+ type: "hook_blocked",
101691
+ exit: 2,
101692
+ ...extra,
101693
+ reasons,
101694
+ cause: r.reason || r.site
101695
+ });
101519
101696
  } else if (r.exitCode === 0 && r.site && advisory && !strict) {
101520
101697
  logEv({ type: "hook_advisory_passthrough", exit: 0, decision: r.decision || null, ...extra, cause: r.site });
101521
101698
  } else {
@@ -101535,20 +101712,27 @@ var require_claude_hook = __commonJS({
101535
101712
  errLog
101536
101713
  }));
101537
101714
  }
101715
+ const PARSE_GAP_STDERR = "unparseable input \u2014 refusing (fail-closed); set CODERIFTS_ADVISORY=1 to soften";
101538
101716
  const raw = typeof options.stdin === "string" ? options.stdin : readStdin();
101539
101717
  const parsed = parseStdinJson(raw);
101540
101718
  if (!parsed.ok) {
101541
- errLog(`CodeRifts claude-hook: ${parsed.reason} \u2014 allowing (soft; never block on parse gap)`);
101542
- logEv({ type: "detection_skip", signals: ["stdin_unparseable"] });
101543
- return emitTerminal({ exitCode: 0, reason: "stdin_unparseable" });
101719
+ if (advisory && !strict) {
101720
+ errLog(`CodeRifts claude-hook: ${parsed.reason} \u2014 allowing (CODERIFTS_ADVISORY)`);
101721
+ return emitTerminal({ exitCode: 0, reason: "stdin_unparseable", site: "stdin_unparseable" });
101722
+ }
101723
+ errLog(`CodeRifts claude-hook: ${PARSE_GAP_STDERR}`);
101724
+ return emitTerminal({ exitCode: 2, reason: "stdin_unparseable" });
101544
101725
  }
101545
101726
  const { toolName, toolInput } = parsed;
101546
101727
  tool = toolName;
101547
101728
  const filePath = toolInput.file_path || toolInput.filePath || toolInput.path;
101548
101729
  if (!filePath || typeof filePath !== "string") {
101549
- errLog("CodeRifts claude-hook: tool_input.file_path missing \u2014 allowing (soft)");
101550
- logEv({ type: "detection_skip", signals: ["missing_file_path"], tool });
101551
- return emitTerminal({ exitCode: 0, reason: "missing_file_path" });
101730
+ if (advisory && !strict) {
101731
+ errLog("CodeRifts claude-hook: tool_input.file_path missing \u2014 allowing (CODERIFTS_ADVISORY)");
101732
+ return emitTerminal({ exitCode: 0, reason: "missing_file_path", site: "missing_file_path" });
101733
+ }
101734
+ errLog(`CodeRifts claude-hook: ${PARSE_GAP_STDERR}`);
101735
+ return emitTerminal({ exitCode: 2, reason: "missing_file_path" });
101552
101736
  }
101553
101737
  filePathKnown = filePath;
101554
101738
  const specPath = resolveSpecPath({ ...deps, cwd });
@@ -101635,27 +101819,37 @@ var require_claude_hook = __commonJS({
101635
101819
  });
101636
101820
  }
101637
101821
  if (mapped.severity === "BLOCK" || mapped.severity === "UNKNOWN") {
101822
+ const why = renderDecisionWhy(result);
101638
101823
  errLog(
101639
- `CodeRifts claude-hook: BLOCKED${decPart}${eaPart}${idPart}` + (mapped.severity === "UNKNOWN" ? " (unrecognised execution_action)" : "")
101824
+ [
101825
+ `CodeRifts claude-hook: BLOCKED${decPart}${eaPart}${idPart}` + (mapped.severity === "UNKNOWN" ? " (unrecognised execution_action)" : ""),
101826
+ ...why.lines
101827
+ ].join("\n")
101640
101828
  );
101641
101829
  return emitTerminal({
101642
101830
  exitCode: 2,
101643
101831
  reason: mapped.severity === "UNKNOWN" ? "unknown_action" : "block",
101644
101832
  severity: mapped.severity,
101645
101833
  decision: mapped.decision,
101646
- decisionId: mapped.decisionId
101834
+ decisionId: mapped.decisionId,
101835
+ reasons: why.reasonCodes
101647
101836
  });
101648
101837
  }
101649
101838
  if (mapped.severity === "REQUIRE_APPROVAL") {
101839
+ const why = renderDecisionWhy(result);
101650
101840
  errLog(
101651
- `CodeRifts claude-hook: BLOCKED approval_required${decPart}${eaPart}${idPart}`
101841
+ [
101842
+ `CodeRifts claude-hook: BLOCKED approval_required${decPart}${eaPart}${idPart}`,
101843
+ ...why.lines
101844
+ ].join("\n")
101652
101845
  );
101653
101846
  return emitTerminal({
101654
101847
  exitCode: 2,
101655
101848
  reason: "approval_required",
101656
101849
  severity: mapped.severity,
101657
101850
  decision: mapped.decision,
101658
- decisionId: mapped.decisionId
101851
+ decisionId: mapped.decisionId,
101852
+ reasons: why.reasonCodes
101659
101853
  });
101660
101854
  }
101661
101855
  if (mapped.severity === "MONITOR" || mapped.severity === "WARN") {
@@ -101694,6 +101888,7 @@ var require_claude_hook = __commonJS({
101694
101888
  isSpecPath,
101695
101889
  deriveAfterContent,
101696
101890
  mapDecisionSeverity,
101891
+ renderDecisionWhy,
101697
101892
  resolveApiKey,
101698
101893
  resolveSpecPath,
101699
101894
  readGitConfig,
@@ -103722,9 +103917,13 @@ ${USAGE}
103722
103917
  }
103723
103918
  });
103724
103919
  var hookCmd = program.command("hook").description("Manage the CodeRifts pre-push Git hook");
103725
- hookCmd.command("install").description("Install the CodeRifts pre-push hook in the current Git repo").action(() => {
103920
+ hookCmd.command("install").description("Install the CodeRifts pre-push hook in the current Git repo").option("--claude", "Write Claude Code PreToolUse hook to .claude/settings.json (JSON-merge)").option("--global", "With --claude: write ~/.claude/settings.json instead of the project file").option("--log <path>", "With --claude: embed CODERIFTS_GUARD_EVENT_LOG and CODERIFTS_SESSION_ID in the command").action((options) => {
103726
103921
  const { install } = require_hook();
103727
- install();
103922
+ install({
103923
+ claude: !!options.claude,
103924
+ global: !!options.global,
103925
+ log: options.log
103926
+ });
103728
103927
  });
103729
103928
  hookCmd.command("uninstall").description("Remove the CodeRifts pre-push hook from the current Git repo").action(() => {
103730
103929
  const { uninstall } = require_hook();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderifts",
3
- "version": "4.1.0",
3
+ "version": "4.2.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",