coderifts 4.4.1 → 4.7.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/dist/cli.js CHANGED
@@ -3028,7 +3028,7 @@ var require_package = __commonJS({
3028
3028
  "package.json"(exports2, module2) {
3029
3029
  module2.exports = {
3030
3030
  name: "coderifts",
3031
- version: "4.4.1",
3031
+ version: "4.7.0",
3032
3032
  description: "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
3033
3033
  author: "CodeRifts <hello@coderifts.com>",
3034
3034
  license: "MIT",
@@ -207461,18 +207461,31 @@ var require_deploy_gate2 = __commonJS({
207461
207461
  return isEnvFlag(e, "CODERIFTS_DEPLOY_ADVISORY") || isEnvFlag(e, "CODERIFTS_ADVISORY");
207462
207462
  }
207463
207463
  function observeCDEnforcement(options = {}) {
207464
+ const evidence = options.providerEvidence;
207465
+ const hostClaimNoBypass = String(process.env.CODERIFTS_DEPLOY_NO_BYPASS || "").toLowerCase() === "true";
207466
+ if (evidence && evidence.inescapable_deploy === true) {
207467
+ return {
207468
+ enforcement: "ENFORCING",
207469
+ bypass_possible: false,
207470
+ step_is_required: true,
207471
+ required_step_name: "CodeRifts / deploy-gate",
207472
+ attestation_source: "provider_query",
207473
+ host_claim_no_bypass: hostClaimNoBypass
207474
+ };
207475
+ }
207464
207476
  const envVal = String(process.env.CODERIFTS_DEPLOY_ENFORCE || "").toLowerCase();
207465
207477
  let enforcement;
207466
207478
  if (enforceSignal(options)) enforcement = "ENFORCING";
207467
207479
  else if (envVal === "unknown") enforcement = "UNKNOWN";
207468
207480
  else enforcement = "ADVISORY";
207469
- const bypass_possible = String(process.env.CODERIFTS_DEPLOY_NO_BYPASS || "").toLowerCase() !== "true";
207470
207481
  return {
207471
207482
  enforcement,
207472
- bypass_possible,
207483
+ bypass_possible: true,
207484
+ // host NO_BYPASS is a claim, not queried evidence
207473
207485
  step_is_required: enforcement === "ENFORCING",
207474
207486
  required_step_name: "CodeRifts / deploy-gate",
207475
- attestation_source: "cli_flag"
207487
+ attestation_source: "cli_flag",
207488
+ host_claim_no_bypass: hostClaimNoBypass
207476
207489
  };
207477
207490
  }
207478
207491
  function deployReportResiduals(state, enforcement_inescapable, enforcement, change_set_rebound) {
@@ -207795,7 +207808,7 @@ var require_deploy_gate2 = __commonJS({
207795
207808
  if (bind.report_residuals.length) lines.push(` Residuals: ${bind.report_residuals.join(", ")}`);
207796
207809
  if (bind.must_re_preflight) lines.push(chalk.yellow(" Action: re-preflight this { environment, artifact } \u2014 the receipt does not authorize it."));
207797
207810
  lines.push("");
207798
- lines.push(enforce ? chalk.dim(" Attested ENFORCING (inescapable_deploy claim). Exit is fail-closed by default.") : chalk.dim(" Fail-closed default \u2014 a failing gate exits non-zero. " + SOFTEN_HINT + "."));
207811
+ lines.push(enforce ? chalk.dim(" Attested ENFORCING via --enforce (host claim). inescapable_deploy requires queried pipeline protection (`coderifts enforce --check`). Exit is fail-closed by default.") : chalk.dim(" Fail-closed default \u2014 a failing gate exits non-zero. " + SOFTEN_HINT + "."));
207799
207812
  lines.push("");
207800
207813
  return lines.join("\n");
207801
207814
  }
@@ -208972,6 +208985,1555 @@ var require_registry_gate = __commonJS({
208972
208985
  }
208973
208986
  });
208974
208987
 
208988
+ // src/agent-host-files.embedded.js
208989
+ var require_agent_host_files_embedded = __commonJS({
208990
+ "src/agent-host-files.embedded.js"(exports2, module2) {
208991
+ "use strict";
208992
+ var AGENT_HOST_PATHS = Object.freeze([
208993
+ "AGENTS.md",
208994
+ "CLAUDE.md",
208995
+ ".cursor/rules/coderifts.mdc",
208996
+ ".github/copilot-instructions.md",
208997
+ "coderifts-langgraph-policy.js",
208998
+ "openai-agent-instructions.md",
208999
+ ".claude/settings.json",
209000
+ ".cursor/hooks.json"
209001
+ ]);
209002
+ var AGENT_HOST_FILES = Object.freeze({
209003
+ "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 authorizes ONE operation: a merge receipt does not authorize a deploy. Before a different operation (deploy, publish), call `preflight_change_set` with `context.operation` set to that operation \u2014 reusing a differently-scoped receipt is not permitted and will fail at the gate.\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\nIf the host requests an execution grant (opt-in `include_execution_grant`), the grant is bound to operation + target + after-payload (`scope_hash`) and is short-lived \u2014 never reuse it after the after-payload changes.\n\nAn ATOMIC-profile grant carries `state_nonce` and is single-use at the executor \u2014 if the executor has consumed the nonce, re-preflight; do not retry the same grant.\n\nWith a proven tenant\u2194repo binding you may request `derivation:"server"` instead of assembling `artifacts[]` yourself (`context.repository` + `context.base` + `context.head` required; caller-supplied artifacts are rejected on that path).\n\nA commit is only proven when an executor attestation verifies (customer-held executor key, `cas_evidence: executor_attested`); otherwise say "authorized, commit not proven".\n',
209004
+ "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 authorizes ONE operation: a merge receipt does not authorize a deploy. Before a different operation (deploy, publish), call `preflight_change_set` with `context.operation` set to that operation \u2014 reusing a differently-scoped receipt is not permitted and will fail at the gate.\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\nIf the host requests an execution grant (opt-in `include_execution_grant`), the grant is bound to operation + target + after-payload (`scope_hash`) and is short-lived \u2014 never reuse it after the after-payload changes.\n\nAn ATOMIC-profile grant carries `state_nonce` and is single-use at the executor \u2014 if the executor has consumed the nonce, re-preflight; do not retry the same grant.\n\nWith a proven tenant\u2194repo binding you may request `derivation:"server"` instead of assembling `artifacts[]` yourself (`context.repository` + `context.base` + `context.head` required; caller-supplied artifacts are rejected on that path).\n\nA commit is only proven when an executor attestation verifies (customer-held executor key, `cas_evidence: executor_attested`); otherwise say "authorized, commit not proven".\n',
209005
+ ".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 authorizes ONE operation: a merge receipt does not authorize a deploy. Before a different operation (deploy, publish), call `preflight_change_set` with `context.operation` set to that operation \u2014 reusing a differently-scoped receipt is not permitted and will fail at the gate.\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\nIf the host requests an execution grant (opt-in `include_execution_grant`), the grant is bound to operation + target + after-payload (`scope_hash`) and is short-lived \u2014 never reuse it after the after-payload changes.\n\nAn ATOMIC-profile grant carries `state_nonce` and is single-use at the executor \u2014 if the executor has consumed the nonce, re-preflight; do not retry the same grant.\n\nWith a proven tenant\u2194repo binding you may request `derivation:"server"` instead of assembling `artifacts[]` yourself (`context.repository` + `context.base` + `context.head` required; caller-supplied artifacts are rejected on that path).\n\nA commit is only proven when an executor attestation verifies (customer-held executor key, `cas_evidence: executor_attested`); otherwise say "authorized, commit not proven".\n',
209006
+ ".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 authorizes ONE operation: a merge receipt does not authorize a deploy. Before a different operation (deploy, publish), call `preflight_change_set` with `context.operation` set to that operation \u2014 reusing a differently-scoped receipt is not permitted and will fail at the gate.\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\nIf the host requests an execution grant (opt-in `include_execution_grant`), the grant is bound to operation + target + after-payload (`scope_hash`) and is short-lived \u2014 never reuse it after the after-payload changes.\n\nAn ATOMIC-profile grant carries `state_nonce` and is single-use at the executor \u2014 if the executor has consumed the nonce, re-preflight; do not retry the same grant.\n\nWith a proven tenant\u2194repo binding you may request `derivation:"server"` instead of assembling `artifacts[]` yourself (`context.repository` + `context.base` + `context.head` required; caller-supplied artifacts are rejected on that path).\n\nA commit is only proven when an executor attestation verifies (customer-held executor key, `cas_evidence: executor_attested`); otherwise say "authorized, commit not proven".\n',
209007
+ "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 authorizes ONE operation: a merge receipt does not authorize a deploy. Before a different operation (deploy, publish), call `preflight_change_set` with `context.operation` set to that operation \u2014 reusing a differently-scoped receipt is not permitted and will fail at the gate.\\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\\nIf the host requests an execution grant (opt-in `include_execution_grant`), the grant is bound to operation + target + after-payload (`scope_hash`) and is short-lived \u2014 never reuse it after the after-payload changes.\\n\\nAn ATOMIC-profile grant carries `state_nonce` and is single-use at the executor \u2014 if the executor has consumed the nonce, re-preflight; do not retry the same grant.\\n\\nWith a proven tenant\u2194repo binding you may request `derivation:\\"server\\"` instead of assembling `artifacts[]` yourself (`context.repository` + `context.base` + `context.head` required; caller-supplied artifacts are rejected on that path).\\n\\nA commit is only proven when an executor attestation verifies (customer-held executor key, `cas_evidence: executor_attested`); otherwise say \\"authorized, commit not proven\\".";\n',
209008
+ "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 authorizes ONE operation: a merge receipt does not authorize a deploy. Before a different operation (deploy, publish), call `preflight_change_set` with `context.operation` set to that operation \u2014 reusing a differently-scoped receipt is not permitted and will fail at the gate.\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\nIf the host requests an execution grant (opt-in `include_execution_grant`), the grant is bound to operation + target + after-payload (`scope_hash`) and is short-lived \u2014 never reuse it after the after-payload changes.\n\nAn ATOMIC-profile grant carries `state_nonce` and is single-use at the executor \u2014 if the executor has consumed the nonce, re-preflight; do not retry the same grant.\n\nWith a proven tenant\u2194repo binding you may request `derivation:"server"` instead of assembling `artifacts[]` yourself (`context.repository` + `context.base` + `context.head` required; caller-supplied artifacts are rejected on that path).\n\nA commit is only proven when an executor attestation verifies (customer-held executor key, `cas_evidence: executor_attested`); otherwise say "authorized, commit not proven".\n',
209009
+ ".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',
209010
+ ".cursor/hooks.json": '{\n "version": 1,\n "hooks": {\n "preToolUse": [\n {\n "command": "coderifts cursor-hook",\n "matcher": "Write|Delete",\n "timeout": 60,\n "failClosed": true\n }\n ]\n }\n}\n'
209011
+ });
209012
+ module2.exports = { AGENT_HOST_FILES, AGENT_HOST_PATHS };
209013
+ }
209014
+ });
209015
+
209016
+ // src/commands/agent-setup.js
209017
+ var require_agent_setup = __commonJS({
209018
+ "src/commands/agent-setup.js"(exports2, module2) {
209019
+ "use strict";
209020
+ var fs = require("fs");
209021
+ var path = require("path");
209022
+ var chalk = require_source();
209023
+ var { AGENT_HOST_FILES, AGENT_HOST_PATHS } = require_agent_host_files_embedded();
209024
+ if (process.env.NO_COLOR) chalk.level = 0;
209025
+ var USAGE = `Usage: coderifts agent-setup [--out <dir>] [--check] [--force]
209026
+
209027
+ --out <dir> Target directory (default: current working directory)
209028
+ --check Exit 0 if on-disk files match embedded content; exit 1 on drift
209029
+ --force Overwrite existing files (default: skip collisions)
209030
+ Unknown flags exit 1 (never silently ignored).
209031
+ `;
209032
+ function parseAgentSetupArgs(argv) {
209033
+ const args = argv.slice(2);
209034
+ let out = null;
209035
+ let check = false;
209036
+ let force = false;
209037
+ let i = 0;
209038
+ while (i < args.length && !String(args[i]).startsWith("-")) i += 1;
209039
+ while (i < args.length) {
209040
+ const a = args[i];
209041
+ if (a === "--out") {
209042
+ const v = args[i + 1];
209043
+ if (v == null || v.startsWith("-")) {
209044
+ return { out, check, force, error: `agent-setup: --out requires a path
209045
+ ${USAGE}` };
209046
+ }
209047
+ out = path.resolve(v);
209048
+ i += 2;
209049
+ continue;
209050
+ }
209051
+ if (a === "--check") {
209052
+ check = true;
209053
+ i += 1;
209054
+ continue;
209055
+ }
209056
+ if (a === "--force") {
209057
+ force = true;
209058
+ i += 1;
209059
+ continue;
209060
+ }
209061
+ if (a.startsWith("-")) {
209062
+ return { out, check, force, error: `agent-setup: unrecognized argument: ${a}
209063
+ ${USAGE}` };
209064
+ }
209065
+ return { out, check, force, error: `agent-setup: unrecognized argument: ${a}
209066
+ ${USAGE}` };
209067
+ }
209068
+ return { out, check, force };
209069
+ }
209070
+ function runAgentSetup(options = {}, deps = {}) {
209071
+ const log = deps.log || console.log.bind(console);
209072
+ const logErr = deps.logErr || console.error.bind(console);
209073
+ const doExit = deps.exit !== false;
209074
+ const cwd = deps.cwd || process.cwd();
209075
+ const exists = deps.exists || fs.existsSync.bind(fs);
209076
+ const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
209077
+ const writeFile = deps.writeFile || ((p, c) => {
209078
+ fs.mkdirSync(path.dirname(p), { recursive: true });
209079
+ fs.writeFileSync(p, c, "utf8");
209080
+ });
209081
+ let outDir = options.out ? path.resolve(String(options.out)) : cwd;
209082
+ let check = !!options.check;
209083
+ let force = !!options.force;
209084
+ if (deps.argv) {
209085
+ const parsed = parseAgentSetupArgs(deps.argv);
209086
+ if (parsed.error) {
209087
+ logErr(parsed.error.trimEnd());
209088
+ if (doExit) process.exit(1);
209089
+ return { exitCode: 1, code: "USAGE", message: parsed.error };
209090
+ }
209091
+ if (parsed.out) outDir = parsed.out;
209092
+ check = parsed.check;
209093
+ force = parsed.force;
209094
+ }
209095
+ const files = deps.files || AGENT_HOST_FILES;
209096
+ const relPaths = deps.paths || AGENT_HOST_PATHS;
209097
+ if (check) {
209098
+ let stale = false;
209099
+ for (const rel of relPaths) {
209100
+ const fp = path.join(outDir, rel);
209101
+ const expected = files[rel];
209102
+ if (!exists(fp)) {
209103
+ logErr(chalk.red(`agent-setup --check: missing ${rel}`));
209104
+ stale = true;
209105
+ continue;
209106
+ }
209107
+ const onDisk = readFile(fp);
209108
+ if (onDisk !== expected) {
209109
+ logErr(chalk.red(`agent-setup --check: drift ${rel}`));
209110
+ stale = true;
209111
+ }
209112
+ }
209113
+ if (stale) {
209114
+ logErr("Run: coderifts agent-setup --out " + outDir + " --force");
209115
+ if (doExit) process.exit(1);
209116
+ return { exitCode: 1, code: "DRIFT", outDir };
209117
+ }
209118
+ log(chalk.green(`agent-setup: up to date (${outDir}, ${relPaths.length} files)`));
209119
+ if (doExit) process.exit(0);
209120
+ return { exitCode: 0, code: "UP_TO_DATE", outDir };
209121
+ }
209122
+ const summary = writeAgentHostFiles({
209123
+ outDir,
209124
+ force,
209125
+ files,
209126
+ paths: relPaths,
209127
+ exists,
209128
+ writeFile,
209129
+ dryRun: !!options.dryRun
209130
+ });
209131
+ if (!options.json) {
209132
+ log(chalk.bold("CodeRifts agent-setup"));
209133
+ log(` target: ${outDir}`);
209134
+ for (const rel of summary.written) {
209135
+ const tag = summary.forced.includes(rel) ? "overwrote" : "wrote";
209136
+ log(chalk.green(` ${tag}: ${rel}`));
209137
+ }
209138
+ for (const rel of summary.skipped) {
209139
+ log(chalk.yellow(` skipped: ${rel} (exists; use --force to overwrite)`));
209140
+ }
209141
+ log("");
209142
+ log(chalk.dim(` ${summary.written.length} written, ${summary.skipped.length} skipped`));
209143
+ log("");
209144
+ for (const line of AGENT_SETUP_WARN_TELEMETRY_LINES) {
209145
+ log(line);
209146
+ }
209147
+ if (summary.skipped.includes(".claude/settings.json")) {
209148
+ for (const line of AGENT_SETUP_HOOK_SKIP_LINES) {
209149
+ log(line);
209150
+ }
209151
+ } else if (summary.written.includes(".claude/settings.json")) {
209152
+ for (const line of AGENT_SETUP_HOOK_WIRE_LINES) {
209153
+ log(line);
209154
+ }
209155
+ }
209156
+ }
209157
+ if (doExit) process.exit(0);
209158
+ return { exitCode: 0, code: "OK", outDir, ...summary };
209159
+ }
209160
+ var AGENT_SETUP_WARN_TELEMETRY_LINES = Object.freeze([
209161
+ chalk.bold(" Execution-state recheck (guard@8): DEFAULT ON"),
209162
+ chalk.dim(" requireExecutionStateMatch absent/true \u2192 enforce: mismatch \u2192 EXECUTION_STATE_DRIFT"),
209163
+ chalk.dim(" (factory does not run); missing fingerprint/artifacts \u2192 EXECUTION_STATE_UNMEASURABLE (STOP)."),
209164
+ chalk.dim(" Opt-down: requireExecutionStateMatch: 'warn' (emit, then run unenforced on mismatch)"),
209165
+ chalk.dim(" or false (no recheck \u2014 v7 proceed-on-drift). Must be set explicitly."),
209166
+ chalk.dim(" Warn emits (then PROCEEDS \u2014 never blocks in warn mode):"),
209167
+ chalk.dim(" \xB7 real drift \u2192 execution_state_drift_observed (loud)"),
209168
+ chalk.dim(" \xB7 missing artifacts/fingerprint \u2192 execution_state_unmeasurable (quiet)"),
209169
+ chalk.dim(" Merge gate block is separate: coderifts setup-required-check --apply")
209170
+ ]);
209171
+ var AGENT_SETUP_HOOK_WIRE_LINES = Object.freeze([
209172
+ chalk.bold(" Claude Code hook (.claude/settings.json): fail-closed PreToolUse"),
209173
+ chalk.dim(" matcher Write|Edit|MultiEdit \u2192 coderifts claude-hook (exit 2 = BLOCK)."),
209174
+ chalk.dim(" Missing key / API down / unreadable spec \u2192 exit 2 (enforce_indeterminate), not ALLOW."),
209175
+ chalk.dim(" Opt-out: CODERIFTS_ADVISORY=1|true restores soft-allow when governance could not run."),
209176
+ chalk.dim(" CODERIFTS_STRICT=1 cannot be weakened by ADVISORY.")
209177
+ ]);
209178
+ var AGENT_SETUP_HOOK_SKIP_LINES = Object.freeze([
209179
+ chalk.yellow(" .claude/settings.json exists \u2014 left unchanged (installer does not merge)."),
209180
+ chalk.dim(" --force replaces the ENTIRE file (not just the PreToolUse hook)."),
209181
+ chalk.dim(" To wire fail-closed: add PreToolUse \u2192 coderifts claude-hook by hand, or --force."),
209182
+ chalk.dim(" Opt-out if wired: CODERIFTS_ADVISORY=1|true. CODERIFTS_STRICT cannot weaken.")
209183
+ ]);
209184
+ function writeAgentHostFiles(opts = {}) {
209185
+ const outDir = opts.outDir;
209186
+ const force = !!opts.force;
209187
+ const files = opts.files || AGENT_HOST_FILES;
209188
+ const relPaths = opts.paths || AGENT_HOST_PATHS;
209189
+ const exists = opts.exists || fs.existsSync.bind(fs);
209190
+ const writeFile = opts.writeFile || ((p, c) => {
209191
+ fs.mkdirSync(path.dirname(p), { recursive: true });
209192
+ fs.writeFileSync(p, c, "utf8");
209193
+ });
209194
+ const dryRun = !!opts.dryRun;
209195
+ const summary = { written: [], skipped: [], forced: [] };
209196
+ for (const rel of relPaths) {
209197
+ const fp = path.join(outDir, rel);
209198
+ if (exists(fp) && !force) {
209199
+ summary.skipped.push(rel);
209200
+ continue;
209201
+ }
209202
+ if (exists(fp) && force) summary.forced.push(rel);
209203
+ if (!dryRun) writeFile(fp, files[rel]);
209204
+ summary.written.push(rel);
209205
+ }
209206
+ return summary;
209207
+ }
209208
+ module2.exports = {
209209
+ runAgentSetup,
209210
+ parseAgentSetupArgs,
209211
+ writeAgentHostFiles,
209212
+ AGENT_HOST_FILES,
209213
+ AGENT_HOST_PATHS,
209214
+ USAGE,
209215
+ AGENT_SETUP_WARN_TELEMETRY_LINES,
209216
+ AGENT_SETUP_HOOK_WIRE_LINES,
209217
+ AGENT_SETUP_HOOK_SKIP_LINES
209218
+ };
209219
+ }
209220
+ });
209221
+
209222
+ // src/commands/hook.js
209223
+ var require_hook = __commonJS({
209224
+ "src/commands/hook.js"(exports2, module2) {
209225
+ "use strict";
209226
+ var fs = require("fs");
209227
+ var path = require("path");
209228
+ var os = require("os");
209229
+ var { execSync } = require("child_process");
209230
+ var HOOK_MARKER = "# CodeRifts pre-push hook";
209231
+ var PRE_PUSH_SCRIPT = `#!/bin/sh
209232
+ ${HOOK_MARKER}
209233
+ # Checks API spec changes before pushing.
209234
+ # The pre-push hook receives lines on stdin:
209235
+ # <local ref> <local sha> <remote ref> <remote sha>
209236
+ #
209237
+ # Re-install after CLI upgrades: coderifts hook install
209238
+ #
209239
+ # Exit map (fail-closed when configured):
209240
+ # CODERIFTS_SKIP=1 \u2192 exit 0 (explicit loud override)
209241
+ # no apiKey \u2192 exit 0 soft (not configured)
209242
+ # BLOCK / STOP / UNKNOWN \u2192 exit 1
209243
+ # INDETERMINATE (no decision obtainable) \u2192 exit 1
209244
+ # ALLOW / analysis clean \u2192 exit 0
209245
+ # REQUIRE_APPROVAL / WARN \u2192 warn, exit 0
209246
+
209247
+ # Explicit escape hatch \u2014 never the default on error; human must set this.
209248
+ if [ "\${CODERIFTS_SKIP:-}" = "1" ]; then
209249
+ echo "CodeRifts: CODERIFTS_SKIP=1 set \u2014 push allowed (explicit override)." >&2
209250
+ exit 0
209251
+ fi
209252
+
209253
+ CODERIFTS_API_KEY=$(git config coderifts.apiKey)
209254
+ SPEC_PATH=$(git config coderifts.specPath || echo "api/openapi.yaml")
209255
+ ZERO="0000000000000000000000000000000000000000"
209256
+
209257
+ if [ -z "$CODERIFTS_API_KEY" ]; then
209258
+ echo "CodeRifts: No API key configured. Run: git config coderifts.apiKey <your-key>"
209259
+ exit 0 # Don't block if not configured
209260
+ fi
209261
+
209262
+ # --- helpers: three-state git blob read (present | absent | error) ---
209263
+ # Sets: BLOB_KIND, BLOB_CONTENT, BLOB_ERR. Never maps unreadable \u2192 empty string.
209264
+ git_blob_at() {
209265
+ _ref="$1"
209266
+ _path="$2"
209267
+ BLOB_CONTENT=""
209268
+ BLOB_ERR=""
209269
+ BLOB_KIND=""
209270
+ if ! git rev-parse --verify "$_ref^{commit}" >/dev/null 2>&1; then
209271
+ BLOB_KIND=error
209272
+ BLOB_ERR="GIT_ERROR: cannot resolve ref $_ref"
209273
+ return 1
209274
+ fi
209275
+ _errf=$(mktemp 2>/dev/null || echo "/tmp/coderifts-hook-err.$$")
209276
+ if BLOB_CONTENT=$(git show "$_ref:$_path" 2>"$_errf"); then
209277
+ BLOB_KIND=present
209278
+ rm -f "$_errf"
209279
+ return 0
209280
+ fi
209281
+ _err=$(cat "$_errf" 2>/dev/null)
209282
+ rm -f "$_errf"
209283
+ case "$_err" in
209284
+ *"does not exist in"*|*"exists on disk, but not in"*)
209285
+ BLOB_KIND=absent
209286
+ BLOB_CONTENT=""
209287
+ return 0
209288
+ ;;
209289
+ esac
209290
+ BLOB_KIND=error
209291
+ BLOB_ERR="GIT_ERROR: git show $_ref:$_path failed"
209292
+ return 1
209293
+ }
209294
+
209295
+ # New-branch baseline: merge-base with default branch (not empty before).
209296
+ # Prints merge-base SHA on success; nonzero if none can be resolved.
209297
+ resolve_new_branch_base_ref() {
209298
+ _local="$1"
209299
+ for _cand in origin/HEAD origin/main origin/master main master; do
209300
+ _mb=$(git merge-base "$_local" "$_cand" 2>/dev/null) || continue
209301
+ if [ -n "$_mb" ] && [ "$_mb" != "$ZERO" ]; then
209302
+ echo "$_mb"
209303
+ return 0
209304
+ fi
209305
+ done
209306
+ return 1
209307
+ }
209308
+
209309
+ # Read stdin lines provided by git pre-push
209310
+ while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
209311
+ # Skip delete pushes
209312
+ if [ "$LOCAL_SHA" = "$ZERO" ]; then
209313
+ continue
209314
+ fi
209315
+
209316
+ # after = local (about-to-be-pushed) commit \u2014 three-state
209317
+ if ! git_blob_at "$LOCAL_SHA" "$SPEC_PATH"; then
209318
+ echo "CodeRifts: $BLOB_ERR (after=$SPEC_PATH at $LOCAL_SHA). Fail-closed."
209319
+ exit 1
209320
+ fi
209321
+ if [ "$BLOB_KIND" = "absent" ]; then
209322
+ continue # Spec doesn't exist in local commit, skip
209323
+ fi
209324
+ HEAD_SPEC=$BLOB_CONTENT
209325
+
209326
+ # before = remote tip, or for new branch (zero remote SHA) a merge-base baseline
209327
+ if [ "$REMOTE_SHA" = "$ZERO" ]; then
209328
+ BASE_REF=$(resolve_new_branch_base_ref "$LOCAL_SHA") || {
209329
+ echo "CodeRifts: GIT_ERROR \u2014 cannot resolve merge-base baseline for new branch (tried origin/HEAD, origin/main, origin/master, main, master). Fail-closed."
209330
+ exit 1
209331
+ }
209332
+ else
209333
+ BASE_REF=$REMOTE_SHA
209334
+ fi
209335
+
209336
+ if ! git_blob_at "$BASE_REF" "$SPEC_PATH"; then
209337
+ echo "CodeRifts: $BLOB_ERR (before=$SPEC_PATH at $BASE_REF). Fail-closed \u2014 not treating as new spec."
209338
+ exit 1
209339
+ fi
209340
+ if [ "$BLOB_KIND" = "absent" ]; then
209341
+ # Honest NEW_ARTIFACT: path genuinely not at baseline
209342
+ echo "CodeRifts: New artifact (spec absent at baseline $BASE_REF:$SPEC_PATH); allowing push."
209343
+ continue
209344
+ fi
209345
+ BASE_SPEC=$BLOB_CONTENT
209346
+
209347
+ # If specs are identical, nothing to check
209348
+ if [ "$BASE_SPEC" = "$HEAD_SPEC" ]; then
209349
+ continue
209350
+ fi
209351
+
209352
+ echo "CodeRifts: Checking API spec changes (baseline $BASE_REF)..."
209353
+
209354
+ # Capture body + HTTP status separately (fail-closed on non-2xx when configured).
209355
+ _CR_BODY=$(mktemp 2>/dev/null || echo "/tmp/coderifts-hook-body.$$")
209356
+ _CR_HTTP=$(curl -sS -o "$_CR_BODY" -w "%{http_code}" -X POST https://app.coderifts.com/api/v1/diff \\
209357
+ -H "Authorization: Bearer $CODERIFTS_API_KEY" \\
209358
+ -H "Content-Type: application/json" \\
209359
+ -d "{\\"before\\": $(echo "$BASE_SPEC" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'), \\"after\\": $(echo "$HEAD_SPEC" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')}" 2>/dev/null) || _CR_HTTP="000"
209360
+ RESULT=$(cat "$_CR_BODY" 2>/dev/null)
209361
+ rm -f "$_CR_BODY"
209362
+
209363
+ # (a) HTTP non-2xx (incl. transport fail \u2192 000) \u2192 configured-but-indeterminate
209364
+ case "$_CR_HTTP" in
209365
+ 2??) ;;
209366
+ *)
209367
+ echo "CodeRifts could not obtain a decision (HTTP \${_CR_HTTP:-000}); push blocked \u2014 set CODERIFTS_SKIP=1 to override intentionally" >&2
209368
+ exit 1
209369
+ ;;
209370
+ esac
209371
+
209372
+ # Prefer closed-set execution_action; omega_decision only when action is absent.
209373
+ # Indeterminate classes \u2192 INDETERMINATE (fail closed), never default ALLOW.
209374
+ # Severity: BLOCK/STOP \u2192 exit 1; REQUIRE_APPROVAL/WARN \u2192 warn; unknown action \u2192 halt;
209375
+ # INDETERMINATE / empty \u2192 exit 1 (configured-but-indeterminate).
209376
+ DECISION=$(echo "$RESULT" | python3 -c '
209377
+ import sys, json
209378
+ raw = sys.stdin.read()
209379
+ if raw is None or not str(raw).strip():
209380
+ print("INDETERMINATE")
209381
+ raise SystemExit(0)
209382
+ try:
209383
+ d = json.loads(raw)
209384
+ except Exception:
209385
+ # (b) 2xx but unparseable JSON
209386
+ print("INDETERMINATE")
209387
+ raise SystemExit(0)
209388
+ if not isinstance(d, dict):
209389
+ print("INDETERMINATE")
209390
+ raise SystemExit(0)
209391
+ # API error body without a decision (e.g. {"error":"internal_error"} / server_error)
209392
+ if d.get("error") and not (
209393
+ d.get("omega_decision") or d.get("decision")
209394
+ or d.get("execution_action")
209395
+ or (isinstance(d.get("decision_result"), dict) and d["decision_result"].get("execution_action"))
209396
+ ):
209397
+ print("INDETERMINATE")
209398
+ raise SystemExit(0)
209399
+ ea = None
209400
+ dr = d.get("decision_result")
209401
+ if isinstance(dr, dict) and isinstance(dr.get("execution_action"), str):
209402
+ ea = dr["execution_action"]
209403
+ elif isinstance(d.get("execution_action"), str):
209404
+ ea = d["execution_action"]
209405
+ closed = {"CONTINUE", "CONTINUE_WITH_MONITORING", "REQUEST_APPROVAL", "STOP"}
209406
+ if ea is not None and ea != "":
209407
+ if ea not in closed:
209408
+ print("UNKNOWN")
209409
+ raise SystemExit(0)
209410
+ if ea in ("CONTINUE", "CONTINUE_WITH_MONITORING"):
209411
+ print("ALLOW")
209412
+ elif ea == "STOP":
209413
+ print("BLOCK")
209414
+ else:
209415
+ print("REQUIRE_APPROVAL")
209416
+ raise SystemExit(0)
209417
+ # (c) parsed but no execution_action AND no decision \u2014 do NOT default ALLOW
209418
+ od = d.get("omega_decision") or d.get("decision")
209419
+ if od is None or od == "":
209420
+ print("INDETERMINATE")
209421
+ raise SystemExit(0)
209422
+ print(od)
209423
+ ' 2>/dev/null)
209424
+ # (d) empty DECISION (python missing / crash with empty stdout) \u2192 fail closed
209425
+ if [ -z "$DECISION" ]; then
209426
+ DECISION="INDETERMINATE"
209427
+ fi
209428
+ OMEGA=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('omega_api',0))" 2>/dev/null)
209429
+ BREAKING=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('breaking_changes',[])))" 2>/dev/null)
209430
+
209431
+ if [ "$DECISION" = "INDETERMINATE" ]; then
209432
+ echo "CodeRifts could not obtain a decision (indeterminate response); push blocked \u2014 set CODERIFTS_SKIP=1 to override intentionally" >&2
209433
+ exit 1
209434
+ fi
209435
+
209436
+ if [ "$DECISION" = "UNKNOWN" ]; then
209437
+ echo ""
209438
+ echo "========================================"
209439
+ echo " CodeRifts: PUSH BLOCKED"
209440
+ echo "========================================"
209441
+ echo " Reason: unrecognised execution_action is not permission"
209442
+ echo " Fail-closed (unknown present action)."
209443
+ echo "========================================"
209444
+ echo ""
209445
+ echo "Set CODERIFTS_SKIP=1 to override intentionally (or git push --no-verify)."
209446
+ exit 1
209447
+ fi
209448
+
209449
+ if [ "$DECISION" = "BLOCK" ]; then
209450
+ echo ""
209451
+ echo "========================================"
209452
+ echo " CodeRifts: PUSH BLOCKED"
209453
+ echo "========================================"
209454
+ echo " Risk score: $OMEGA/100"
209455
+ echo " Breaking changes: $BREAKING"
209456
+ echo " Decision: BLOCK"
209457
+ echo "========================================"
209458
+ echo ""
209459
+ echo "Fix breaking changes, set CODERIFTS_SKIP=1 to override intentionally, or use --no-verify."
209460
+ exit 1
209461
+ fi
209462
+
209463
+ if [ "$DECISION" = "REQUIRE_APPROVAL" ] || [ "$DECISION" = "WARN" ]; then
209464
+ echo "CodeRifts: WARNING \u2014 $DECISION \u2014 Risk: $OMEGA/100, Breaking: $BREAKING"
209465
+ fi
209466
+ done
209467
+
209468
+ exit 0
209469
+ `;
209470
+ function findGitDir(startDir) {
209471
+ let dir = startDir || process.cwd();
209472
+ while (dir !== path.dirname(dir)) {
209473
+ const gitDir = path.join(dir, ".git");
209474
+ if (fs.existsSync(gitDir) && fs.statSync(gitDir).isDirectory()) {
209475
+ return gitDir;
209476
+ }
209477
+ dir = path.dirname(dir);
209478
+ }
209479
+ return null;
209480
+ }
209481
+ function getHookPath(gitDir) {
209482
+ return path.join(gitDir, "hooks", "pre-push");
209483
+ }
209484
+ function isCodeRiftsHook(hookPath) {
209485
+ if (!fs.existsSync(hookPath)) return false;
209486
+ const content = fs.readFileSync(hookPath, "utf-8");
209487
+ return content.includes(HOOK_MARKER);
209488
+ }
209489
+ var CLAUDE_MATCHER = "Write|Edit|MultiEdit";
209490
+ var CLAUDE_HOOK_BIN = "coderifts claude-hook";
209491
+ function claudeSettingsPath({ global: isGlobal, cwd } = {}) {
209492
+ if (isGlobal) return path.join(os.homedir(), ".claude", "settings.json");
209493
+ return path.join(cwd || process.cwd(), ".claude", "settings.json");
209494
+ }
209495
+ function claudeHookCommand(logPath) {
209496
+ if (!logPath) return CLAUDE_HOOK_BIN;
209497
+ const abs = path.resolve(String(logPath));
209498
+ return `CODERIFTS_GUARD_EVENT_LOG=${abs} CODERIFTS_SESSION_ID=coderifts-claude-hook ${CLAUDE_HOOK_BIN}`;
209499
+ }
209500
+ function isOurPreToolUseEntry(entry, command) {
209501
+ if (!entry || typeof entry !== "object") return false;
209502
+ if (entry.matcher !== CLAUDE_MATCHER) return false;
209503
+ const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
209504
+ return hooks.some((h) => h && h.type === "command" && String(h.command || "") === command);
209505
+ }
209506
+ function installClaudeHook(opts = {}) {
209507
+ const cwd = opts.cwd || process.cwd();
209508
+ const home = opts.homeDir || os.homedir();
209509
+ const dest = opts.global ? path.join(home, ".claude", "settings.json") : path.join(cwd, ".claude", "settings.json");
209510
+ const command = claudeHookCommand(opts.logPath);
209511
+ const say = (...a) => {
209512
+ if (!opts.silent) console.log(...a);
209513
+ };
209514
+ const block = {
209515
+ matcher: CLAUDE_MATCHER,
209516
+ hooks: [{ type: "command", command, timeout: 60 }]
209517
+ };
209518
+ let existing = {};
209519
+ let hadFile = false;
209520
+ if (fs.existsSync(dest)) {
209521
+ hadFile = true;
209522
+ let raw;
209523
+ try {
209524
+ raw = fs.readFileSync(dest, "utf8");
209525
+ } catch (err) {
209526
+ const message = `Error: cannot read ${dest} (${err && err.message}) \u2014 refusing to write.`;
209527
+ console.error(message);
209528
+ process.exitCode = 1;
209529
+ return { exitCode: 1, code: "READ_FAILED", path: dest, message };
209530
+ }
209531
+ if (String(raw).trim() !== "") {
209532
+ try {
209533
+ existing = JSON.parse(raw);
209534
+ } catch (err) {
209535
+ const message = `Error: ${dest} is not valid JSON \u2014 refusing (no write). Fix or remove the file, then re-run.`;
209536
+ console.error(message);
209537
+ process.exitCode = 1;
209538
+ return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
209539
+ }
209540
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
209541
+ const message = `Error: ${dest} JSON root is not an object \u2014 refusing (no write).`;
209542
+ console.error(message);
209543
+ process.exitCode = 1;
209544
+ return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
209545
+ }
209546
+ }
209547
+ }
209548
+ const hooksRoot = existing.hooks && typeof existing.hooks === "object" && !Array.isArray(existing.hooks) ? { ...existing.hooks } : {};
209549
+ const pre = Array.isArray(hooksRoot.PreToolUse) ? hooksRoot.PreToolUse.slice() : [];
209550
+ if (pre.some((e) => isOurPreToolUseEntry(e, command))) {
209551
+ say(`Claude Code hook already installed at ${dest} (no-op).`);
209552
+ return { exitCode: 0, code: "NOOP", path: dest, wrote: false };
209553
+ }
209554
+ if (opts.dryRun) {
209555
+ return { exitCode: 0, code: "WOULD_WRITE", path: dest, wrote: false, wouldWrite: true, hadFile };
209556
+ }
209557
+ const nextPre = pre.concat([block]);
209558
+ const next = {
209559
+ ...existing,
209560
+ hooks: { ...hooksRoot, PreToolUse: nextPre }
209561
+ };
209562
+ const serialized = `${JSON.stringify(next, null, 2)}
209563
+ `;
209564
+ let backedUp;
209565
+ if (hadFile) {
209566
+ backedUp = `${dest}.bak`;
209567
+ fs.copyFileSync(dest, backedUp);
209568
+ }
209569
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
209570
+ fs.writeFileSync(dest, serialized, "utf8");
209571
+ say(`Claude Code PreToolUse hook written to ${dest}`);
209572
+ if (backedUp) say(`Backup: ${backedUp}`);
209573
+ say("matcher Write|Edit|MultiEdit \u2192 coderifts claude-hook (exit 2 = BLOCK).");
209574
+ return { exitCode: 0, code: "OK", path: dest, backedUp, wrote: true };
209575
+ }
209576
+ var CURSOR_HOOK_BIN = "coderifts cursor-hook";
209577
+ var CURSOR_MATCHER = "Write|Delete";
209578
+ function cursorHooksPath({ global: isGlobal, cwd } = {}) {
209579
+ if (isGlobal) return path.join(os.homedir(), ".cursor", "hooks.json");
209580
+ return path.join(cwd || process.cwd(), ".cursor", "hooks.json");
209581
+ }
209582
+ function cursorHookCommand(logPath) {
209583
+ if (!logPath) return CURSOR_HOOK_BIN;
209584
+ const abs = path.resolve(String(logPath));
209585
+ return `CODERIFTS_GUARD_EVENT_LOG=${abs} CODERIFTS_SESSION_ID=coderifts-cursor-hook ${CURSOR_HOOK_BIN}`;
209586
+ }
209587
+ function isOurCursorEntry(entry, command) {
209588
+ if (!entry || typeof entry !== "object") return false;
209589
+ return String(entry.command || "") === command;
209590
+ }
209591
+ function installCursorHook(opts = {}) {
209592
+ const cwd = opts.cwd || process.cwd();
209593
+ const home = opts.homeDir || os.homedir();
209594
+ const dest = opts.global ? path.join(home, ".cursor", "hooks.json") : path.join(cwd, ".cursor", "hooks.json");
209595
+ const command = cursorHookCommand(opts.logPath);
209596
+ const say = (...a) => {
209597
+ if (!opts.silent) console.log(...a);
209598
+ };
209599
+ const block = {
209600
+ command,
209601
+ matcher: CURSOR_MATCHER,
209602
+ timeout: 60,
209603
+ failClosed: true
209604
+ };
209605
+ let existing = {};
209606
+ let hadFile = false;
209607
+ if (fs.existsSync(dest)) {
209608
+ hadFile = true;
209609
+ let raw;
209610
+ try {
209611
+ raw = fs.readFileSync(dest, "utf8");
209612
+ } catch (err) {
209613
+ const message = `Error: cannot read ${dest} (${err && err.message}) \u2014 refusing to write.`;
209614
+ console.error(message);
209615
+ process.exitCode = 1;
209616
+ return { exitCode: 1, code: "READ_FAILED", path: dest, message };
209617
+ }
209618
+ if (String(raw).trim() !== "") {
209619
+ try {
209620
+ existing = JSON.parse(raw);
209621
+ } catch (_) {
209622
+ const message = `Error: ${dest} is not valid JSON \u2014 refusing (no write). Fix or remove the file, then re-run.`;
209623
+ console.error(message);
209624
+ process.exitCode = 1;
209625
+ return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
209626
+ }
209627
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
209628
+ const message = `Error: ${dest} JSON root is not an object \u2014 refusing (no write).`;
209629
+ console.error(message);
209630
+ process.exitCode = 1;
209631
+ return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
209632
+ }
209633
+ }
209634
+ }
209635
+ const hooksRoot = existing.hooks && typeof existing.hooks === "object" && !Array.isArray(existing.hooks) ? { ...existing.hooks } : {};
209636
+ const pre = Array.isArray(hooksRoot.preToolUse) ? hooksRoot.preToolUse.slice() : [];
209637
+ if (pre.some((e) => isOurCursorEntry(e, command))) {
209638
+ say(`Cursor hook already installed at ${dest} (no-op).`);
209639
+ return { exitCode: 0, code: "NOOP", path: dest, wrote: false };
209640
+ }
209641
+ if (opts.dryRun) {
209642
+ return { exitCode: 0, code: "WOULD_WRITE", path: dest, wrote: false, wouldWrite: true, hadFile };
209643
+ }
209644
+ const next = {
209645
+ ...existing,
209646
+ version: Number.isFinite(existing.version) ? existing.version : 1,
209647
+ hooks: { ...hooksRoot, preToolUse: pre.concat([block]) }
209648
+ };
209649
+ const serialized = `${JSON.stringify(next, null, 2)}
209650
+ `;
209651
+ let backedUp;
209652
+ if (hadFile) {
209653
+ backedUp = `${dest}.bak`;
209654
+ fs.copyFileSync(dest, backedUp);
209655
+ }
209656
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
209657
+ fs.writeFileSync(dest, serialized, "utf8");
209658
+ say(`Cursor preToolUse hook written to ${dest}`);
209659
+ if (backedUp) say(`Backup: ${backedUp}`);
209660
+ say(`matcher ${CURSOR_MATCHER} \u2192 ${CURSOR_HOOK_BIN} (deny = {"permission":"deny"} + exit 2; failClosed:true).`);
209661
+ return { exitCode: 0, code: "OK", path: dest, backedUp, wrote: true };
209662
+ }
209663
+ function install(options = {}) {
209664
+ if (options.global && !options.claude && !options.cursor) {
209665
+ console.error("Error: --global requires --claude or --cursor (writes ~/.claude/settings.json or ~/.cursor/hooks.json).");
209666
+ process.exitCode = 1;
209667
+ return { exitCode: 1, code: "USAGE" };
209668
+ }
209669
+ if (options.cursor) {
209670
+ const r = installCursorHook({
209671
+ global: !!options.global,
209672
+ logPath: options.log,
209673
+ cwd: options.cwd,
209674
+ homeDir: options.homeDir
209675
+ });
209676
+ if (r.exitCode !== 0) return r;
209677
+ }
209678
+ if (options.claude) {
209679
+ const r = installClaudeHook({
209680
+ global: !!options.global,
209681
+ logPath: options.log,
209682
+ cwd: options.cwd,
209683
+ homeDir: options.homeDir
209684
+ });
209685
+ if (r.exitCode !== 0) return r;
209686
+ }
209687
+ const gitDir = findGitDir(options.cwd);
209688
+ if (!gitDir) {
209689
+ if (options.claude || options.cursor) {
209690
+ console.log("Not inside a Git repository \u2014 skipped pre-push hook.");
209691
+ return { exitCode: 0, code: "OK" };
209692
+ }
209693
+ console.error("Error: Not inside a Git repository.");
209694
+ process.exitCode = 1;
209695
+ return { exitCode: 1, code: "NO_GIT" };
209696
+ }
209697
+ const hooksDir = path.join(gitDir, "hooks");
209698
+ if (!fs.existsSync(hooksDir)) {
209699
+ fs.mkdirSync(hooksDir, { recursive: true });
209700
+ }
209701
+ const hookPath = getHookPath(gitDir);
209702
+ if (fs.existsSync(hookPath) && !isCodeRiftsHook(hookPath)) {
209703
+ console.error("Error: A pre-push hook already exists and was not created by CodeRifts.");
209704
+ console.error("Remove it manually or back it up before installing.");
209705
+ process.exitCode = 1;
209706
+ return;
209707
+ }
209708
+ fs.writeFileSync(hookPath, PRE_PUSH_SCRIPT, { mode: 493 });
209709
+ console.log("CodeRifts pre-push hook installed.");
209710
+ console.log("");
209711
+ console.log("Configure your API key:");
209712
+ console.log(" git config coderifts.apiKey <your-key>");
209713
+ console.log("");
209714
+ console.log("Optionally set spec path (default: api/openapi.yaml):");
209715
+ console.log(" git config coderifts.specPath path/to/openapi.yaml");
209716
+ console.log("");
209717
+ console.log("After upgrading the coderifts CLI, re-run this command so the installed");
209718
+ console.log("hook matches the package (already-installed hooks are not auto-updated):");
209719
+ console.log(" coderifts hook install");
209720
+ console.log("");
209721
+ console.log("Agent-using repo? Run: coderifts agent-setup");
209722
+ }
209723
+ function uninstall() {
209724
+ const gitDir = findGitDir();
209725
+ if (!gitDir) {
209726
+ console.error("Error: Not inside a Git repository.");
209727
+ process.exitCode = 1;
209728
+ return;
209729
+ }
209730
+ const hookPath = getHookPath(gitDir);
209731
+ if (!fs.existsSync(hookPath)) {
209732
+ console.log("No pre-push hook found. Nothing to uninstall.");
209733
+ return;
209734
+ }
209735
+ if (!isCodeRiftsHook(hookPath)) {
209736
+ console.error("Error: The existing pre-push hook was not created by CodeRifts. Skipping.");
209737
+ process.exitCode = 1;
209738
+ return;
209739
+ }
209740
+ fs.unlinkSync(hookPath);
209741
+ console.log("CodeRifts pre-push hook removed.");
209742
+ }
209743
+ function status() {
209744
+ const gitDir = findGitDir();
209745
+ if (!gitDir) {
209746
+ console.error("Error: Not inside a Git repository.");
209747
+ process.exitCode = 1;
209748
+ return;
209749
+ }
209750
+ const hookPath = getHookPath(gitDir);
209751
+ const installed = isCodeRiftsHook(hookPath);
209752
+ console.log(`Hook: ${installed ? "installed" : "not installed"}`);
209753
+ if (installed) {
209754
+ try {
209755
+ const apiKey = execSync("git config coderifts.apiKey", { encoding: "utf-8", cwd: path.dirname(gitDir) }).trim();
209756
+ console.log(`API key: ${apiKey ? "configured" : "not configured"}`);
209757
+ } catch {
209758
+ console.log("API key: not configured");
209759
+ }
209760
+ try {
209761
+ const specPath = execSync("git config coderifts.specPath", { encoding: "utf-8", cwd: path.dirname(gitDir) }).trim();
209762
+ console.log(`Spec path: ${specPath || "api/openapi.yaml (default)"}`);
209763
+ } catch {
209764
+ console.log("Spec path: api/openapi.yaml (default)");
209765
+ }
209766
+ try {
209767
+ fs.accessSync(hookPath, fs.constants.X_OK);
209768
+ console.log("Executable: yes");
209769
+ } catch {
209770
+ console.log("Executable: no (run: chmod +x " + hookPath + ")");
209771
+ }
209772
+ }
209773
+ }
209774
+ module2.exports = {
209775
+ install,
209776
+ uninstall,
209777
+ status,
209778
+ PRE_PUSH_SCRIPT,
209779
+ HOOK_MARKER,
209780
+ findGitDir,
209781
+ isCodeRiftsHook,
209782
+ installClaudeHook,
209783
+ claudeSettingsPath,
209784
+ claudeHookCommand,
209785
+ CLAUDE_MATCHER,
209786
+ installCursorHook,
209787
+ cursorHooksPath,
209788
+ cursorHookCommand,
209789
+ CURSOR_MATCHER
209790
+ };
209791
+ }
209792
+ });
209793
+
209794
+ // src/mcp-repo-config.js
209795
+ var require_mcp_repo_config = __commonJS({
209796
+ "src/mcp-repo-config.js"(exports2, module2) {
209797
+ "use strict";
209798
+ var fs = require("fs");
209799
+ var path = require("path");
209800
+ var MCP_URL = "https://app.coderifts.com/mcp";
209801
+ var SERVER_NAME = "coderifts";
209802
+ var MCP_HOSTS = Object.freeze({
209803
+ claude: Object.freeze({
209804
+ id: "claude",
209805
+ relPath: ".mcp.json",
209806
+ label: "Claude Code",
209807
+ // Measured: type+url required. url-without-type is a config error.
209808
+ entry: Object.freeze({
209809
+ type: "http",
209810
+ url: MCP_URL,
209811
+ headers: Object.freeze({
209812
+ Authorization: "Bearer ${CODERIFTS_API_KEY}"
209813
+ })
209814
+ }),
209815
+ shapeNote: "mcpServers.coderifts type=http url (not httpUrl; type is required)"
209816
+ }),
209817
+ cursor: Object.freeze({
209818
+ id: "cursor",
209819
+ relPath: ".cursor/mcp.json",
209820
+ label: "Cursor",
209821
+ // Measured: url only — Cursor docs do not use type for remote HTTP.
209822
+ entry: Object.freeze({
209823
+ url: MCP_URL,
209824
+ headers: Object.freeze({
209825
+ Authorization: "Bearer ${env:CODERIFTS_API_KEY}"
209826
+ })
209827
+ }),
209828
+ shapeNote: "mcpServers.coderifts url (no type \u2014 Cursor remote form)"
209829
+ })
209830
+ });
209831
+ function deepEqual(a, b) {
209832
+ if (a === b) return true;
209833
+ try {
209834
+ return JSON.stringify(a) === JSON.stringify(b);
209835
+ } catch {
209836
+ return false;
209837
+ }
209838
+ }
209839
+ function planMcpMerge(existing, entry) {
209840
+ const hadFile = existing != null;
209841
+ const base = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
209842
+ const servers = base.mcpServers && typeof base.mcpServers === "object" && !Array.isArray(base.mcpServers) ? base.mcpServers : {};
209843
+ const current = Object.prototype.hasOwnProperty.call(servers, SERVER_NAME) ? servers[SERVER_NAME] : void 0;
209844
+ if (deepEqual(current, entry)) {
209845
+ return { action: "skip", next: null, reason: "already present, identical" };
209846
+ }
209847
+ const next = {
209848
+ ...base,
209849
+ mcpServers: {
209850
+ ...servers,
209851
+ [SERVER_NAME]: entry
209852
+ }
209853
+ };
209854
+ if (!hadFile) {
209855
+ return { action: "create", next, reason: "created" };
209856
+ }
209857
+ const reason = current === void 0 ? "added mcpServers.coderifts; preserved other servers" : "updated mcpServers.coderifts; preserved other servers";
209858
+ return { action: "merge", next, reason };
209859
+ }
209860
+ function serializeMcp(doc) {
209861
+ return `${JSON.stringify(doc, null, 2)}
209862
+ `;
209863
+ }
209864
+ function writeHostMcpConfig(opts) {
209865
+ const host = MCP_HOSTS[opts.host];
209866
+ if (!host) {
209867
+ return { action: "error", relPath: "", reason: `unknown host ${opts.host}`, exitCode: 1 };
209868
+ }
209869
+ const outDir = opts.outDir;
209870
+ const dest = path.join(outDir, host.relPath);
209871
+ const exists = opts.exists || fs.existsSync.bind(fs);
209872
+ const readFile = opts.readFile || ((p) => fs.readFileSync(p, "utf8"));
209873
+ const writeFile = opts.writeFile || ((p, c) => {
209874
+ fs.mkdirSync(path.dirname(p), { recursive: true });
209875
+ fs.writeFileSync(p, c, "utf8");
209876
+ });
209877
+ const copyFile = opts.copyFile || ((a, b) => fs.copyFileSync(a, b));
209878
+ const dryRun = !!opts.dryRun;
209879
+ let existing = null;
209880
+ if (exists(dest)) {
209881
+ let raw;
209882
+ try {
209883
+ raw = readFile(dest);
209884
+ } catch (err) {
209885
+ const message = `Error: cannot read ${host.relPath} (${err && err.message}) \u2014 refusing to write.`;
209886
+ return {
209887
+ action: "error",
209888
+ relPath: host.relPath,
209889
+ reason: "read failed",
209890
+ exitCode: 1,
209891
+ message,
209892
+ code: "READ_FAILED"
209893
+ };
209894
+ }
209895
+ if (String(raw).trim() !== "") {
209896
+ let parsed;
209897
+ try {
209898
+ parsed = JSON.parse(raw);
209899
+ } catch {
209900
+ const message = `Error: ${host.relPath} is not valid JSON \u2014 refusing (no write). Fix or remove the file, then re-run.`;
209901
+ return {
209902
+ action: "error",
209903
+ relPath: host.relPath,
209904
+ reason: "not valid JSON",
209905
+ exitCode: 1,
209906
+ message,
209907
+ code: "PARSE_FAILED"
209908
+ };
209909
+ }
209910
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
209911
+ const message = `Error: ${host.relPath} JSON root is not an object \u2014 refusing (no write).`;
209912
+ return {
209913
+ action: "error",
209914
+ relPath: host.relPath,
209915
+ reason: "JSON root is not an object",
209916
+ exitCode: 1,
209917
+ message,
209918
+ code: "PARSE_FAILED"
209919
+ };
209920
+ }
209921
+ existing = parsed;
209922
+ } else {
209923
+ existing = {};
209924
+ }
209925
+ }
209926
+ const plan = planMcpMerge(existing, host.entry);
209927
+ if (plan.action === "skip") {
209928
+ return { action: "skip", relPath: host.relPath, reason: plan.reason, exitCode: 0 };
209929
+ }
209930
+ if (dryRun) {
209931
+ return {
209932
+ action: plan.action,
209933
+ relPath: host.relPath,
209934
+ reason: plan.reason,
209935
+ backedUp: plan.action === "merge" ? `${host.relPath}.bak` : void 0,
209936
+ exitCode: 0,
209937
+ dryRun: true
209938
+ };
209939
+ }
209940
+ let backedUp;
209941
+ if (plan.action === "merge" && exists(dest)) {
209942
+ backedUp = `${dest}.bak`;
209943
+ copyFile(dest, backedUp);
209944
+ }
209945
+ writeFile(dest, serializeMcp(plan.next));
209946
+ return {
209947
+ action: plan.action,
209948
+ relPath: host.relPath,
209949
+ reason: plan.reason,
209950
+ backedUp: backedUp ? `${host.relPath}.bak` : void 0,
209951
+ exitCode: 0
209952
+ };
209953
+ }
209954
+ function mcpConfigPresent(outDir, hostId, deps = {}) {
209955
+ const host = MCP_HOSTS[hostId];
209956
+ if (!host) return false;
209957
+ const exists = deps.exists || fs.existsSync.bind(fs);
209958
+ const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
209959
+ const fp = path.join(outDir, host.relPath);
209960
+ if (!exists(fp)) return false;
209961
+ try {
209962
+ const doc = JSON.parse(readFile(fp));
209963
+ return planMcpMerge(doc, host.entry).action === "skip";
209964
+ } catch {
209965
+ return false;
209966
+ }
209967
+ }
209968
+ module2.exports = {
209969
+ MCP_URL,
209970
+ SERVER_NAME,
209971
+ MCP_HOSTS,
209972
+ planMcpMerge,
209973
+ serializeMcp,
209974
+ writeHostMcpConfig,
209975
+ mcpConfigPresent
209976
+ };
209977
+ }
209978
+ });
209979
+
209980
+ // src/contract-gate-workflow.js
209981
+ var require_contract_gate_workflow = __commonJS({
209982
+ "src/contract-gate-workflow.js"(exports2, module2) {
209983
+ "use strict";
209984
+ var fs = require("fs");
209985
+ var path = require("path");
209986
+ var WORKFLOW_REL = ".github/workflows/coderifts.yml";
209987
+ var GATE_ACTION = "coderifts/contract-gate@v0";
209988
+ var ENFORCEMENT_DOC = "https://github.com/coderifts/contract-gate/blob/main/ENFORCEMENT.md";
209989
+ var WORKFLOW_BODY = `# Written by \`coderifts init --agents\`. Re-run is a no-op when this file already exists.
209990
+ #
209991
+ # This workflow POSTS the Check Run named exactly:
209992
+ # CodeRifts / contract-gate
209993
+ # It does NOT enable branch protection and it does NOT make that check required.
209994
+ # Turn the check on in repo settings to enforce (${ENFORCEMENT_DOC}).
209995
+ #
209996
+ # Copied from the gate repo example (examples/contract-gate.yml) \u2014 action at @v0.
209997
+ name: CodeRifts Contract Gate
209998
+
209999
+ # Run on EVERY pull request. Do NOT add paths: filters \u2014 a required check that
210000
+ # never runs leaves the PR mergeable (see ENFORCEMENT.md, "fail-closed on absence").
210001
+ on:
210002
+ pull_request:
210003
+
210004
+ permissions:
210005
+ checks: write
210006
+ contents: read
210007
+
210008
+ jobs:
210009
+ contract-gate:
210010
+ runs-on: ubuntu-latest
210011
+ steps:
210012
+ - uses: actions/checkout@v4
210013
+ with:
210014
+ # REQUIRED: the gate derives the change set from git diff base...head.
210015
+ fetch-depth: 0
210016
+
210017
+ - uses: ${GATE_ACTION}
210018
+ with:
210019
+ api-key: \${{ secrets.CODERIFTS_API_KEY }}
210020
+ # api-url: https://app.coderifts.com # override only for self-hosted
210021
+ #
210022
+ # Optional \u2014 require a signed CWM delivery attestation (guard \u22659.2.0).
210023
+ # Honest default: false (commented). Uncomment the three lines to enforce.
210024
+ # require-verified-monitoring: 'true'
210025
+ # monitoring-attestation: \${{ vars.CODERIFTS_MONITORING_ATTESTATION }}
210026
+ # monitoring-keyring: \${{ github.workspace }}/.coderifts/monitoring-keys.json
210027
+ `;
210028
+ function writeContractGateWorkflow(opts = {}) {
210029
+ const outDir = opts.outDir;
210030
+ const dest = path.join(outDir, WORKFLOW_REL);
210031
+ const exists = opts.exists || fs.existsSync.bind(fs);
210032
+ const readFile = opts.readFile || ((p) => fs.readFileSync(p, "utf8"));
210033
+ const writeFile = opts.writeFile || ((p, c) => {
210034
+ fs.mkdirSync(path.dirname(p), { recursive: true });
210035
+ fs.writeFileSync(p, c, "utf8");
210036
+ });
210037
+ const dryRun = !!opts.dryRun;
210038
+ if (exists(dest)) {
210039
+ let onDisk = "";
210040
+ try {
210041
+ onDisk = readFile(dest);
210042
+ } catch {
210043
+ onDisk = "";
210044
+ }
210045
+ const reason = onDisk === WORKFLOW_BODY ? "already present, identical" : "exists; installer does not clobber workflows";
210046
+ return { action: "skip", relPath: WORKFLOW_REL, reason, exitCode: 0 };
210047
+ }
210048
+ if (dryRun) {
210049
+ return { action: "create", relPath: WORKFLOW_REL, reason: "created", exitCode: 0, dryRun: true };
210050
+ }
210051
+ writeFile(dest, WORKFLOW_BODY);
210052
+ return { action: "create", relPath: WORKFLOW_REL, reason: "created", exitCode: 0 };
210053
+ }
210054
+ function workflowPresent(outDir, deps = {}) {
210055
+ const exists = deps.exists || fs.existsSync.bind(fs);
210056
+ const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
210057
+ const fp = path.join(outDir, WORKFLOW_REL);
210058
+ if (!exists(fp)) return false;
210059
+ try {
210060
+ return readFile(fp).includes(GATE_ACTION);
210061
+ } catch {
210062
+ return false;
210063
+ }
210064
+ }
210065
+ module2.exports = {
210066
+ WORKFLOW_REL,
210067
+ WORKFLOW_BODY,
210068
+ GATE_ACTION,
210069
+ ENFORCEMENT_DOC,
210070
+ writeContractGateWorkflow,
210071
+ workflowPresent
210072
+ };
210073
+ }
210074
+ });
210075
+
210076
+ // src/commands/init-agents.js
210077
+ var require_init_agents = __commonJS({
210078
+ "src/commands/init-agents.js"(exports2, module2) {
210079
+ "use strict";
210080
+ var fs = require("fs");
210081
+ var path = require("path");
210082
+ var chalk = require_source();
210083
+ var { writeAgentHostFiles, AGENT_HOST_FILES } = require_agent_setup();
210084
+ var { installClaudeHook, installCursorHook } = require_hook();
210085
+ var {
210086
+ MCP_HOSTS,
210087
+ writeHostMcpConfig,
210088
+ mcpConfigPresent
210089
+ } = require_mcp_repo_config();
210090
+ var {
210091
+ WORKFLOW_REL,
210092
+ GATE_ACTION,
210093
+ ENFORCEMENT_DOC,
210094
+ writeContractGateWorkflow,
210095
+ workflowPresent
210096
+ } = require_contract_gate_workflow();
210097
+ if (process.env.NO_COLOR) chalk.level = 0;
210098
+ var USAGE = `Usage: coderifts init --agents [--hosts=claude,cursor,all] [--no-hook] [--no-workflow] [--dry-run] [--check] [--out <dir>]
210099
+
210100
+ Wire this repository for governed agent work in ONE command:
210101
+ 1. repo-level MCP config (per host: .mcp.json / .cursor/mcp.json)
210102
+ 2. generated agent rule files (reuses agent-setup)
210103
+ 3. host hook (Claude Code today; Cursor when hook install --cursor is present)
210104
+ 4. .github/workflows/coderifts.yml (coderifts/contract-gate@v0)
210105
+
210106
+ --hosts <list> claude, cursor, or all (default: all)
210107
+ --no-hook Skip host hook install
210108
+ --no-workflow Skip writing the CI workflow
210109
+ --dry-run Print the plan without writing
210110
+ --check Report which of the four pieces are present (no write)
210111
+ --out <dir> Target directory (default: cwd)
210112
+ Unknown flags exit 1 (never silently ignored).
210113
+ `;
210114
+ var SHARED_RULE = "AGENTS.md";
210115
+ var HOST_RULES = Object.freeze({
210116
+ claude: Object.freeze(["CLAUDE.md"]),
210117
+ cursor: Object.freeze([".cursor/rules/coderifts.mdc"])
210118
+ });
210119
+ var EXTRA_RULES_ON_ALL = Object.freeze([
210120
+ ".github/copilot-instructions.md",
210121
+ "coderifts-langgraph-policy.js",
210122
+ "openai-agent-instructions.md"
210123
+ ]);
210124
+ var NEXT_MANUAL_STEP_LINES = Object.freeze([
210125
+ " Next manual step",
210126
+ " Add CODERIFTS_API_KEY to the repository secrets (Settings \u2192 Secrets and variables \u2192 Actions).",
210127
+ " The CLI never writes a key anywhere."
210128
+ ]);
210129
+ var HONESTY_LINES = Object.freeze([
210130
+ " This command does NOT",
210131
+ ` \xB7 enable branch protection \u2014 ${ENFORCEMENT_DOC}`,
210132
+ " \xB7 make raw host tools inescapable (host convention, not a package guarantee)",
210133
+ ' \xB7 enforce the workflow until "CodeRifts / contract-gate" is a required check in repo settings',
210134
+ " \xB7 write Gemini MCP config (Gemini uses httpUrl at extension level, not a repo file)"
210135
+ ]);
210136
+ function parseHosts(raw) {
210137
+ if (raw == null || raw === true || String(raw).trim() === "") {
210138
+ return { hosts: ["claude", "cursor"], allRules: true };
210139
+ }
210140
+ const parts = String(raw).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
210141
+ if (parts.length === 0) {
210142
+ return { hosts: ["claude", "cursor"], allRules: true, error: `init --agents: --hosts requires a value
210143
+ ${USAGE}` };
210144
+ }
210145
+ const allowed = /* @__PURE__ */ new Set(["claude", "cursor", "all"]);
210146
+ for (const p of parts) {
210147
+ if (!allowed.has(p)) {
210148
+ return {
210149
+ hosts: [],
210150
+ allRules: false,
210151
+ error: `init --agents: unknown host "${p}" (use claude, cursor, all)
210152
+ ${USAGE}`
210153
+ };
210154
+ }
210155
+ }
210156
+ if (parts.includes("all")) {
210157
+ return { hosts: ["claude", "cursor"], allRules: true };
210158
+ }
210159
+ const hosts = [];
210160
+ for (const p of parts) {
210161
+ if (!hosts.includes(p)) hosts.push(p);
210162
+ }
210163
+ return { hosts, allRules: false };
210164
+ }
210165
+ function agentFlagsRequireAgents(options = {}) {
210166
+ return !!(options.check || options.dryRun || options.out || options.hosts != null && String(options.hosts) !== "all" || options.hook === false || options.workflow === false || options.noHook === true || options.noWorkflow === true);
210167
+ }
210168
+ var AGENT_FLAGS_REQUIRE_AGENTS = "Error: --check/--dry-run/--out/--hosts/--no-hook/--no-workflow require --agents";
210169
+ var AGENTS_NO_TEMPLATE = "Error: init --agents does not take a template. Did you mean `coderifts init --agents` (not `coderifts init agent`)?";
210170
+ function rulePathsForHosts(hosts, allRules) {
210171
+ const out = [SHARED_RULE];
210172
+ for (const h of hosts) {
210173
+ const extra = HOST_RULES[h] || [];
210174
+ for (const rel of extra) {
210175
+ if (!out.includes(rel)) out.push(rel);
210176
+ }
210177
+ }
210178
+ if (allRules) {
210179
+ for (const rel of EXTRA_RULES_ON_ALL) {
210180
+ if (!out.includes(rel)) out.push(rel);
210181
+ }
210182
+ }
210183
+ return out;
210184
+ }
210185
+ function hookRelForHost(hostId) {
210186
+ return hostId === "cursor" ? ".cursor/hooks.json" : ".claude/settings.json";
210187
+ }
210188
+ function hookNoteForHost(hostId) {
210189
+ return hostId === "cursor" ? "preToolUse \u2192 coderifts cursor-hook" : "PreToolUse \u2192 coderifts claude-hook";
210190
+ }
210191
+ function wantHook(options) {
210192
+ if (options.noHook === true) return false;
210193
+ if (options.hook === false) return false;
210194
+ return true;
210195
+ }
210196
+ function wantWorkflow(options) {
210197
+ if (options.noWorkflow === true) return false;
210198
+ if (options.workflow === false) return false;
210199
+ return true;
210200
+ }
210201
+ function padVerb(verb) {
210202
+ return String(verb).padEnd(7, " ");
210203
+ }
210204
+ function formatActionLine(action, relPath, note) {
210205
+ const verb = padVerb(action);
210206
+ const extra = note ? ` (${note})` : "";
210207
+ return ` ${verb} ${relPath}${extra}`;
210208
+ }
210209
+ function checkInitAgents(options = {}, deps = {}) {
210210
+ const outDir = options.out ? path.resolve(String(options.out)) : deps.cwd || process.cwd();
210211
+ const parsed = parseHosts(options.hosts);
210212
+ const hosts = parsed.hosts;
210213
+ const allRules = parsed.allRules;
210214
+ const fsDeps = {
210215
+ exists: deps.exists || fs.existsSync.bind(fs),
210216
+ readFile: deps.readFile || ((p) => fs.readFileSync(p, "utf8"))
210217
+ };
210218
+ const mcpPresent = [];
210219
+ const mcpMissing = [];
210220
+ for (const h of hosts) {
210221
+ const rel = MCP_HOSTS[h].relPath;
210222
+ if (mcpConfigPresent(outDir, h, fsDeps)) mcpPresent.push(rel);
210223
+ else mcpMissing.push(rel);
210224
+ }
210225
+ const rulePaths = rulePathsForHosts(hosts, allRules);
210226
+ const rulesPresent = [];
210227
+ const rulesMissing = [];
210228
+ for (const rel of rulePaths) {
210229
+ if (fsDeps.exists(path.join(outDir, rel))) rulesPresent.push(rel);
210230
+ else rulesMissing.push(rel);
210231
+ }
210232
+ const hooksPresent = [];
210233
+ const hooksMissing = [];
210234
+ const hooksWanted = wantHook(options);
210235
+ if (hooksWanted) {
210236
+ for (const h of hosts) {
210237
+ const rel = hookRelForHost(h);
210238
+ const installer = h === "cursor" ? installCursorHook : installClaudeHook;
210239
+ const r = installer({ cwd: outDir, silent: true, dryRun: true });
210240
+ if (r && r.code === "NOOP") hooksPresent.push(rel);
210241
+ else hooksMissing.push(rel);
210242
+ }
210243
+ }
210244
+ const workflowWanted = wantWorkflow(options);
210245
+ const wfPresent = workflowWanted && workflowPresent(outDir, fsDeps);
210246
+ const pieces = {
210247
+ mcp: { present: mcpPresent, missing: mcpMissing },
210248
+ rules: { present: rulesPresent, missing: rulesMissing },
210249
+ hooks: {
210250
+ wanted: hooksWanted,
210251
+ present: hooksPresent,
210252
+ missing: hooksMissing
210253
+ },
210254
+ workflow: {
210255
+ wanted: workflowWanted,
210256
+ present: wfPresent,
210257
+ missing: workflowWanted && !wfPresent ? [WORKFLOW_REL] : []
210258
+ }
210259
+ };
210260
+ const missingCount = mcpMissing.length + rulesMissing.length + hooksMissing.length + (workflowWanted && !wfPresent ? 1 : 0);
210261
+ return {
210262
+ exitCode: missingCount === 0 ? 0 : 1,
210263
+ pieces,
210264
+ allPresent: missingCount === 0,
210265
+ outDir,
210266
+ hosts
210267
+ };
210268
+ }
210269
+ function printCheck(result, log) {
210270
+ const { pieces, outDir, hosts, allPresent } = result;
210271
+ log(chalk.bold("CodeRifts init --agents --check"));
210272
+ log(` target: ${outDir}`);
210273
+ log(` hosts: ${hosts.join(", ")}`);
210274
+ log("");
210275
+ const mcpStatus = pieces.mcp.missing.length === 0 ? "present" : "missing";
210276
+ const mcpDetail = pieces.mcp.missing.length === 0 ? pieces.mcp.present.join(", ") : pieces.mcp.missing.join(", ");
210277
+ log(` MCP config: ${mcpStatus} (${mcpDetail})`);
210278
+ const rulesStatus = pieces.rules.missing.length === 0 ? "present" : "missing";
210279
+ const rulesDetail = pieces.rules.missing.length === 0 ? `${pieces.rules.present.length} files` : pieces.rules.missing.join(", ");
210280
+ log(` Agent rules: ${rulesStatus} (${rulesDetail})`);
210281
+ if (!pieces.hooks.wanted) {
210282
+ log(" Host hooks: skipped (--no-hook)");
210283
+ } else {
210284
+ const hookStatus = pieces.hooks.missing.length === 0 ? "present" : "missing";
210285
+ const hookDetail = pieces.hooks.missing.length === 0 ? pieces.hooks.present.join(", ") : pieces.hooks.missing.join(", ");
210286
+ log(` Host hooks: ${hookStatus} (${hookDetail})`);
210287
+ }
210288
+ if (!pieces.workflow.wanted) {
210289
+ log(" CI workflow: skipped (--no-workflow)");
210290
+ } else {
210291
+ log(pieces.workflow.present ? ` CI workflow: present (${WORKFLOW_REL})` : ` CI workflow: missing (${WORKFLOW_REL})`);
210292
+ }
210293
+ log("");
210294
+ if (allPresent) log(chalk.green(" All four pieces present."));
210295
+ else log(chalk.yellow(" Incomplete \u2014 run: coderifts init --agents"));
210296
+ }
210297
+ function runInitAgents(options = {}, deps = {}) {
210298
+ const log = deps.log || console.log.bind(console);
210299
+ const logErr = deps.logErr || console.error.bind(console);
210300
+ const doExit = deps.exit !== false;
210301
+ const outDir = options.out ? path.resolve(String(options.out)) : deps.cwd || process.cwd();
210302
+ const dryRun = !!options.dryRun;
210303
+ const fsDeps = {
210304
+ exists: deps.exists || fs.existsSync.bind(fs),
210305
+ readFile: deps.readFile || ((p) => fs.readFileSync(p, "utf8")),
210306
+ writeFile: deps.writeFile || ((p, c) => {
210307
+ fs.mkdirSync(path.dirname(p), { recursive: true });
210308
+ fs.writeFileSync(p, c, "utf8");
210309
+ }),
210310
+ copyFile: deps.copyFile || ((a, b) => fs.copyFileSync(a, b))
210311
+ };
210312
+ const parsed = parseHosts(options.hosts);
210313
+ if (parsed.error) {
210314
+ logErr(parsed.error.trimEnd());
210315
+ if (doExit) process.exit(1);
210316
+ return { exitCode: 1, code: "USAGE", message: parsed.error };
210317
+ }
210318
+ const hosts = parsed.hosts;
210319
+ const allRules = parsed.allRules;
210320
+ if (options.check) {
210321
+ const result2 = checkInitAgents({ ...options, out: outDir, hosts: options.hosts }, { ...deps, cwd: outDir });
210322
+ printCheck(result2, log);
210323
+ if (doExit) process.exit(result2.exitCode);
210324
+ return { ...result2, code: result2.allPresent ? "UP_TO_DATE" : "INCOMPLETE" };
210325
+ }
210326
+ const items = [];
210327
+ let fatal;
210328
+ for (const h of hosts) {
210329
+ const r = writeHostMcpConfig({
210330
+ host: h,
210331
+ outDir,
210332
+ dryRun,
210333
+ ...fsDeps
210334
+ });
210335
+ if (r.exitCode !== 0) {
210336
+ fatal = r;
210337
+ break;
210338
+ }
210339
+ items.push({
210340
+ section: "mcp",
210341
+ action: r.action,
210342
+ relPath: r.relPath,
210343
+ note: r.action === "skip" ? r.reason : `${MCP_HOSTS[h].shapeNote}${r.action === "merge" ? "; other servers preserved" : ""}`,
210344
+ backedUp: r.backedUp
210345
+ });
210346
+ }
210347
+ if (fatal) {
210348
+ printPartialAndError(items, fatal, { outDir, hosts, dryRun, log, logErr });
210349
+ if (doExit) process.exit(1);
210350
+ return { exitCode: 1, code: fatal.code || "ERROR", message: fatal.message, items };
210351
+ }
210352
+ const rulePaths = rulePathsForHosts(hosts, allRules);
210353
+ const ruleSummary = writeAgentHostFiles({
210354
+ outDir,
210355
+ force: false,
210356
+ files: AGENT_HOST_FILES,
210357
+ paths: rulePaths,
210358
+ exists: fsDeps.exists,
210359
+ writeFile: fsDeps.writeFile,
210360
+ dryRun
210361
+ });
210362
+ for (const rel of ruleSummary.written) {
210363
+ items.push({ section: "rules", action: "create", relPath: rel, note: "generated agent-host rule" });
210364
+ }
210365
+ for (const rel of ruleSummary.skipped) {
210366
+ items.push({ section: "rules", action: "skip", relPath: rel, note: "exists; installer does not overwrite" });
210367
+ }
210368
+ if (!wantHook(options)) {
210369
+ for (const h of hosts) {
210370
+ items.push({
210371
+ section: "hooks",
210372
+ action: "skip",
210373
+ relPath: hookRelForHost(h),
210374
+ note: "--no-hook"
210375
+ });
210376
+ }
210377
+ } else {
210378
+ for (const h of hosts) {
210379
+ const installer = h === "cursor" ? installCursorHook : installClaudeHook;
210380
+ const r = installer({
210381
+ cwd: outDir,
210382
+ silent: true,
210383
+ dryRun
210384
+ });
210385
+ if (r.exitCode !== 0) {
210386
+ fatal = r;
210387
+ break;
210388
+ }
210389
+ const rel = hookRelForHost(h);
210390
+ if (r.code === "NOOP") {
210391
+ items.push({ section: "hooks", action: "skip", relPath: rel, note: "already installed" });
210392
+ } else if (r.backedUp || r.hadFile) {
210393
+ items.push({
210394
+ section: "hooks",
210395
+ action: "merge",
210396
+ relPath: rel,
210397
+ note: hookNoteForHost(h),
210398
+ backedUp: r.backedUp ? `${rel}.bak` : dryRun ? `${rel}.bak` : void 0
210399
+ });
210400
+ } else {
210401
+ items.push({ section: "hooks", action: "create", relPath: rel, note: hookNoteForHost(h) });
210402
+ }
210403
+ }
210404
+ }
210405
+ if (fatal) {
210406
+ printPartialAndError(items, fatal, { outDir, hosts, dryRun, log, logErr });
210407
+ if (doExit) process.exit(1);
210408
+ return { exitCode: 1, code: fatal.code || "ERROR", message: fatal.message, items };
210409
+ }
210410
+ if (!wantWorkflow(options)) {
210411
+ items.push({ section: "workflow", action: "skip", relPath: WORKFLOW_REL, note: "--no-workflow" });
210412
+ } else {
210413
+ const r = writeContractGateWorkflow({ outDir, dryRun, ...fsDeps });
210414
+ items.push({
210415
+ section: "workflow",
210416
+ action: r.action,
210417
+ relPath: r.relPath,
210418
+ note: r.action === "create" ? `${GATE_ACTION}` : r.reason
210419
+ });
210420
+ }
210421
+ const created = items.filter((i) => i.action === "create").length;
210422
+ const merged = items.filter((i) => i.action === "merge").length;
210423
+ const skipped = items.filter((i) => i.action === "skip").length;
210424
+ const unchanged = created === 0 && merged === 0;
210425
+ const summaryLines = buildSummaryLines({
210426
+ outDir,
210427
+ hosts,
210428
+ dryRun,
210429
+ items,
210430
+ created,
210431
+ merged,
210432
+ skipped,
210433
+ unchanged
210434
+ });
210435
+ for (const line of summaryLines) log(line);
210436
+ const result = {
210437
+ exitCode: 0,
210438
+ code: unchanged ? "NOOP" : "OK",
210439
+ outDir,
210440
+ hosts,
210441
+ dryRun,
210442
+ created,
210443
+ merged,
210444
+ skipped,
210445
+ items,
210446
+ summary: summaryLines.join("\n")
210447
+ };
210448
+ if (doExit) process.exit(0);
210449
+ return result;
210450
+ }
210451
+ function buildSummaryLines({ outDir, hosts, dryRun, items, created, merged, skipped, unchanged }) {
210452
+ const lines = [];
210453
+ lines.push(chalk.bold("CodeRifts init --agents"));
210454
+ lines.push(` target: ${outDir}`);
210455
+ lines.push(` hosts: ${hosts.join(", ")}`);
210456
+ if (dryRun) {
210457
+ lines.push("");
210458
+ lines.push(chalk.yellow(" DRY-RUN \u2014 no files written"));
210459
+ }
210460
+ lines.push("");
210461
+ const sections = [
210462
+ ["mcp", "MCP config"],
210463
+ ["rules", "Agent rules"],
210464
+ ["hooks", "Host hooks"],
210465
+ ["workflow", "CI workflow"]
210466
+ ];
210467
+ for (const [key, title] of sections) {
210468
+ lines.push(` ${title}`);
210469
+ const group = items.filter((i) => i.section === key);
210470
+ for (const it of group) {
210471
+ const verb = it.action === "create" ? "created" : it.action === "merge" ? "merged" : "skipped";
210472
+ const color = it.action === "skip" ? chalk.yellow : chalk.green;
210473
+ lines.push(color(formatActionLine(verb, it.relPath, it.note)));
210474
+ if (it.backedUp) {
210475
+ lines.push(chalk.dim(formatActionLine("backup", it.backedUp, "")));
210476
+ }
210477
+ }
210478
+ }
210479
+ lines.push("");
210480
+ lines.push(chalk.dim(` ${created} created, ${merged} merged, ${skipped} skipped`));
210481
+ if (unchanged && !dryRun) {
210482
+ lines.push(chalk.dim(" Re-run changed nothing."));
210483
+ }
210484
+ if (items.some((i) => i.backedUp)) {
210485
+ lines.push(chalk.dim(" Do not commit *.bak (may contain prior contents, including secrets)."));
210486
+ }
210487
+ lines.push("");
210488
+ for (const line of NEXT_MANUAL_STEP_LINES) lines.push(chalk.dim(line));
210489
+ lines.push("");
210490
+ for (const line of HONESTY_LINES) {
210491
+ lines.push(line === HONESTY_LINES[0] ? chalk.bold(line) : chalk.dim(line));
210492
+ }
210493
+ return lines;
210494
+ }
210495
+ function printPartialAndError(items, fatal, { outDir, hosts, dryRun, log, logErr }) {
210496
+ if (items.length > 0) {
210497
+ const created = items.filter((i) => i.action === "create").length;
210498
+ const merged = items.filter((i) => i.action === "merge").length;
210499
+ const skipped = items.filter((i) => i.action === "skip").length;
210500
+ log(chalk.yellow(" Partial write (stopped on error) \u2014 already-written pieces:"));
210501
+ for (const line of buildSummaryLines({
210502
+ outDir,
210503
+ hosts,
210504
+ dryRun,
210505
+ items,
210506
+ created,
210507
+ merged,
210508
+ skipped,
210509
+ unchanged: false
210510
+ })) {
210511
+ log(line);
210512
+ }
210513
+ }
210514
+ logErr(fatal.message || fatal.reason || "init --agents failed");
210515
+ }
210516
+ module2.exports = {
210517
+ runInitAgents,
210518
+ parseHosts,
210519
+ checkInitAgents,
210520
+ rulePathsForHosts,
210521
+ wantHook,
210522
+ wantWorkflow,
210523
+ agentFlagsRequireAgents,
210524
+ AGENT_FLAGS_REQUIRE_AGENTS,
210525
+ AGENTS_NO_TEMPLATE,
210526
+ USAGE,
210527
+ NEXT_MANUAL_STEP_LINES,
210528
+ HONESTY_LINES,
210529
+ HOST_RULES,
210530
+ EXTRA_RULES_ON_ALL,
210531
+ SHARED_RULE,
210532
+ buildSummaryLines
210533
+ };
210534
+ }
210535
+ });
210536
+
208975
210537
  // src/commands/init.js
208976
210538
  var require_init2 = __commonJS({
208977
210539
  "src/commands/init.js"(exports2, module2) {
@@ -235450,468 +237012,534 @@ var require_status = __commonJS({
235450
237012
  }
235451
237013
  });
235452
237014
 
235453
- // src/commands/hook.js
235454
- var require_hook = __commonJS({
235455
- "src/commands/hook.js"(exports2, module2) {
237015
+ // src/provider/github-enforcement.js
237016
+ var require_github_enforcement = __commonJS({
237017
+ "src/provider/github-enforcement.js"(exports2, module2) {
235456
237018
  "use strict";
235457
237019
  var fs = require("fs");
235458
237020
  var path = require("path");
235459
- var os = require("os");
235460
- var { execSync } = require("child_process");
235461
- var HOOK_MARKER = "# CodeRifts pre-push hook";
235462
- var PRE_PUSH_SCRIPT = `#!/bin/sh
235463
- ${HOOK_MARKER}
235464
- # Checks API spec changes before pushing.
235465
- # The pre-push hook receives lines on stdin:
235466
- # <local ref> <local sha> <remote ref> <remote sha>
235467
- #
235468
- # Re-install after CLI upgrades: coderifts hook install
235469
- #
235470
- # Exit map (fail-closed when configured):
235471
- # CODERIFTS_SKIP=1 \u2192 exit 0 (explicit loud override)
235472
- # no apiKey \u2192 exit 0 soft (not configured)
235473
- # BLOCK / STOP / UNKNOWN \u2192 exit 1
235474
- # INDETERMINATE (no decision obtainable) \u2192 exit 1
235475
- # ALLOW / analysis clean \u2192 exit 0
235476
- # REQUIRE_APPROVAL / WARN \u2192 warn, exit 0
235477
-
235478
- # Explicit escape hatch \u2014 never the default on error; human must set this.
235479
- if [ "\${CODERIFTS_SKIP:-}" = "1" ]; then
235480
- echo "CodeRifts: CODERIFTS_SKIP=1 set \u2014 push allowed (explicit override)." >&2
235481
- exit 0
235482
- fi
235483
-
235484
- CODERIFTS_API_KEY=$(git config coderifts.apiKey)
235485
- SPEC_PATH=$(git config coderifts.specPath || echo "api/openapi.yaml")
235486
- ZERO="0000000000000000000000000000000000000000"
235487
-
235488
- if [ -z "$CODERIFTS_API_KEY" ]; then
235489
- echo "CodeRifts: No API key configured. Run: git config coderifts.apiKey <your-key>"
235490
- exit 0 # Don't block if not configured
235491
- fi
235492
-
235493
- # --- helpers: three-state git blob read (present | absent | error) ---
235494
- # Sets: BLOB_KIND, BLOB_CONTENT, BLOB_ERR. Never maps unreadable \u2192 empty string.
235495
- git_blob_at() {
235496
- _ref="$1"
235497
- _path="$2"
235498
- BLOB_CONTENT=""
235499
- BLOB_ERR=""
235500
- BLOB_KIND=""
235501
- if ! git rev-parse --verify "$_ref^{commit}" >/dev/null 2>&1; then
235502
- BLOB_KIND=error
235503
- BLOB_ERR="GIT_ERROR: cannot resolve ref $_ref"
235504
- return 1
235505
- fi
235506
- _errf=$(mktemp 2>/dev/null || echo "/tmp/coderifts-hook-err.$$")
235507
- if BLOB_CONTENT=$(git show "$_ref:$_path" 2>"$_errf"); then
235508
- BLOB_KIND=present
235509
- rm -f "$_errf"
235510
- return 0
235511
- fi
235512
- _err=$(cat "$_errf" 2>/dev/null)
235513
- rm -f "$_errf"
235514
- case "$_err" in
235515
- *"does not exist in"*|*"exists on disk, but not in"*)
235516
- BLOB_KIND=absent
235517
- BLOB_CONTENT=""
235518
- return 0
235519
- ;;
235520
- esac
235521
- BLOB_KIND=error
235522
- BLOB_ERR="GIT_ERROR: git show $_ref:$_path failed"
235523
- return 1
235524
- }
235525
-
235526
- # New-branch baseline: merge-base with default branch (not empty before).
235527
- # Prints merge-base SHA on success; nonzero if none can be resolved.
235528
- resolve_new_branch_base_ref() {
235529
- _local="$1"
235530
- for _cand in origin/HEAD origin/main origin/master main master; do
235531
- _mb=$(git merge-base "$_local" "$_cand" 2>/dev/null) || continue
235532
- if [ -n "$_mb" ] && [ "$_mb" != "$ZERO" ]; then
235533
- echo "$_mb"
235534
- return 0
235535
- fi
235536
- done
235537
- return 1
235538
- }
235539
-
235540
- # Read stdin lines provided by git pre-push
235541
- while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
235542
- # Skip delete pushes
235543
- if [ "$LOCAL_SHA" = "$ZERO" ]; then
235544
- continue
235545
- fi
235546
-
235547
- # after = local (about-to-be-pushed) commit \u2014 three-state
235548
- if ! git_blob_at "$LOCAL_SHA" "$SPEC_PATH"; then
235549
- echo "CodeRifts: $BLOB_ERR (after=$SPEC_PATH at $LOCAL_SHA). Fail-closed."
235550
- exit 1
235551
- fi
235552
- if [ "$BLOB_KIND" = "absent" ]; then
235553
- continue # Spec doesn't exist in local commit, skip
235554
- fi
235555
- HEAD_SPEC=$BLOB_CONTENT
235556
-
235557
- # before = remote tip, or for new branch (zero remote SHA) a merge-base baseline
235558
- if [ "$REMOTE_SHA" = "$ZERO" ]; then
235559
- BASE_REF=$(resolve_new_branch_base_ref "$LOCAL_SHA") || {
235560
- echo "CodeRifts: GIT_ERROR \u2014 cannot resolve merge-base baseline for new branch (tried origin/HEAD, origin/main, origin/master, main, master). Fail-closed."
235561
- exit 1
235562
- }
235563
- else
235564
- BASE_REF=$REMOTE_SHA
235565
- fi
235566
-
235567
- if ! git_blob_at "$BASE_REF" "$SPEC_PATH"; then
235568
- echo "CodeRifts: $BLOB_ERR (before=$SPEC_PATH at $BASE_REF). Fail-closed \u2014 not treating as new spec."
235569
- exit 1
235570
- fi
235571
- if [ "$BLOB_KIND" = "absent" ]; then
235572
- # Honest NEW_ARTIFACT: path genuinely not at baseline
235573
- echo "CodeRifts: New artifact (spec absent at baseline $BASE_REF:$SPEC_PATH); allowing push."
235574
- continue
235575
- fi
235576
- BASE_SPEC=$BLOB_CONTENT
235577
-
235578
- # If specs are identical, nothing to check
235579
- if [ "$BASE_SPEC" = "$HEAD_SPEC" ]; then
235580
- continue
235581
- fi
235582
-
235583
- echo "CodeRifts: Checking API spec changes (baseline $BASE_REF)..."
235584
-
235585
- # Capture body + HTTP status separately (fail-closed on non-2xx when configured).
235586
- _CR_BODY=$(mktemp 2>/dev/null || echo "/tmp/coderifts-hook-body.$$")
235587
- _CR_HTTP=$(curl -sS -o "$_CR_BODY" -w "%{http_code}" -X POST https://app.coderifts.com/api/v1/diff \\
235588
- -H "Authorization: Bearer $CODERIFTS_API_KEY" \\
235589
- -H "Content-Type: application/json" \\
235590
- -d "{\\"before\\": $(echo "$BASE_SPEC" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'), \\"after\\": $(echo "$HEAD_SPEC" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')}" 2>/dev/null) || _CR_HTTP="000"
235591
- RESULT=$(cat "$_CR_BODY" 2>/dev/null)
235592
- rm -f "$_CR_BODY"
235593
-
235594
- # (a) HTTP non-2xx (incl. transport fail \u2192 000) \u2192 configured-but-indeterminate
235595
- case "$_CR_HTTP" in
235596
- 2??) ;;
235597
- *)
235598
- echo "CodeRifts could not obtain a decision (HTTP \${_CR_HTTP:-000}); push blocked \u2014 set CODERIFTS_SKIP=1 to override intentionally" >&2
235599
- exit 1
235600
- ;;
235601
- esac
235602
-
235603
- # Prefer closed-set execution_action; omega_decision only when action is absent.
235604
- # Indeterminate classes \u2192 INDETERMINATE (fail closed), never default ALLOW.
235605
- # Severity: BLOCK/STOP \u2192 exit 1; REQUIRE_APPROVAL/WARN \u2192 warn; unknown action \u2192 halt;
235606
- # INDETERMINATE / empty \u2192 exit 1 (configured-but-indeterminate).
235607
- DECISION=$(echo "$RESULT" | python3 -c '
235608
- import sys, json
235609
- raw = sys.stdin.read()
235610
- if raw is None or not str(raw).strip():
235611
- print("INDETERMINATE")
235612
- raise SystemExit(0)
235613
- try:
235614
- d = json.loads(raw)
235615
- except Exception:
235616
- # (b) 2xx but unparseable JSON
235617
- print("INDETERMINATE")
235618
- raise SystemExit(0)
235619
- if not isinstance(d, dict):
235620
- print("INDETERMINATE")
235621
- raise SystemExit(0)
235622
- # API error body without a decision (e.g. {"error":"internal_error"} / server_error)
235623
- if d.get("error") and not (
235624
- d.get("omega_decision") or d.get("decision")
235625
- or d.get("execution_action")
235626
- or (isinstance(d.get("decision_result"), dict) and d["decision_result"].get("execution_action"))
235627
- ):
235628
- print("INDETERMINATE")
235629
- raise SystemExit(0)
235630
- ea = None
235631
- dr = d.get("decision_result")
235632
- if isinstance(dr, dict) and isinstance(dr.get("execution_action"), str):
235633
- ea = dr["execution_action"]
235634
- elif isinstance(d.get("execution_action"), str):
235635
- ea = d["execution_action"]
235636
- closed = {"CONTINUE", "CONTINUE_WITH_MONITORING", "REQUEST_APPROVAL", "STOP"}
235637
- if ea is not None and ea != "":
235638
- if ea not in closed:
235639
- print("UNKNOWN")
235640
- raise SystemExit(0)
235641
- if ea in ("CONTINUE", "CONTINUE_WITH_MONITORING"):
235642
- print("ALLOW")
235643
- elif ea == "STOP":
235644
- print("BLOCK")
235645
- else:
235646
- print("REQUIRE_APPROVAL")
235647
- raise SystemExit(0)
235648
- # (c) parsed but no execution_action AND no decision \u2014 do NOT default ALLOW
235649
- od = d.get("omega_decision") or d.get("decision")
235650
- if od is None or od == "":
235651
- print("INDETERMINATE")
235652
- raise SystemExit(0)
235653
- print(od)
235654
- ' 2>/dev/null)
235655
- # (d) empty DECISION (python missing / crash with empty stdout) \u2192 fail closed
235656
- if [ -z "$DECISION" ]; then
235657
- DECISION="INDETERMINATE"
235658
- fi
235659
- OMEGA=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('omega_api',0))" 2>/dev/null)
235660
- BREAKING=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('breaking_changes',[])))" 2>/dev/null)
235661
-
235662
- if [ "$DECISION" = "INDETERMINATE" ]; then
235663
- echo "CodeRifts could not obtain a decision (indeterminate response); push blocked \u2014 set CODERIFTS_SKIP=1 to override intentionally" >&2
235664
- exit 1
235665
- fi
235666
-
235667
- if [ "$DECISION" = "UNKNOWN" ]; then
235668
- echo ""
235669
- echo "========================================"
235670
- echo " CodeRifts: PUSH BLOCKED"
235671
- echo "========================================"
235672
- echo " Reason: unrecognised execution_action is not permission"
235673
- echo " Fail-closed (unknown present action)."
235674
- echo "========================================"
235675
- echo ""
235676
- echo "Set CODERIFTS_SKIP=1 to override intentionally (or git push --no-verify)."
235677
- exit 1
235678
- fi
235679
-
235680
- if [ "$DECISION" = "BLOCK" ]; then
235681
- echo ""
235682
- echo "========================================"
235683
- echo " CodeRifts: PUSH BLOCKED"
235684
- echo "========================================"
235685
- echo " Risk score: $OMEGA/100"
235686
- echo " Breaking changes: $BREAKING"
235687
- echo " Decision: BLOCK"
235688
- echo "========================================"
235689
- echo ""
235690
- echo "Fix breaking changes, set CODERIFTS_SKIP=1 to override intentionally, or use --no-verify."
235691
- exit 1
235692
- fi
235693
-
235694
- if [ "$DECISION" = "REQUIRE_APPROVAL" ] || [ "$DECISION" = "WARN" ]; then
235695
- echo "CodeRifts: WARNING \u2014 $DECISION \u2014 Risk: $OMEGA/100, Breaking: $BREAKING"
235696
- fi
235697
- done
235698
-
235699
- exit 0
235700
- `;
235701
- function findGitDir(startDir) {
235702
- let dir = startDir || process.cwd();
235703
- while (dir !== path.dirname(dir)) {
235704
- const gitDir = path.join(dir, ".git");
235705
- if (fs.existsSync(gitDir) && fs.statSync(gitDir).isDirectory()) {
235706
- return gitDir;
235707
- }
235708
- dir = path.dirname(dir);
237021
+ var {
237022
+ CHECK_NAME,
237023
+ extractRequiredContexts
237024
+ } = require_setup_required_check();
237025
+ var { GATE_ACTION } = require_contract_gate_workflow();
237026
+ var EVIDENCE_SPEC = "provider-enforcement-evidence.v1";
237027
+ var APP_INSTALLATION = Object.freeze({
237028
+ can_query_protection: false,
237029
+ permissions: Object.freeze([
237030
+ "contents:read",
237031
+ "pull_requests:read+write",
237032
+ "checks:read+write"
237033
+ ]),
237034
+ missing_scope: "administration:read",
237035
+ reason: "GET /repos/{owner}/{repo}/branches/{branch}/protection requires administration:read (or repo admin). The CodeRifts GitHub App permission set is contents:read, pull_requests:read+write, checks:read+write \u2014 administration:read is not granted."
237036
+ });
237037
+ var NO_TOKEN_INSTRUCTION = "NO token \u2014 set GITHUB_TOKEN (repo + administration:read; Environments: Read if querying --env). The CodeRifts GitHub App installation cannot query branch protection (permissions: contents:read, pull_requests:read+write, checks:read+write \u2014 missing administration:read).";
237038
+ var LAYER_IDS = Object.freeze([
237039
+ "required_check",
237040
+ "enforce_admins",
237041
+ "strict_status_checks",
237042
+ "environment_protection",
237043
+ "workflow_contract_gate",
237044
+ "workflow_deploy_gate"
237045
+ ]);
237046
+ var STATUS = Object.freeze({
237047
+ VERIFIED: "VERIFIED",
237048
+ NOT_VERIFIED: "NOT_VERIFIED",
237049
+ UNVERIFIABLE: "UNVERIFIABLE"
237050
+ });
237051
+ function resolveGitHubAuth(env = process.env) {
237052
+ const user = String(env && (env.GITHUB_TOKEN || env.GH_TOKEN) || "").trim();
237053
+ if (user) {
237054
+ return { ok: true, source: "github_token", token: user };
235709
237055
  }
235710
- return null;
237056
+ const app = String(env && (env.GITHUB_APP_TOKEN || env.CODERIFTS_GITHUB_APP_TOKEN) || "").trim();
237057
+ if (app) {
237058
+ return { ok: true, source: "github_app_installation", token: app };
237059
+ }
237060
+ return { ok: false, source: "none", token: null, reason: NO_TOKEN_INSTRUCTION };
235711
237061
  }
235712
- function getHookPath(gitDir) {
235713
- return path.join(gitDir, "hooks", "pre-push");
237062
+ function redactSecrets(text) {
237063
+ return String(text || "").replace(/Bearer\s+\S+/gi, "Bearer [redacted]").replace(/ghp_[A-Za-z0-9_]+/g, "ghp_[redacted]").replace(/github_pat_[A-Za-z0-9_]+/g, "github_pat_[redacted]");
235714
237064
  }
235715
- function isCodeRiftsHook(hookPath) {
235716
- if (!fs.existsSync(hookPath)) return false;
235717
- const content = fs.readFileSync(hookPath, "utf-8");
235718
- return content.includes(HOOK_MARKER);
237065
+ function createGitHubHttpClient(opts = {}) {
237066
+ const token = opts.token;
237067
+ const fetchFn = opts.fetchImpl || (typeof fetch === "function" ? fetch.bind(globalThis) : null);
237068
+ const apiBase = (opts.apiBase || "https://api.github.com").replace(/\/$/, "");
237069
+ return {
237070
+ name: "github",
237071
+ async get(apiPath) {
237072
+ const endpoint = `${apiBase}/${String(apiPath).replace(/^\//, "")}`;
237073
+ if (!fetchFn) {
237074
+ return {
237075
+ status: 0,
237076
+ body: null,
237077
+ endpoint,
237078
+ error: "fetch is not available in this runtime"
237079
+ };
237080
+ }
237081
+ const res = await fetchFn(endpoint, {
237082
+ method: "GET",
237083
+ headers: {
237084
+ Authorization: `Bearer ${token}`,
237085
+ Accept: "application/vnd.github+json",
237086
+ "X-GitHub-Api-Version": "2022-11-28",
237087
+ "User-Agent": "coderifts-cli"
237088
+ }
237089
+ });
237090
+ const text = await res.text();
237091
+ let body = null;
237092
+ try {
237093
+ body = text ? JSON.parse(text) : null;
237094
+ } catch {
237095
+ body = null;
237096
+ }
237097
+ return { status: res.status, body, endpoint };
237098
+ }
237099
+ };
235719
237100
  }
235720
- var CLAUDE_MATCHER = "Write|Edit|MultiEdit";
235721
- var CLAUDE_HOOK_BIN = "coderifts claude-hook";
235722
- function claudeSettingsPath({ global: isGlobal, cwd } = {}) {
235723
- if (isGlobal) return path.join(os.homedir(), ".claude", "settings.json");
235724
- return path.join(cwd || process.cwd(), ".claude", "settings.json");
237101
+ function enforceAdminsEnabled(protection) {
237102
+ if (!protection || typeof protection !== "object") return false;
237103
+ const ea = protection.enforce_admins;
237104
+ if (ea === true) return true;
237105
+ if (ea && typeof ea === "object" && ea.enabled === true) return true;
237106
+ return false;
235725
237107
  }
235726
- function claudeHookCommand(logPath) {
235727
- if (!logPath) return CLAUDE_HOOK_BIN;
235728
- const abs = path.resolve(String(logPath));
235729
- return `CODERIFTS_GUARD_EVENT_LOG=${abs} CODERIFTS_SESSION_ID=coderifts-claude-hook ${CLAUDE_HOOK_BIN}`;
237108
+ function strictEnabled(protection) {
237109
+ if (!protection || typeof protection !== "object") return false;
237110
+ const rsc = protection.required_status_checks;
237111
+ return !!(rsc && typeof rsc === "object" && rsc.strict === true);
235730
237112
  }
235731
- function isOurPreToolUseEntry(entry, command) {
235732
- if (!entry || typeof entry !== "object") return false;
235733
- if (entry.matcher !== CLAUDE_MATCHER) return false;
235734
- const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
235735
- return hooks.some((h) => h && h.type === "command" && String(h.command || "") === command);
237113
+ function environmentProtected(body) {
237114
+ if (!body || typeof body !== "object") return false;
237115
+ const rules = Array.isArray(body.protection_rules) ? body.protection_rules : [];
237116
+ if (rules.length > 0) return true;
237117
+ const policy = body.deployment_branch_policy;
237118
+ if (policy && typeof policy === "object") {
237119
+ return policy.protected_branches === true || policy.custom_branch_policies === true;
237120
+ }
237121
+ return false;
235736
237122
  }
235737
- function installClaudeHook(opts = {}) {
235738
- const cwd = opts.cwd || process.cwd();
235739
- const home = opts.homeDir || os.homedir();
235740
- const dest = opts.global ? path.join(home, ".claude", "settings.json") : path.join(cwd, ".claude", "settings.json");
235741
- const command = claudeHookCommand(opts.logPath);
235742
- const block = {
235743
- matcher: CLAUDE_MATCHER,
235744
- hooks: [{ type: "command", command, timeout: 60 }]
235745
- };
235746
- let existing = {};
235747
- let hadFile = false;
235748
- if (fs.existsSync(dest)) {
235749
- hadFile = true;
235750
- let raw;
235751
- try {
235752
- raw = fs.readFileSync(dest, "utf8");
235753
- } catch (err) {
235754
- const message = `Error: cannot read ${dest} (${err && err.message}) \u2014 refusing to write.`;
235755
- console.error(message);
235756
- process.exitCode = 1;
235757
- return { exitCode: 1, code: "READ_FAILED", path: dest, message };
237123
+ function layer(id, status, extra = {}) {
237124
+ return { id, status, ...extra };
237125
+ }
237126
+ function evaluateLayers({
237127
+ protectionRead,
237128
+ environmentRead,
237129
+ environmentName,
237130
+ local,
237131
+ auth
237132
+ } = {}) {
237133
+ const layers = [];
237134
+ const localFiles = local || { contractGateFiles: [], deployGateFiles: [] };
237135
+ const noAuth = !auth || auth.source === "none" || !auth.ok;
237136
+ const prot = protectionRead || null;
237137
+ if (noAuth && !prot) {
237138
+ const why = auth && auth.reason || NO_TOKEN_INSTRUCTION;
237139
+ for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
237140
+ layers.push(layer(id, STATUS.UNVERIFIABLE, {
237141
+ reason: why,
237142
+ endpoint: null,
237143
+ missing_scope: APP_INSTALLATION.missing_scope
237144
+ }));
235758
237145
  }
235759
- if (String(raw).trim() !== "") {
235760
- try {
235761
- existing = JSON.parse(raw);
235762
- } catch (err) {
235763
- const message = `Error: ${dest} is not valid JSON \u2014 refusing (no write). Fix or remove the file, then re-run.`;
235764
- console.error(message);
235765
- process.exitCode = 1;
235766
- return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
235767
- }
235768
- if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
235769
- const message = `Error: ${dest} JSON root is not an object \u2014 refusing (no write).`;
235770
- console.error(message);
235771
- process.exitCode = 1;
235772
- return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
235773
- }
237146
+ } else if (!prot) {
237147
+ const why = "branch protection was not queried";
237148
+ for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
237149
+ layers.push(layer(id, STATUS.UNVERIFIABLE, { reason: why, endpoint: null }));
237150
+ }
237151
+ } else if (prot.status === 403) {
237152
+ const why = "GitHub HTTP 403 \u2014 missing scope administration:read (branch protection). Grant Administration: Read on the token, or use a repo-admin token.";
237153
+ const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
237154
+ for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
237155
+ layers.push(layer(id, STATUS.UNVERIFIABLE, {
237156
+ reason: why,
237157
+ endpoint,
237158
+ missing_scope: "administration:read",
237159
+ http_status: 403
237160
+ }));
237161
+ }
237162
+ } else if (prot.status === 404) {
237163
+ const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
237164
+ layers.push(layer("required_check", STATUS.NOT_VERIFIED, {
237165
+ reason: `classic branch protection is absent \u2014 required check "${CHECK_NAME}" is not required`,
237166
+ endpoint,
237167
+ http_status: 404
237168
+ }));
237169
+ layers.push(layer("enforce_admins", STATUS.NOT_VERIFIED, {
237170
+ reason: "classic branch protection is absent \u2014 administrators are unrestricted",
237171
+ endpoint,
237172
+ http_status: 404
237173
+ }));
237174
+ layers.push(layer("strict_status_checks", STATUS.NOT_VERIFIED, {
237175
+ reason: "classic branch protection is absent \u2014 required_status_checks.strict is not set",
237176
+ endpoint,
237177
+ http_status: 404
237178
+ }));
237179
+ } else if (prot.status >= 400 || prot.status === 0) {
237180
+ const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
237181
+ const why = `GitHub HTTP ${prot.status || 0} reading branch protection \u2014 cannot verify`;
237182
+ for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
237183
+ layers.push(layer(id, STATUS.UNVERIFIABLE, {
237184
+ reason: redactSecrets(why),
237185
+ endpoint,
237186
+ http_status: prot.status
237187
+ }));
235774
237188
  }
237189
+ } else {
237190
+ const protection = prot.body && typeof prot.body === "object" ? prot.body : {};
237191
+ const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
237192
+ const contexts = extractRequiredContexts(protection);
237193
+ const hasCheck = contexts.includes(CHECK_NAME);
237194
+ layers.push(layer("required_check", hasCheck ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
237195
+ reason: hasCheck ? `required_status_checks includes "${CHECK_NAME}"` : `required_status_checks does not include "${CHECK_NAME}" (have: ${contexts.join(", ") || "none"})`,
237196
+ endpoint,
237197
+ required_contexts: contexts
237198
+ }));
237199
+ const admins = enforceAdminsEnabled(protection);
237200
+ layers.push(layer("enforce_admins", admins ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
237201
+ reason: admins ? "enforce_admins.enabled is true \u2014 administrators cannot bypass required checks" : "enforce_admins.enabled is false \u2014 repository administrators can bypass required checks",
237202
+ endpoint,
237203
+ enforce_admins: admins
237204
+ }));
237205
+ const strict = strictEnabled(protection);
237206
+ layers.push(layer("strict_status_checks", strict ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
237207
+ reason: strict ? "required_status_checks.strict is true" : "required_status_checks.strict is not true \u2014 a branch need not be up to date before merge",
237208
+ endpoint,
237209
+ strict
237210
+ }));
235775
237211
  }
235776
- const hooksRoot = existing.hooks && typeof existing.hooks === "object" && !Array.isArray(existing.hooks) ? { ...existing.hooks } : {};
235777
- const pre = Array.isArray(hooksRoot.PreToolUse) ? hooksRoot.PreToolUse.slice() : [];
235778
- if (pre.some((e) => isOurPreToolUseEntry(e, command))) {
235779
- console.log(`Claude Code hook already installed at ${dest} (no-op).`);
235780
- return { exitCode: 0, code: "NOOP", path: dest, wrote: false };
237212
+ if (!environmentName) {
237213
+ layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
237214
+ reason: "no --env supplied; pass --env <GitHub Environment name> to query environment protection rules",
237215
+ endpoint: null
237216
+ }));
237217
+ } else if (noAuth && !environmentRead) {
237218
+ layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
237219
+ reason: auth && auth.reason || NO_TOKEN_INSTRUCTION,
237220
+ endpoint: null,
237221
+ missing_scope: "actions:read / Environments: Read"
237222
+ }));
237223
+ } else if (!environmentRead) {
237224
+ layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
237225
+ reason: "environment was not queried",
237226
+ endpoint: null
237227
+ }));
237228
+ } else if (environmentRead.status === 403) {
237229
+ layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
237230
+ reason: "GitHub HTTP 403 \u2014 missing scope to read environments (Actions/Environments: Read, or repo admin).",
237231
+ endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
237232
+ missing_scope: "environments:read",
237233
+ http_status: 403
237234
+ }));
237235
+ } else if (environmentRead.status === 404) {
237236
+ layers.push(layer("environment_protection", STATUS.NOT_VERIFIED, {
237237
+ reason: `GitHub Environment "${environmentName}" does not exist (or has no protection)`,
237238
+ endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
237239
+ http_status: 404
237240
+ }));
237241
+ } else if (environmentRead.status >= 400 || environmentRead.status === 0) {
237242
+ layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
237243
+ reason: `GitHub HTTP ${environmentRead.status || 0} reading environment "${environmentName}"`,
237244
+ endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
237245
+ http_status: environmentRead.status
237246
+ }));
237247
+ } else {
237248
+ const ok = environmentProtected(environmentRead.body);
237249
+ layers.push(layer("environment_protection", ok ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
237250
+ reason: ok ? `GitHub Environment "${environmentName}" has protection rules or a deployment branch policy` : `GitHub Environment "${environmentName}" exists but has no protection_rules and no deployment_branch_policy`,
237251
+ endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
237252
+ environment: environmentName
237253
+ }));
235781
237254
  }
235782
- const nextPre = pre.concat([block]);
235783
- const next = {
235784
- ...existing,
235785
- hooks: { ...hooksRoot, PreToolUse: nextPre }
237255
+ const cg = localFiles.contractGateFiles || [];
237256
+ layers.push(layer("workflow_contract_gate", cg.length ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
237257
+ reason: cg.length ? `workflow references ${GATE_ACTION} (${cg.join(", ")})` : `no .github/workflows file references ${GATE_ACTION}`,
237258
+ endpoint: "local:.github/workflows",
237259
+ files: cg
237260
+ }));
237261
+ const dg = localFiles.deployGateFiles || [];
237262
+ layers.push(layer("workflow_deploy_gate", dg.length ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
237263
+ reason: dg.length ? `workflow runs coderifts deploy-gate (${dg.join(", ")})` : "no .github/workflows file runs coderifts deploy-gate",
237264
+ endpoint: "local:.github/workflows",
237265
+ files: dg
237266
+ }));
237267
+ const byId = Object.fromEntries(layers.map((l) => [l.id, l]));
237268
+ const inescapable = LAYER_IDS.every((id) => byId[id] && byId[id].status === STATUS.VERIFIED);
237269
+ return {
237270
+ layers,
237271
+ inescapable_deploy: inescapable,
237272
+ claim: {
237273
+ inescapable_deploy: inescapable,
237274
+ basis: inescapable ? "all six layers VERIFIED from queried provider evidence + local workflow files" : "inescapable_deploy stays false until every layer is VERIFIED"
237275
+ }
235786
237276
  };
235787
- const serialized = `${JSON.stringify(next, null, 2)}
235788
- `;
235789
- let backedUp;
235790
- if (hadFile) {
235791
- backedUp = `${dest}.bak`;
235792
- fs.copyFileSync(dest, backedUp);
235793
- }
235794
- fs.mkdirSync(path.dirname(dest), { recursive: true });
235795
- fs.writeFileSync(dest, serialized, "utf8");
235796
- console.log(`Claude Code PreToolUse hook written to ${dest}`);
235797
- if (backedUp) console.log(`Backup: ${backedUp}`);
235798
- console.log("matcher Write|Edit|MultiEdit \u2192 coderifts claude-hook (exit 2 = BLOCK).");
235799
- return { exitCode: 0, code: "OK", path: dest, backedUp, wrote: true };
235800
237277
  }
235801
- function install(options = {}) {
235802
- if (options.global && !options.claude) {
235803
- console.error("Error: --global requires --claude (writes ~/.claude/settings.json).");
235804
- process.exitCode = 1;
235805
- return { exitCode: 1, code: "USAGE" };
235806
- }
235807
- if (options.claude) {
235808
- const r = installClaudeHook({
235809
- global: !!options.global,
235810
- logPath: options.log,
235811
- cwd: options.cwd,
235812
- homeDir: options.homeDir
235813
- });
235814
- if (r.exitCode !== 0) return r;
237278
+ function scanLocalWorkflows(cwd, deps = {}) {
237279
+ const exists = deps.exists || fs.existsSync.bind(fs);
237280
+ const readdir = deps.readdir || ((p) => fs.readdirSync(p));
237281
+ const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
237282
+ const dir = path.join(cwd, ".github", "workflows");
237283
+ const contractGateFiles = [];
237284
+ const deployGateFiles = [];
237285
+ if (!exists(dir)) return { contractGateFiles, deployGateFiles };
237286
+ let names = [];
237287
+ try {
237288
+ names = readdir(dir);
237289
+ } catch {
237290
+ return { contractGateFiles, deployGateFiles };
235815
237291
  }
235816
- const gitDir = findGitDir(options.cwd);
235817
- if (!gitDir) {
235818
- if (options.claude) {
235819
- console.log("Not inside a Git repository \u2014 skipped pre-push hook.");
235820
- return { exitCode: 0, code: "OK" };
237292
+ for (const name of names) {
237293
+ if (!/\.ya?ml$/i.test(name)) continue;
237294
+ const rel = `.github/workflows/${name}`;
237295
+ let body = "";
237296
+ try {
237297
+ body = String(readFile(path.join(dir, name)));
237298
+ } catch {
237299
+ continue;
235821
237300
  }
235822
- console.error("Error: Not inside a Git repository.");
235823
- process.exitCode = 1;
235824
- return { exitCode: 1, code: "NO_GIT" };
235825
- }
235826
- const hooksDir = path.join(gitDir, "hooks");
235827
- if (!fs.existsSync(hooksDir)) {
235828
- fs.mkdirSync(hooksDir, { recursive: true });
235829
- }
235830
- const hookPath = getHookPath(gitDir);
235831
- if (fs.existsSync(hookPath) && !isCodeRiftsHook(hookPath)) {
235832
- console.error("Error: A pre-push hook already exists and was not created by CodeRifts.");
235833
- console.error("Remove it manually or back it up before installing.");
235834
- process.exitCode = 1;
235835
- return;
237301
+ if (body.includes(GATE_ACTION)) contractGateFiles.push(rel);
237302
+ if (/\bcoderifts\s+deploy-gate\b/.test(body)) deployGateFiles.push(rel);
235836
237303
  }
235837
- fs.writeFileSync(hookPath, PRE_PUSH_SCRIPT, { mode: 493 });
235838
- console.log("CodeRifts pre-push hook installed.");
235839
- console.log("");
235840
- console.log("Configure your API key:");
235841
- console.log(" git config coderifts.apiKey <your-key>");
235842
- console.log("");
235843
- console.log("Optionally set spec path (default: api/openapi.yaml):");
235844
- console.log(" git config coderifts.specPath path/to/openapi.yaml");
235845
- console.log("");
235846
- console.log("After upgrading the coderifts CLI, re-run this command so the installed");
235847
- console.log("hook matches the package (already-installed hooks are not auto-updated):");
235848
- console.log(" coderifts hook install");
235849
- console.log("");
235850
- console.log("Agent-using repo? Run: coderifts agent-setup");
237304
+ return { contractGateFiles, deployGateFiles };
235851
237305
  }
235852
- function uninstall() {
235853
- const gitDir = findGitDir();
235854
- if (!gitDir) {
235855
- console.error("Error: Not inside a Git repository.");
235856
- process.exitCode = 1;
235857
- return;
235858
- }
235859
- const hookPath = getHookPath(gitDir);
235860
- if (!fs.existsSync(hookPath)) {
235861
- console.log("No pre-push hook found. Nothing to uninstall.");
235862
- return;
235863
- }
235864
- if (!isCodeRiftsHook(hookPath)) {
235865
- console.error("Error: The existing pre-push hook was not created by CodeRifts. Skipping.");
235866
- process.exitCode = 1;
235867
- return;
235868
- }
235869
- fs.unlinkSync(hookPath);
235870
- console.log("CodeRifts pre-push hook removed.");
237306
+ async function queryGitHubEnforcement({
237307
+ owner,
237308
+ repo,
237309
+ branch,
237310
+ environmentName,
237311
+ client
237312
+ } = {}) {
237313
+ const protectionPath = branch ? `repos/${owner}/${repo}/branches/${encodeURIComponent(branch)}/protection` : null;
237314
+ const envPath = environmentName ? `repos/${owner}/${repo}/environments/${encodeURIComponent(environmentName)}` : null;
237315
+ let resolvedBranch = branch || null;
237316
+ let repoRead = null;
237317
+ if (!resolvedBranch && client) {
237318
+ repoRead = await client.get(`repos/${owner}/${repo}`);
237319
+ if (repoRead && repoRead.status === 200 && repoRead.body && repoRead.body.default_branch) {
237320
+ resolvedBranch = String(repoRead.body.default_branch);
237321
+ }
237322
+ }
237323
+ const protPath = resolvedBranch ? `repos/${owner}/${repo}/branches/${encodeURIComponent(resolvedBranch)}/protection` : protectionPath;
237324
+ const protectionRead = client && protPath ? await client.get(protPath) : null;
237325
+ const environmentRead = client && envPath ? await client.get(envPath) : null;
237326
+ return {
237327
+ branch: resolvedBranch,
237328
+ repoRead,
237329
+ protectionRead,
237330
+ environmentRead
237331
+ };
235871
237332
  }
235872
- function status() {
235873
- const gitDir = findGitDir();
235874
- if (!gitDir) {
235875
- console.error("Error: Not inside a Git repository.");
235876
- process.exitCode = 1;
235877
- return;
235878
- }
235879
- const hookPath = getHookPath(gitDir);
235880
- const installed = isCodeRiftsHook(hookPath);
235881
- console.log(`Hook: ${installed ? "installed" : "not installed"}`);
235882
- if (installed) {
235883
- try {
235884
- const apiKey = execSync("git config coderifts.apiKey", { encoding: "utf-8", cwd: path.dirname(gitDir) }).trim();
235885
- console.log(`API key: ${apiKey ? "configured" : "not configured"}`);
235886
- } catch {
235887
- console.log("API key: not configured");
237333
+ module2.exports = {
237334
+ EVIDENCE_SPEC,
237335
+ APP_INSTALLATION,
237336
+ NO_TOKEN_INSTRUCTION,
237337
+ LAYER_IDS,
237338
+ STATUS,
237339
+ CHECK_NAME,
237340
+ GATE_ACTION,
237341
+ resolveGitHubAuth,
237342
+ redactSecrets,
237343
+ createGitHubHttpClient,
237344
+ evaluateLayers,
237345
+ scanLocalWorkflows,
237346
+ queryGitHubEnforcement,
237347
+ enforceAdminsEnabled,
237348
+ strictEnabled,
237349
+ environmentProtected
237350
+ };
237351
+ }
237352
+ });
237353
+
237354
+ // src/commands/enforce-check.js
237355
+ var require_enforce_check = __commonJS({
237356
+ "src/commands/enforce-check.js"(exports2, module2) {
237357
+ "use strict";
237358
+ var chalk = require_source();
237359
+ var {
237360
+ EVIDENCE_SPEC,
237361
+ APP_INSTALLATION,
237362
+ NO_TOKEN_INSTRUCTION,
237363
+ resolveGitHubAuth,
237364
+ createGitHubHttpClient,
237365
+ evaluateLayers,
237366
+ scanLocalWorkflows,
237367
+ queryGitHubEnforcement
237368
+ } = require_github_enforcement();
237369
+ var { isValidRepo } = require_status();
237370
+ var { resolveOwnerRepo } = require_setup_required_check();
237371
+ if (process.env.NO_COLOR) chalk.level = 0;
237372
+ var USAGE = [
237373
+ "Usage: coderifts enforce --check --repo owner/repo [--env <github-environment>] [--branch <name>] [--json]",
237374
+ "",
237375
+ "Query the provider (GitHub) and local workflow files. Report, per layer:",
237376
+ " VERIFIED / NOT_VERIFIED / UNVERIFIABLE(reason).",
237377
+ "inescapable_deploy is true only when every layer is VERIFIED.",
237378
+ "",
237379
+ "Auth: GITHUB_TOKEN (or GH_TOKEN). Never prints token values.",
237380
+ "The CodeRifts GitHub App installation cannot query branch protection",
237381
+ "(missing administration:read)."
237382
+ ].join("\n");
237383
+ function statusColor(status) {
237384
+ if (status === "VERIFIED") return chalk.green(status);
237385
+ if (status === "NOT_VERIFIED") return chalk.yellow(status);
237386
+ return chalk.red(status);
237387
+ }
237388
+ function renderHuman(report) {
237389
+ const lines = [];
237390
+ lines.push(chalk.bold("CodeRifts enforce --check"));
237391
+ lines.push(` repo: ${report.repo}`);
237392
+ lines.push(` branch: ${report.branch || "(unknown)"}`);
237393
+ if (report.environment) lines.push(` env: ${report.environment}`);
237394
+ lines.push(` queried: ${report.queried_at}`);
237395
+ lines.push(` auth: ${report.auth.source} (value not printed)`);
237396
+ lines.push(chalk.dim(` App installation: cannot query protection (${report.auth.app_installation.missing_scope})`));
237397
+ lines.push("");
237398
+ lines.push(chalk.bold(" Layer Status"));
237399
+ for (const l of report.layers) {
237400
+ const id = String(l.id).padEnd(34, " ");
237401
+ lines.push(` ${id}${statusColor(l.status)}`);
237402
+ if (l.status !== "VERIFIED" && l.reason) {
237403
+ lines.push(chalk.dim(` WHY ${l.reason}`));
235888
237404
  }
235889
- try {
235890
- const specPath = execSync("git config coderifts.specPath", { encoding: "utf-8", cwd: path.dirname(gitDir) }).trim();
235891
- console.log(`Spec path: ${specPath || "api/openapi.yaml (default)"}`);
235892
- } catch {
235893
- console.log("Spec path: api/openapi.yaml (default)");
237405
+ if (l.endpoint) lines.push(chalk.dim(` via ${l.endpoint}`));
237406
+ }
237407
+ lines.push("");
237408
+ const claim = report.claim && report.claim.inescapable_deploy === true;
237409
+ lines.push(` inescapable_deploy: ${claim ? chalk.green("true") : chalk.yellow("false")}`);
237410
+ lines.push(chalk.dim(` ${report.claim.basis}`));
237411
+ lines.push("");
237412
+ lines.push(chalk.dim(" This is queried evidence, not a host claim."));
237413
+ lines.push(chalk.dim(" --enforce / CODERIFTS_DEPLOY_NO_BYPASS are host claims and do not set this field."));
237414
+ if (!claim) {
237415
+ lines.push("");
237416
+ lines.push(chalk.bold(" ACTION"));
237417
+ const missing = report.layers.filter((l) => l.status !== "VERIFIED");
237418
+ for (const l of missing) {
237419
+ if (l.id === "required_check") {
237420
+ lines.push(chalk.dim(' require "CodeRifts / contract-gate" on default-branch protection'));
237421
+ lines.push(chalk.dim(" (coderifts setup-required-check --apply)"));
237422
+ } else if (l.id === "enforce_admins") {
237423
+ lines.push(chalk.dim(" set enforce_admins: true so administrators cannot bypass"));
237424
+ lines.push(chalk.dim(" (coderifts setup-required-check --enforce-admins --apply)"));
237425
+ } else if (l.id === "strict_status_checks") {
237426
+ lines.push(chalk.dim(" set required_status_checks.strict: true"));
237427
+ } else if (l.id === "environment_protection") {
237428
+ lines.push(chalk.dim(" add a GitHub Environment with protection rules, pass --env <name>"));
237429
+ } else if (l.id === "workflow_contract_gate") {
237430
+ lines.push(chalk.dim(" add .github/workflows/coderifts.yml using coderifts/contract-gate@v0"));
237431
+ lines.push(chalk.dim(" (coderifts init --agents)"));
237432
+ } else if (l.id === "workflow_deploy_gate") {
237433
+ lines.push(chalk.dim(" add a CD step: coderifts deploy-gate --env <env> --artifact <id> --receipt <file>"));
237434
+ }
235894
237435
  }
237436
+ }
237437
+ return lines.join("\n");
237438
+ }
237439
+ async function runEnforceCheck(options = {}, deps = {}) {
237440
+ const log = deps.log || console.log.bind(console);
237441
+ const errLog = deps.errLog || console.error.bind(console);
237442
+ const cwd = deps.cwd || options.cwd || process.cwd();
237443
+ const now = deps.now || (() => (/* @__PURE__ */ new Date()).toISOString());
237444
+ const env = deps.env || process.env;
237445
+ const repoArg = options.repo;
237446
+ let ownerRepo = null;
237447
+ if (repoArg && isValidRepo(String(repoArg).trim())) {
237448
+ const [owner, repo] = String(repoArg).trim().split("/");
237449
+ ownerRepo = { owner, repo };
237450
+ } else if (!repoArg) {
235895
237451
  try {
235896
- fs.accessSync(hookPath, fs.constants.X_OK);
235897
- console.log("Executable: yes");
237452
+ ownerRepo = resolveOwnerRepo(cwd, deps.gitImpl);
235898
237453
  } catch {
235899
- console.log("Executable: no (run: chmod +x " + hookPath + ")");
237454
+ ownerRepo = null;
235900
237455
  }
235901
237456
  }
237457
+ if (!ownerRepo) {
237458
+ errLog(chalk.red("Error: missing repo (pass --repo owner/repo)"));
237459
+ errLog(USAGE);
237460
+ return { exitCode: 1, error: "missing_repo" };
237461
+ }
237462
+ const providerName = String(options.provider || "github").toLowerCase();
237463
+ const queriedAt = now();
237464
+ const environmentName = options.env ? String(options.env).trim() : null;
237465
+ const local = (deps.scanLocalWorkflows || scanLocalWorkflows)(cwd, deps.fs || {});
237466
+ const authPublic = {
237467
+ source: "none",
237468
+ app_installation: { ...APP_INSTALLATION }
237469
+ };
237470
+ if (providerName !== "github") {
237471
+ const reason = `no ${providerName} adapter \u2014 GitHub is the only implemented provider; refusing to stub results`;
237472
+ const evaluated2 = evaluateLayers({
237473
+ protectionRead: null,
237474
+ environmentRead: null,
237475
+ environmentName,
237476
+ local,
237477
+ auth: { ok: false, source: "none", reason }
237478
+ });
237479
+ const report2 = {
237480
+ command: "enforce --check",
237481
+ spec: EVIDENCE_SPEC,
237482
+ queried_at: queriedAt,
237483
+ repo: `${ownerRepo.owner}/${ownerRepo.repo}`,
237484
+ branch: options.branch || null,
237485
+ environment: environmentName,
237486
+ provider: providerName,
237487
+ auth: { ...authPublic, reason },
237488
+ ...evaluated2
237489
+ };
237490
+ if (options.json) log(JSON.stringify(report2, null, 2));
237491
+ else log(renderHuman(report2));
237492
+ return { exitCode: 0, report: report2 };
237493
+ }
237494
+ const auth = (deps.resolveAuth || resolveGitHubAuth)(env);
237495
+ authPublic.source = auth.source;
237496
+ if (auth.reason) authPublic.reason = auth.reason;
237497
+ let branch = options.branch ? String(options.branch).trim() : null;
237498
+ let protectionRead = null;
237499
+ let environmentRead = null;
237500
+ if (auth.ok) {
237501
+ const client = deps.githubClient || createGitHubHttpClient({
237502
+ token: auth.token,
237503
+ fetchImpl: deps.fetchImpl
237504
+ });
237505
+ const queried = await queryGitHubEnforcement({
237506
+ owner: ownerRepo.owner,
237507
+ repo: ownerRepo.repo,
237508
+ branch,
237509
+ environmentName,
237510
+ client
237511
+ });
237512
+ branch = queried.branch || branch;
237513
+ protectionRead = queried.protectionRead;
237514
+ environmentRead = queried.environmentRead;
237515
+ }
237516
+ const evaluated = evaluateLayers({
237517
+ protectionRead,
237518
+ environmentRead,
237519
+ environmentName,
237520
+ local,
237521
+ auth
237522
+ });
237523
+ const report = {
237524
+ command: "enforce --check",
237525
+ spec: EVIDENCE_SPEC,
237526
+ queried_at: queriedAt,
237527
+ repo: `${ownerRepo.owner}/${ownerRepo.repo}`,
237528
+ branch,
237529
+ environment: environmentName,
237530
+ provider: "github",
237531
+ auth: authPublic,
237532
+ ...evaluated
237533
+ };
237534
+ if (options.json) log(JSON.stringify(report, null, 2));
237535
+ else log(renderHuman(report));
237536
+ return { exitCode: 0, report };
235902
237537
  }
235903
237538
  module2.exports = {
235904
- install,
235905
- uninstall,
235906
- status,
235907
- PRE_PUSH_SCRIPT,
235908
- HOOK_MARKER,
235909
- findGitDir,
235910
- isCodeRiftsHook,
235911
- installClaudeHook,
235912
- claudeSettingsPath,
235913
- claudeHookCommand,
235914
- CLAUDE_MATCHER
237539
+ runEnforceCheck,
237540
+ renderHuman,
237541
+ USAGE,
237542
+ NO_TOKEN_INSTRUCTION
235915
237543
  };
235916
237544
  }
235917
237545
  });
@@ -235938,14 +237566,19 @@ var require_enforce = __commonJS({
235938
237566
  var USAGE = [
235939
237567
  "Usage: coderifts enforce --repo owner/repo [--apply]",
235940
237568
  " or: coderifts enforce owner/repo [--apply]",
237569
+ " or: coderifts enforce --check --repo owner/repo [--env <name>] [--json]",
235941
237570
  "",
235942
237571
  "Cross-layer orchestrator: reads enforcement-status, then closes gaps by calling existing",
235943
237572
  "setup commands (setup-required-check, hook install, deploy-gate guidance).",
235944
237573
  "",
237574
+ "--check: query GitHub + local workflows and report VERIFIED / NOT_VERIFIED /",
237575
+ "UNVERIFIABLE per layer. inescapable_deploy is true only when every layer is VERIFIED.",
237576
+ "Does not use the CodeRifts API key. Auth: GITHUB_TOKEN. Read-only.",
237577
+ "",
235945
237578
  "DRY-RUN BY DEFAULT \u2014 without --apply, prints what WOULD run and mutates NOTHING.",
235946
237579
  "With --apply, threads apply into each underlying command (their own safety still applies).",
235947
237580
  "",
235948
- "Requires a cloud API key for the status read (coderifts login / CODERIFTS_API_KEY).",
237581
+ "Requires a cloud API key for the (non --check) status read (coderifts login / CODERIFTS_API_KEY).",
235949
237582
  "Merge apply uses your local `gh` credentials, not the CodeRifts API key."
235950
237583
  ].join("\n");
235951
237584
  var AGENT_GUARD_GUIDANCE = [
@@ -235956,8 +237589,10 @@ var require_enforce = __commonJS({
235956
237589
  ].join("\n");
235957
237590
  var DEPLOY_GUIDANCE = [
235958
237591
  "Deploy is declared-only on the server (never server-observed ENFORCING).",
235959
- "Add a CD step that runs: coderifts deploy-gate --env <env> --artifact <id> --receipt <file> --enforce",
235960
- " (fail-closed by default; --enforce attests ENFORCING for inescapable_deploy. CODERIFTS_DEPLOY_ADVISORY=1 to soften.)",
237592
+ "Add a CD step that runs: coderifts deploy-gate --env <env> --artifact <id> --receipt <file>",
237593
+ " (fail-closed by default. --enforce is a host claim, not inescapable_deploy.",
237594
+ " Prove pipeline protection with: coderifts enforce --check --repo owner/repo --env <env>.",
237595
+ " CODERIFTS_DEPLOY_ADVISORY=1 to soften the exit.)",
235961
237596
  "Also set policy.require_source_binding: true in .coderifts.yml for the deploy declaration leg."
235962
237597
  ].join("\n");
235963
237598
  var CONTENT_GUIDANCE = [
@@ -235984,6 +237619,15 @@ var require_enforce = __commonJS({
235984
237619
  }
235985
237620
  }
235986
237621
  async function runEnforce(options = {}, deps = {}) {
237622
+ if (options.check === true) {
237623
+ if (options.apply === true) {
237624
+ const errLog2 = deps.errLog || console.error.bind(console);
237625
+ errLog2(chalk.red("Error: --check is read-only evidence and cannot be combined with --apply"));
237626
+ return { exitCode: 1, error: "check_apply_conflict", outcomes: [] };
237627
+ }
237628
+ const { runEnforceCheck } = require_enforce_check();
237629
+ return runEnforceCheck(options, deps);
237630
+ }
235987
237631
  const apply = options.apply === true;
235988
237632
  const getKey = deps.getApiKey || getApiKey;
235989
237633
  const fetchStatus = deps.cloudGetEnforcementStatus || cloudGetEnforcementStatus;
@@ -236179,216 +237823,6 @@ var require_enforce = __commonJS({
236179
237823
  }
236180
237824
  });
236181
237825
 
236182
- // src/agent-host-files.embedded.js
236183
- var require_agent_host_files_embedded = __commonJS({
236184
- "src/agent-host-files.embedded.js"(exports2, module2) {
236185
- "use strict";
236186
- var AGENT_HOST_PATHS = Object.freeze([
236187
- "AGENTS.md",
236188
- "CLAUDE.md",
236189
- ".cursor/rules/coderifts.mdc",
236190
- ".github/copilot-instructions.md",
236191
- "coderifts-langgraph-policy.js",
236192
- "openai-agent-instructions.md",
236193
- ".claude/settings.json"
236194
- ]);
236195
- var AGENT_HOST_FILES = Object.freeze({
236196
- "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',
236197
- "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',
236198
- ".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',
236199
- ".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',
236200
- "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',
236201
- "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',
236202
- ".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'
236203
- });
236204
- module2.exports = { AGENT_HOST_FILES, AGENT_HOST_PATHS };
236205
- }
236206
- });
236207
-
236208
- // src/commands/agent-setup.js
236209
- var require_agent_setup = __commonJS({
236210
- "src/commands/agent-setup.js"(exports2, module2) {
236211
- "use strict";
236212
- var fs = require("fs");
236213
- var path = require("path");
236214
- var chalk = require_source();
236215
- var { AGENT_HOST_FILES, AGENT_HOST_PATHS } = require_agent_host_files_embedded();
236216
- if (process.env.NO_COLOR) chalk.level = 0;
236217
- var USAGE = `Usage: coderifts agent-setup [--out <dir>] [--check] [--force]
236218
-
236219
- --out <dir> Target directory (default: current working directory)
236220
- --check Exit 0 if on-disk files match embedded content; exit 1 on drift
236221
- --force Overwrite existing files (default: skip collisions)
236222
- Unknown flags exit 1 (never silently ignored).
236223
- `;
236224
- function parseAgentSetupArgs(argv) {
236225
- const args = argv.slice(2);
236226
- let out = null;
236227
- let check = false;
236228
- let force = false;
236229
- let i = 0;
236230
- while (i < args.length && !String(args[i]).startsWith("-")) i += 1;
236231
- while (i < args.length) {
236232
- const a = args[i];
236233
- if (a === "--out") {
236234
- const v = args[i + 1];
236235
- if (v == null || v.startsWith("-")) {
236236
- return { out, check, force, error: `agent-setup: --out requires a path
236237
- ${USAGE}` };
236238
- }
236239
- out = path.resolve(v);
236240
- i += 2;
236241
- continue;
236242
- }
236243
- if (a === "--check") {
236244
- check = true;
236245
- i += 1;
236246
- continue;
236247
- }
236248
- if (a === "--force") {
236249
- force = true;
236250
- i += 1;
236251
- continue;
236252
- }
236253
- if (a.startsWith("-")) {
236254
- return { out, check, force, error: `agent-setup: unrecognized argument: ${a}
236255
- ${USAGE}` };
236256
- }
236257
- return { out, check, force, error: `agent-setup: unrecognized argument: ${a}
236258
- ${USAGE}` };
236259
- }
236260
- return { out, check, force };
236261
- }
236262
- function runAgentSetup(options = {}, deps = {}) {
236263
- const log = deps.log || console.log.bind(console);
236264
- const logErr = deps.logErr || console.error.bind(console);
236265
- const doExit = deps.exit !== false;
236266
- const cwd = deps.cwd || process.cwd();
236267
- const exists = deps.exists || fs.existsSync.bind(fs);
236268
- const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
236269
- const writeFile = deps.writeFile || ((p, c) => {
236270
- fs.mkdirSync(path.dirname(p), { recursive: true });
236271
- fs.writeFileSync(p, c, "utf8");
236272
- });
236273
- let outDir = options.out ? path.resolve(String(options.out)) : cwd;
236274
- let check = !!options.check;
236275
- let force = !!options.force;
236276
- if (deps.argv) {
236277
- const parsed = parseAgentSetupArgs(deps.argv);
236278
- if (parsed.error) {
236279
- logErr(parsed.error.trimEnd());
236280
- if (doExit) process.exit(1);
236281
- return { exitCode: 1, code: "USAGE", message: parsed.error };
236282
- }
236283
- if (parsed.out) outDir = parsed.out;
236284
- check = parsed.check;
236285
- force = parsed.force;
236286
- }
236287
- const files = deps.files || AGENT_HOST_FILES;
236288
- const relPaths = deps.paths || AGENT_HOST_PATHS;
236289
- if (check) {
236290
- let stale = false;
236291
- for (const rel of relPaths) {
236292
- const fp = path.join(outDir, rel);
236293
- const expected = files[rel];
236294
- if (!exists(fp)) {
236295
- logErr(chalk.red(`agent-setup --check: missing ${rel}`));
236296
- stale = true;
236297
- continue;
236298
- }
236299
- const onDisk = readFile(fp);
236300
- if (onDisk !== expected) {
236301
- logErr(chalk.red(`agent-setup --check: drift ${rel}`));
236302
- stale = true;
236303
- }
236304
- }
236305
- if (stale) {
236306
- logErr("Run: coderifts agent-setup --out " + outDir + " --force");
236307
- if (doExit) process.exit(1);
236308
- return { exitCode: 1, code: "DRIFT", outDir };
236309
- }
236310
- log(chalk.green(`agent-setup: up to date (${outDir}, ${relPaths.length} files)`));
236311
- if (doExit) process.exit(0);
236312
- return { exitCode: 0, code: "UP_TO_DATE", outDir };
236313
- }
236314
- const summary = { written: [], skipped: [], forced: [] };
236315
- for (const rel of relPaths) {
236316
- const fp = path.join(outDir, rel);
236317
- const content = files[rel];
236318
- if (exists(fp) && !force) {
236319
- summary.skipped.push(rel);
236320
- continue;
236321
- }
236322
- if (exists(fp) && force) summary.forced.push(rel);
236323
- writeFile(fp, content);
236324
- summary.written.push(rel);
236325
- }
236326
- if (!options.json) {
236327
- log(chalk.bold("CodeRifts agent-setup"));
236328
- log(` target: ${outDir}`);
236329
- for (const rel of summary.written) {
236330
- const tag = summary.forced.includes(rel) ? "overwrote" : "wrote";
236331
- log(chalk.green(` ${tag}: ${rel}`));
236332
- }
236333
- for (const rel of summary.skipped) {
236334
- log(chalk.yellow(` skipped: ${rel} (exists; use --force to overwrite)`));
236335
- }
236336
- log("");
236337
- log(chalk.dim(` ${summary.written.length} written, ${summary.skipped.length} skipped`));
236338
- log("");
236339
- for (const line of AGENT_SETUP_WARN_TELEMETRY_LINES) {
236340
- log(line);
236341
- }
236342
- if (summary.skipped.includes(".claude/settings.json")) {
236343
- for (const line of AGENT_SETUP_HOOK_SKIP_LINES) {
236344
- log(line);
236345
- }
236346
- } else if (summary.written.includes(".claude/settings.json")) {
236347
- for (const line of AGENT_SETUP_HOOK_WIRE_LINES) {
236348
- log(line);
236349
- }
236350
- }
236351
- }
236352
- if (doExit) process.exit(0);
236353
- return { exitCode: 0, code: "OK", outDir, ...summary };
236354
- }
236355
- var AGENT_SETUP_WARN_TELEMETRY_LINES = Object.freeze([
236356
- chalk.bold(" Execution-state recheck (guard@8): DEFAULT ON"),
236357
- chalk.dim(" requireExecutionStateMatch absent/true \u2192 enforce: mismatch \u2192 EXECUTION_STATE_DRIFT"),
236358
- chalk.dim(" (factory does not run); missing fingerprint/artifacts \u2192 EXECUTION_STATE_UNMEASURABLE (STOP)."),
236359
- chalk.dim(" Opt-down: requireExecutionStateMatch: 'warn' (emit, then run unenforced on mismatch)"),
236360
- chalk.dim(" or false (no recheck \u2014 v7 proceed-on-drift). Must be set explicitly."),
236361
- chalk.dim(" Warn emits (then PROCEEDS \u2014 never blocks in warn mode):"),
236362
- chalk.dim(" \xB7 real drift \u2192 execution_state_drift_observed (loud)"),
236363
- chalk.dim(" \xB7 missing artifacts/fingerprint \u2192 execution_state_unmeasurable (quiet)"),
236364
- chalk.dim(" Merge gate block is separate: coderifts setup-required-check --apply")
236365
- ]);
236366
- var AGENT_SETUP_HOOK_WIRE_LINES = Object.freeze([
236367
- chalk.bold(" Claude Code hook (.claude/settings.json): fail-closed PreToolUse"),
236368
- chalk.dim(" matcher Write|Edit|MultiEdit \u2192 coderifts claude-hook (exit 2 = BLOCK)."),
236369
- chalk.dim(" Missing key / API down / unreadable spec \u2192 exit 2 (enforce_indeterminate), not ALLOW."),
236370
- chalk.dim(" Opt-out: CODERIFTS_ADVISORY=1|true restores soft-allow when governance could not run."),
236371
- chalk.dim(" CODERIFTS_STRICT=1 cannot be weakened by ADVISORY.")
236372
- ]);
236373
- var AGENT_SETUP_HOOK_SKIP_LINES = Object.freeze([
236374
- chalk.yellow(" .claude/settings.json exists \u2014 left unchanged (installer does not merge)."),
236375
- chalk.dim(" --force replaces the ENTIRE file (not just the PreToolUse hook)."),
236376
- chalk.dim(" To wire fail-closed: add PreToolUse \u2192 coderifts claude-hook by hand, or --force."),
236377
- chalk.dim(" Opt-out if wired: CODERIFTS_ADVISORY=1|true. CODERIFTS_STRICT cannot weaken.")
236378
- ]);
236379
- module2.exports = {
236380
- runAgentSetup,
236381
- parseAgentSetupArgs,
236382
- AGENT_HOST_FILES,
236383
- AGENT_HOST_PATHS,
236384
- USAGE,
236385
- AGENT_SETUP_WARN_TELEMETRY_LINES,
236386
- AGENT_SETUP_HOOK_WIRE_LINES,
236387
- AGENT_SETUP_HOOK_SKIP_LINES
236388
- };
236389
- }
236390
- });
236391
-
236392
237826
  // src/copilot-mcp.embedded.js
236393
237827
  var require_copilot_mcp_embedded = __commonJS({
236394
237828
  "src/copilot-mcp.embedded.js"(exports2, module2) {
@@ -237329,6 +238763,130 @@ var require_outcome = __commonJS({
237329
238763
  }
237330
238764
  });
237331
238765
 
238766
+ // src/commands/cursor-hook.js
238767
+ var require_cursor_hook = __commonJS({
238768
+ "src/commands/cursor-hook.js"(exports2, module2) {
238769
+ "use strict";
238770
+ var { runClaudeHook } = require_claude_hook();
238771
+ var USAGE = `Usage: coderifts cursor-hook
238772
+
238773
+ Cursor preToolUse adapter. Reads Cursor's hook JSON on stdin, emits
238774
+ { permission, user_message, agent_message } on stdout.
238775
+
238776
+ allow -> the mutation proceeds (non-governed paths are silent)
238777
+ deny -> the mutation is blocked; user_message carries the WHY,
238778
+ agent_message carries the remediation
238779
+
238780
+ REQUIRE_APPROVAL maps to deny: Cursor does not enforce "ask" for preToolUse,
238781
+ so surfacing it as ask would let a human-approval decision through.
238782
+ Unparseable stdin denies (fail-closed).
238783
+ `;
238784
+ var CURSOR_WRITE_TOOLS = Object.freeze(["Write", "Delete"]);
238785
+ var CURSOR_MATCHER = CURSOR_WRITE_TOOLS.join("|");
238786
+ function parseCursorStdin(raw) {
238787
+ if (raw == null || String(raw).trim() === "") return { ok: false, reason: "empty stdin" };
238788
+ let obj;
238789
+ try {
238790
+ obj = JSON.parse(String(raw));
238791
+ } catch (_) {
238792
+ return { ok: false, reason: "stdin is not JSON" };
238793
+ }
238794
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
238795
+ return { ok: false, reason: "stdin JSON is not an object" };
238796
+ }
238797
+ const toolName = typeof obj.tool_name === "string" ? obj.tool_name : null;
238798
+ if (!toolName) return { ok: false, reason: "missing tool_name" };
238799
+ const toolInput = obj.tool_input && typeof obj.tool_input === "object" && !Array.isArray(obj.tool_input) ? obj.tool_input : null;
238800
+ if (!toolInput) return { ok: false, reason: "missing tool_input" };
238801
+ return { ok: true, payload: obj, toolName, toolInput };
238802
+ }
238803
+ function toClaudeShape(toolName, toolInput) {
238804
+ const filePath = toolInput.file_path || toolInput.path || toolInput.target_file || null;
238805
+ const content = toolInput.content !== void 0 ? toolInput.content : toolInput.contents !== void 0 ? toolInput.contents : void 0;
238806
+ const out = { ...toolInput };
238807
+ if (filePath) out.file_path = filePath;
238808
+ if (content !== void 0) out.content = content;
238809
+ if (toolName === "Delete" && out.content === void 0) out.content = "";
238810
+ return { tool_name: toolName === "Delete" ? "Write" : "Write", tool_input: out };
238811
+ }
238812
+ function emit(permission, { userMessage, agentMessage } = {}, deps = {}) {
238813
+ const write = deps.stdout || ((s) => process.stdout.write(s));
238814
+ const body = { permission };
238815
+ if (permission === "deny") {
238816
+ body.user_message = userMessage || "CodeRifts blocked this change.";
238817
+ body.agent_message = agentMessage || "Blocked by CodeRifts. Do not retry the same change.";
238818
+ }
238819
+ write(`${JSON.stringify(body)}
238820
+ `);
238821
+ return permission === "deny" ? 2 : 0;
238822
+ }
238823
+ function denyMessages(whyText, detail = {}) {
238824
+ const decision = detail.decision || null;
238825
+ const severity = detail.severity || null;
238826
+ const head = decision ? `CodeRifts ${decision}: this change is not authorized.` : "CodeRifts blocked this change.";
238827
+ const body = String(whyText || "").trim();
238828
+ const userMessage = body || `${head}${detail.reason ? ` (${detail.reason})` : ""}`;
238829
+ const agentMessage = severity === "REQUIRE_APPROVAL" ? `${head} A human must approve this change. Do not retry the same edit and do not claim approval yourself.` : `${head} Do not retry the same edit. Address the reasons in the block above, then re-run preflight.`;
238830
+ return { userMessage, agentMessage };
238831
+ }
238832
+ async function runCursorHook(o = {}) {
238833
+ const argv = Array.isArray(o.argv) ? o.argv : [];
238834
+ const deps = o.deps || {};
238835
+ if (argv.includes("--help") || argv.includes("-h")) {
238836
+ (deps.stdout || ((s) => process.stdout.write(s)))(USAGE);
238837
+ return 0;
238838
+ }
238839
+ const parsed = parseCursorStdin(o.stdin);
238840
+ if (!parsed.ok) {
238841
+ return emit("deny", {
238842
+ userMessage: `CodeRifts could not read the Cursor hook payload (${parsed.reason}) \u2014 refusing (fail-closed).`,
238843
+ agentMessage: "CodeRifts could not evaluate this action and refused it. Do not retry blindly; check the hook installation."
238844
+ }, deps);
238845
+ }
238846
+ if (!CURSOR_WRITE_TOOLS.includes(parsed.toolName)) {
238847
+ return emit("allow", {}, deps);
238848
+ }
238849
+ const claudeStdin = JSON.stringify(toClaudeShape(parsed.toolName, parsed.toolInput));
238850
+ const whyLines = [];
238851
+ let detail;
238852
+ try {
238853
+ detail = await runClaudeHook(
238854
+ { argv: [], env: o.env || process.env },
238855
+ {
238856
+ ...deps.inner || {},
238857
+ readStdin: () => claudeStdin,
238858
+ errLog: (m) => {
238859
+ whyLines.push(String(m));
238860
+ },
238861
+ env: o.env || process.env
238862
+ }
238863
+ );
238864
+ } catch (err) {
238865
+ return emit("deny", {
238866
+ userMessage: `CodeRifts hook error: ${err && err.message} \u2014 refusing (fail-closed).`,
238867
+ agentMessage: "The CodeRifts hook failed to evaluate this action. It was refused rather than allowed."
238868
+ }, deps);
238869
+ }
238870
+ const d = detail && typeof detail === "object" ? detail : {};
238871
+ const exitCode = Number.isFinite(d.exitCode) ? d.exitCode : 0;
238872
+ if (exitCode === 2) {
238873
+ return emit("deny", denyMessages(whyLines.join("\n"), d), deps);
238874
+ }
238875
+ return emit("allow", {}, deps);
238876
+ }
238877
+ module2.exports = {
238878
+ runCursorHook,
238879
+ parseCursorStdin,
238880
+ toClaudeShape,
238881
+ denyMessages,
238882
+ emit,
238883
+ CURSOR_WRITE_TOOLS,
238884
+ CURSOR_MATCHER,
238885
+ USAGE
238886
+ };
238887
+ }
238888
+ });
238889
+
237332
238890
  // corpus/vectors-mcp-fpfn.json
237333
238891
  var require_vectors_mcp_fpfn = __commonJS({
237334
238892
  "corpus/vectors-mcp-fpfn.json"(exports2, module2) {
@@ -239006,7 +240564,37 @@ program.command("registry-gate [dir]").description("Admit a directory of OpenAPI
239006
240564
  process.exitCode = code;
239007
240565
  process.exit(code);
239008
240566
  });
239009
- program.command("init [template]").description("Generate a .coderifts.yml from a policy template (startup, growth, fintech, public-api, microservices)").action(async (template) => {
240567
+ program.command("init [template]").description("Generate a .coderifts.yml from a policy template, or wire governed agent work (--agents)").option("--agents", "Wire this repo for governed agent work (MCP config, rule files, host hook, CI gate)").option("--hosts <list>", "With --agents: claude, cursor, or all (default: all)", "all").option("--no-hook", "With --agents: skip host hook install").option("--no-workflow", "With --agents: skip writing .github/workflows/coderifts.yml").option("--dry-run", "With --agents: print the plan without writing").option("--check", "With --agents: report which of the four pieces are present (no write)").option("--out <dir>", "With --agents: target directory (default: cwd)").action(async (template, options) => {
240568
+ const {
240569
+ runInitAgents,
240570
+ agentFlagsRequireAgents,
240571
+ AGENT_FLAGS_REQUIRE_AGENTS,
240572
+ AGENTS_NO_TEMPLATE
240573
+ } = require_init_agents();
240574
+ if (options.agents) {
240575
+ if (template) {
240576
+ console.error(AGENTS_NO_TEMPLATE);
240577
+ process.exitCode = 1;
240578
+ return;
240579
+ }
240580
+ const result = runInitAgents({
240581
+ hosts: options.hosts,
240582
+ hook: options.hook,
240583
+ workflow: options.workflow,
240584
+ dryRun: !!options.dryRun,
240585
+ check: !!options.check,
240586
+ out: options.out
240587
+ });
240588
+ if (result && typeof result.exitCode === "number") {
240589
+ process.exitCode = result.exitCode;
240590
+ }
240591
+ return;
240592
+ }
240593
+ if (agentFlagsRequireAgents(options)) {
240594
+ console.error(AGENT_FLAGS_REQUIRE_AGENTS);
240595
+ process.exitCode = 1;
240596
+ return;
240597
+ }
239010
240598
  const { init } = require_init2();
239011
240599
  await init(template);
239012
240600
  });
@@ -239031,12 +240619,13 @@ program.command("status [repo]").description("Show cross-layer enforcement statu
239031
240619
  process.exitCode = result.exitCode;
239032
240620
  }
239033
240621
  });
239034
- program.command("enforce [repo]").description("Close enforcement gaps by chaining setup-required-check / hook / deploy guidance (dry-run default; use --apply to mutate)").option("--repo <owner/repo>", "Repository (owner/repo); also accepted as a positional argument").option("--apply", "Actually run underlying setup commands (default: dry-run only)").option("--json", "Machine-readable JSON result").action(async (repoPositional, options) => {
240622
+ program.command("enforce [repo]").description("Close enforcement gaps (dry-run default), or --check to query provider-native pipeline protection evidence").option("--repo <owner/repo>", "Repository (owner/repo); also accepted as a positional argument").option("--apply", "Actually run underlying setup commands (default: dry-run only)").option("--check", "Query GitHub + local workflows; report VERIFIED / NOT_VERIFIED / UNVERIFIABLE (read-only)").option("--env <name>", "With --check: GitHub Environment name to query for protection rules").option("--branch <name>", "With --check: branch to query (default: repository default branch)").option("--provider <name>", "With --check: provider adapter (github only; others UNVERIFIABLE, never stubbed)", "github").option("--json", "Machine-readable JSON result").action(async (repoPositional, options) => {
239035
240623
  const { runEnforce } = require_enforce();
239036
240624
  const result = await runEnforce({
239037
240625
  ...options,
239038
240626
  repo: options.repo || repoPositional || null,
239039
- apply: !!options.apply
240627
+ apply: !!options.apply,
240628
+ check: !!options.check
239040
240629
  });
239041
240630
  if (result && typeof result.exitCode === "number") {
239042
240631
  process.exitCode = result.exitCode;
@@ -239103,11 +240692,31 @@ ${USAGE}
239103
240692
  process.exitCode = result.exitCode;
239104
240693
  }
239105
240694
  });
240695
+ program.command("cursor-hook").description(
240696
+ 'Cursor preToolUse gate: deny contract-touching Write|Delete on BLOCK/STOP/REQUEST_APPROVAL. Emits {permission,user_message,agent_message} on stdout. Cursor does not enforce "ask" for preToolUse, so REQUEST_APPROVAL maps to deny. Fail-closed on unparseable stdin.'
240697
+ ).addHelpText("after", () => {
240698
+ const { USAGE } = require_cursor_hook();
240699
+ return `
240700
+ ${USAGE}
240701
+ `;
240702
+ }).action(async () => {
240703
+ const { runCursorHook } = require_cursor_hook();
240704
+ const fsMod = require("fs");
240705
+ let stdin = "";
240706
+ try {
240707
+ stdin = fsMod.readFileSync(0, "utf8");
240708
+ } catch {
240709
+ stdin = "";
240710
+ }
240711
+ const code = await runCursorHook({ argv: process.argv.slice(3), stdin });
240712
+ if (typeof code === "number") process.exitCode = code;
240713
+ });
239106
240714
  var hookCmd = program.command("hook").description("Manage the CodeRifts pre-push Git hook");
239107
- 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) => {
240715
+ 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("--cursor", "Write Cursor preToolUse hook to .cursor/hooks.json (JSON-merge, failClosed:true)").option("--global", "With --claude/--cursor: write the ~/ config instead of the project file").option("--log <path>", "With --claude/--cursor: embed CODERIFTS_GUARD_EVENT_LOG and CODERIFTS_SESSION_ID in the command").action((options) => {
239108
240716
  const { install } = require_hook();
239109
240717
  install({
239110
240718
  claude: !!options.claude,
240719
+ cursor: !!options.cursor,
239111
240720
  global: !!options.global,
239112
240721
  log: options.log
239113
240722
  });