coderifts 4.4.1 → 4.6.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.6.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",
@@ -208972,6 +208972,1555 @@ var require_registry_gate = __commonJS({
208972
208972
  }
208973
208973
  });
208974
208974
 
208975
+ // src/agent-host-files.embedded.js
208976
+ var require_agent_host_files_embedded = __commonJS({
208977
+ "src/agent-host-files.embedded.js"(exports2, module2) {
208978
+ "use strict";
208979
+ var AGENT_HOST_PATHS = Object.freeze([
208980
+ "AGENTS.md",
208981
+ "CLAUDE.md",
208982
+ ".cursor/rules/coderifts.mdc",
208983
+ ".github/copilot-instructions.md",
208984
+ "coderifts-langgraph-policy.js",
208985
+ "openai-agent-instructions.md",
208986
+ ".claude/settings.json",
208987
+ ".cursor/hooks.json"
208988
+ ]);
208989
+ var AGENT_HOST_FILES = Object.freeze({
208990
+ "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',
208991
+ "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',
208992
+ ".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',
208993
+ ".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',
208994
+ "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',
208995
+ "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',
208996
+ ".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',
208997
+ ".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'
208998
+ });
208999
+ module2.exports = { AGENT_HOST_FILES, AGENT_HOST_PATHS };
209000
+ }
209001
+ });
209002
+
209003
+ // src/commands/agent-setup.js
209004
+ var require_agent_setup = __commonJS({
209005
+ "src/commands/agent-setup.js"(exports2, module2) {
209006
+ "use strict";
209007
+ var fs = require("fs");
209008
+ var path = require("path");
209009
+ var chalk = require_source();
209010
+ var { AGENT_HOST_FILES, AGENT_HOST_PATHS } = require_agent_host_files_embedded();
209011
+ if (process.env.NO_COLOR) chalk.level = 0;
209012
+ var USAGE = `Usage: coderifts agent-setup [--out <dir>] [--check] [--force]
209013
+
209014
+ --out <dir> Target directory (default: current working directory)
209015
+ --check Exit 0 if on-disk files match embedded content; exit 1 on drift
209016
+ --force Overwrite existing files (default: skip collisions)
209017
+ Unknown flags exit 1 (never silently ignored).
209018
+ `;
209019
+ function parseAgentSetupArgs(argv) {
209020
+ const args = argv.slice(2);
209021
+ let out = null;
209022
+ let check = false;
209023
+ let force = false;
209024
+ let i = 0;
209025
+ while (i < args.length && !String(args[i]).startsWith("-")) i += 1;
209026
+ while (i < args.length) {
209027
+ const a = args[i];
209028
+ if (a === "--out") {
209029
+ const v = args[i + 1];
209030
+ if (v == null || v.startsWith("-")) {
209031
+ return { out, check, force, error: `agent-setup: --out requires a path
209032
+ ${USAGE}` };
209033
+ }
209034
+ out = path.resolve(v);
209035
+ i += 2;
209036
+ continue;
209037
+ }
209038
+ if (a === "--check") {
209039
+ check = true;
209040
+ i += 1;
209041
+ continue;
209042
+ }
209043
+ if (a === "--force") {
209044
+ force = true;
209045
+ i += 1;
209046
+ continue;
209047
+ }
209048
+ if (a.startsWith("-")) {
209049
+ return { out, check, force, error: `agent-setup: unrecognized argument: ${a}
209050
+ ${USAGE}` };
209051
+ }
209052
+ return { out, check, force, error: `agent-setup: unrecognized argument: ${a}
209053
+ ${USAGE}` };
209054
+ }
209055
+ return { out, check, force };
209056
+ }
209057
+ function runAgentSetup(options = {}, deps = {}) {
209058
+ const log = deps.log || console.log.bind(console);
209059
+ const logErr = deps.logErr || console.error.bind(console);
209060
+ const doExit = deps.exit !== false;
209061
+ const cwd = deps.cwd || process.cwd();
209062
+ const exists = deps.exists || fs.existsSync.bind(fs);
209063
+ const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
209064
+ const writeFile = deps.writeFile || ((p, c) => {
209065
+ fs.mkdirSync(path.dirname(p), { recursive: true });
209066
+ fs.writeFileSync(p, c, "utf8");
209067
+ });
209068
+ let outDir = options.out ? path.resolve(String(options.out)) : cwd;
209069
+ let check = !!options.check;
209070
+ let force = !!options.force;
209071
+ if (deps.argv) {
209072
+ const parsed = parseAgentSetupArgs(deps.argv);
209073
+ if (parsed.error) {
209074
+ logErr(parsed.error.trimEnd());
209075
+ if (doExit) process.exit(1);
209076
+ return { exitCode: 1, code: "USAGE", message: parsed.error };
209077
+ }
209078
+ if (parsed.out) outDir = parsed.out;
209079
+ check = parsed.check;
209080
+ force = parsed.force;
209081
+ }
209082
+ const files = deps.files || AGENT_HOST_FILES;
209083
+ const relPaths = deps.paths || AGENT_HOST_PATHS;
209084
+ if (check) {
209085
+ let stale = false;
209086
+ for (const rel of relPaths) {
209087
+ const fp = path.join(outDir, rel);
209088
+ const expected = files[rel];
209089
+ if (!exists(fp)) {
209090
+ logErr(chalk.red(`agent-setup --check: missing ${rel}`));
209091
+ stale = true;
209092
+ continue;
209093
+ }
209094
+ const onDisk = readFile(fp);
209095
+ if (onDisk !== expected) {
209096
+ logErr(chalk.red(`agent-setup --check: drift ${rel}`));
209097
+ stale = true;
209098
+ }
209099
+ }
209100
+ if (stale) {
209101
+ logErr("Run: coderifts agent-setup --out " + outDir + " --force");
209102
+ if (doExit) process.exit(1);
209103
+ return { exitCode: 1, code: "DRIFT", outDir };
209104
+ }
209105
+ log(chalk.green(`agent-setup: up to date (${outDir}, ${relPaths.length} files)`));
209106
+ if (doExit) process.exit(0);
209107
+ return { exitCode: 0, code: "UP_TO_DATE", outDir };
209108
+ }
209109
+ const summary = writeAgentHostFiles({
209110
+ outDir,
209111
+ force,
209112
+ files,
209113
+ paths: relPaths,
209114
+ exists,
209115
+ writeFile,
209116
+ dryRun: !!options.dryRun
209117
+ });
209118
+ if (!options.json) {
209119
+ log(chalk.bold("CodeRifts agent-setup"));
209120
+ log(` target: ${outDir}`);
209121
+ for (const rel of summary.written) {
209122
+ const tag = summary.forced.includes(rel) ? "overwrote" : "wrote";
209123
+ log(chalk.green(` ${tag}: ${rel}`));
209124
+ }
209125
+ for (const rel of summary.skipped) {
209126
+ log(chalk.yellow(` skipped: ${rel} (exists; use --force to overwrite)`));
209127
+ }
209128
+ log("");
209129
+ log(chalk.dim(` ${summary.written.length} written, ${summary.skipped.length} skipped`));
209130
+ log("");
209131
+ for (const line of AGENT_SETUP_WARN_TELEMETRY_LINES) {
209132
+ log(line);
209133
+ }
209134
+ if (summary.skipped.includes(".claude/settings.json")) {
209135
+ for (const line of AGENT_SETUP_HOOK_SKIP_LINES) {
209136
+ log(line);
209137
+ }
209138
+ } else if (summary.written.includes(".claude/settings.json")) {
209139
+ for (const line of AGENT_SETUP_HOOK_WIRE_LINES) {
209140
+ log(line);
209141
+ }
209142
+ }
209143
+ }
209144
+ if (doExit) process.exit(0);
209145
+ return { exitCode: 0, code: "OK", outDir, ...summary };
209146
+ }
209147
+ var AGENT_SETUP_WARN_TELEMETRY_LINES = Object.freeze([
209148
+ chalk.bold(" Execution-state recheck (guard@8): DEFAULT ON"),
209149
+ chalk.dim(" requireExecutionStateMatch absent/true \u2192 enforce: mismatch \u2192 EXECUTION_STATE_DRIFT"),
209150
+ chalk.dim(" (factory does not run); missing fingerprint/artifacts \u2192 EXECUTION_STATE_UNMEASURABLE (STOP)."),
209151
+ chalk.dim(" Opt-down: requireExecutionStateMatch: 'warn' (emit, then run unenforced on mismatch)"),
209152
+ chalk.dim(" or false (no recheck \u2014 v7 proceed-on-drift). Must be set explicitly."),
209153
+ chalk.dim(" Warn emits (then PROCEEDS \u2014 never blocks in warn mode):"),
209154
+ chalk.dim(" \xB7 real drift \u2192 execution_state_drift_observed (loud)"),
209155
+ chalk.dim(" \xB7 missing artifacts/fingerprint \u2192 execution_state_unmeasurable (quiet)"),
209156
+ chalk.dim(" Merge gate block is separate: coderifts setup-required-check --apply")
209157
+ ]);
209158
+ var AGENT_SETUP_HOOK_WIRE_LINES = Object.freeze([
209159
+ chalk.bold(" Claude Code hook (.claude/settings.json): fail-closed PreToolUse"),
209160
+ chalk.dim(" matcher Write|Edit|MultiEdit \u2192 coderifts claude-hook (exit 2 = BLOCK)."),
209161
+ chalk.dim(" Missing key / API down / unreadable spec \u2192 exit 2 (enforce_indeterminate), not ALLOW."),
209162
+ chalk.dim(" Opt-out: CODERIFTS_ADVISORY=1|true restores soft-allow when governance could not run."),
209163
+ chalk.dim(" CODERIFTS_STRICT=1 cannot be weakened by ADVISORY.")
209164
+ ]);
209165
+ var AGENT_SETUP_HOOK_SKIP_LINES = Object.freeze([
209166
+ chalk.yellow(" .claude/settings.json exists \u2014 left unchanged (installer does not merge)."),
209167
+ chalk.dim(" --force replaces the ENTIRE file (not just the PreToolUse hook)."),
209168
+ chalk.dim(" To wire fail-closed: add PreToolUse \u2192 coderifts claude-hook by hand, or --force."),
209169
+ chalk.dim(" Opt-out if wired: CODERIFTS_ADVISORY=1|true. CODERIFTS_STRICT cannot weaken.")
209170
+ ]);
209171
+ function writeAgentHostFiles(opts = {}) {
209172
+ const outDir = opts.outDir;
209173
+ const force = !!opts.force;
209174
+ const files = opts.files || AGENT_HOST_FILES;
209175
+ const relPaths = opts.paths || AGENT_HOST_PATHS;
209176
+ const exists = opts.exists || fs.existsSync.bind(fs);
209177
+ const writeFile = opts.writeFile || ((p, c) => {
209178
+ fs.mkdirSync(path.dirname(p), { recursive: true });
209179
+ fs.writeFileSync(p, c, "utf8");
209180
+ });
209181
+ const dryRun = !!opts.dryRun;
209182
+ const summary = { written: [], skipped: [], forced: [] };
209183
+ for (const rel of relPaths) {
209184
+ const fp = path.join(outDir, rel);
209185
+ if (exists(fp) && !force) {
209186
+ summary.skipped.push(rel);
209187
+ continue;
209188
+ }
209189
+ if (exists(fp) && force) summary.forced.push(rel);
209190
+ if (!dryRun) writeFile(fp, files[rel]);
209191
+ summary.written.push(rel);
209192
+ }
209193
+ return summary;
209194
+ }
209195
+ module2.exports = {
209196
+ runAgentSetup,
209197
+ parseAgentSetupArgs,
209198
+ writeAgentHostFiles,
209199
+ AGENT_HOST_FILES,
209200
+ AGENT_HOST_PATHS,
209201
+ USAGE,
209202
+ AGENT_SETUP_WARN_TELEMETRY_LINES,
209203
+ AGENT_SETUP_HOOK_WIRE_LINES,
209204
+ AGENT_SETUP_HOOK_SKIP_LINES
209205
+ };
209206
+ }
209207
+ });
209208
+
209209
+ // src/commands/hook.js
209210
+ var require_hook = __commonJS({
209211
+ "src/commands/hook.js"(exports2, module2) {
209212
+ "use strict";
209213
+ var fs = require("fs");
209214
+ var path = require("path");
209215
+ var os = require("os");
209216
+ var { execSync } = require("child_process");
209217
+ var HOOK_MARKER = "# CodeRifts pre-push hook";
209218
+ var PRE_PUSH_SCRIPT = `#!/bin/sh
209219
+ ${HOOK_MARKER}
209220
+ # Checks API spec changes before pushing.
209221
+ # The pre-push hook receives lines on stdin:
209222
+ # <local ref> <local sha> <remote ref> <remote sha>
209223
+ #
209224
+ # Re-install after CLI upgrades: coderifts hook install
209225
+ #
209226
+ # Exit map (fail-closed when configured):
209227
+ # CODERIFTS_SKIP=1 \u2192 exit 0 (explicit loud override)
209228
+ # no apiKey \u2192 exit 0 soft (not configured)
209229
+ # BLOCK / STOP / UNKNOWN \u2192 exit 1
209230
+ # INDETERMINATE (no decision obtainable) \u2192 exit 1
209231
+ # ALLOW / analysis clean \u2192 exit 0
209232
+ # REQUIRE_APPROVAL / WARN \u2192 warn, exit 0
209233
+
209234
+ # Explicit escape hatch \u2014 never the default on error; human must set this.
209235
+ if [ "\${CODERIFTS_SKIP:-}" = "1" ]; then
209236
+ echo "CodeRifts: CODERIFTS_SKIP=1 set \u2014 push allowed (explicit override)." >&2
209237
+ exit 0
209238
+ fi
209239
+
209240
+ CODERIFTS_API_KEY=$(git config coderifts.apiKey)
209241
+ SPEC_PATH=$(git config coderifts.specPath || echo "api/openapi.yaml")
209242
+ ZERO="0000000000000000000000000000000000000000"
209243
+
209244
+ if [ -z "$CODERIFTS_API_KEY" ]; then
209245
+ echo "CodeRifts: No API key configured. Run: git config coderifts.apiKey <your-key>"
209246
+ exit 0 # Don't block if not configured
209247
+ fi
209248
+
209249
+ # --- helpers: three-state git blob read (present | absent | error) ---
209250
+ # Sets: BLOB_KIND, BLOB_CONTENT, BLOB_ERR. Never maps unreadable \u2192 empty string.
209251
+ git_blob_at() {
209252
+ _ref="$1"
209253
+ _path="$2"
209254
+ BLOB_CONTENT=""
209255
+ BLOB_ERR=""
209256
+ BLOB_KIND=""
209257
+ if ! git rev-parse --verify "$_ref^{commit}" >/dev/null 2>&1; then
209258
+ BLOB_KIND=error
209259
+ BLOB_ERR="GIT_ERROR: cannot resolve ref $_ref"
209260
+ return 1
209261
+ fi
209262
+ _errf=$(mktemp 2>/dev/null || echo "/tmp/coderifts-hook-err.$$")
209263
+ if BLOB_CONTENT=$(git show "$_ref:$_path" 2>"$_errf"); then
209264
+ BLOB_KIND=present
209265
+ rm -f "$_errf"
209266
+ return 0
209267
+ fi
209268
+ _err=$(cat "$_errf" 2>/dev/null)
209269
+ rm -f "$_errf"
209270
+ case "$_err" in
209271
+ *"does not exist in"*|*"exists on disk, but not in"*)
209272
+ BLOB_KIND=absent
209273
+ BLOB_CONTENT=""
209274
+ return 0
209275
+ ;;
209276
+ esac
209277
+ BLOB_KIND=error
209278
+ BLOB_ERR="GIT_ERROR: git show $_ref:$_path failed"
209279
+ return 1
209280
+ }
209281
+
209282
+ # New-branch baseline: merge-base with default branch (not empty before).
209283
+ # Prints merge-base SHA on success; nonzero if none can be resolved.
209284
+ resolve_new_branch_base_ref() {
209285
+ _local="$1"
209286
+ for _cand in origin/HEAD origin/main origin/master main master; do
209287
+ _mb=$(git merge-base "$_local" "$_cand" 2>/dev/null) || continue
209288
+ if [ -n "$_mb" ] && [ "$_mb" != "$ZERO" ]; then
209289
+ echo "$_mb"
209290
+ return 0
209291
+ fi
209292
+ done
209293
+ return 1
209294
+ }
209295
+
209296
+ # Read stdin lines provided by git pre-push
209297
+ while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
209298
+ # Skip delete pushes
209299
+ if [ "$LOCAL_SHA" = "$ZERO" ]; then
209300
+ continue
209301
+ fi
209302
+
209303
+ # after = local (about-to-be-pushed) commit \u2014 three-state
209304
+ if ! git_blob_at "$LOCAL_SHA" "$SPEC_PATH"; then
209305
+ echo "CodeRifts: $BLOB_ERR (after=$SPEC_PATH at $LOCAL_SHA). Fail-closed."
209306
+ exit 1
209307
+ fi
209308
+ if [ "$BLOB_KIND" = "absent" ]; then
209309
+ continue # Spec doesn't exist in local commit, skip
209310
+ fi
209311
+ HEAD_SPEC=$BLOB_CONTENT
209312
+
209313
+ # before = remote tip, or for new branch (zero remote SHA) a merge-base baseline
209314
+ if [ "$REMOTE_SHA" = "$ZERO" ]; then
209315
+ BASE_REF=$(resolve_new_branch_base_ref "$LOCAL_SHA") || {
209316
+ echo "CodeRifts: GIT_ERROR \u2014 cannot resolve merge-base baseline for new branch (tried origin/HEAD, origin/main, origin/master, main, master). Fail-closed."
209317
+ exit 1
209318
+ }
209319
+ else
209320
+ BASE_REF=$REMOTE_SHA
209321
+ fi
209322
+
209323
+ if ! git_blob_at "$BASE_REF" "$SPEC_PATH"; then
209324
+ echo "CodeRifts: $BLOB_ERR (before=$SPEC_PATH at $BASE_REF). Fail-closed \u2014 not treating as new spec."
209325
+ exit 1
209326
+ fi
209327
+ if [ "$BLOB_KIND" = "absent" ]; then
209328
+ # Honest NEW_ARTIFACT: path genuinely not at baseline
209329
+ echo "CodeRifts: New artifact (spec absent at baseline $BASE_REF:$SPEC_PATH); allowing push."
209330
+ continue
209331
+ fi
209332
+ BASE_SPEC=$BLOB_CONTENT
209333
+
209334
+ # If specs are identical, nothing to check
209335
+ if [ "$BASE_SPEC" = "$HEAD_SPEC" ]; then
209336
+ continue
209337
+ fi
209338
+
209339
+ echo "CodeRifts: Checking API spec changes (baseline $BASE_REF)..."
209340
+
209341
+ # Capture body + HTTP status separately (fail-closed on non-2xx when configured).
209342
+ _CR_BODY=$(mktemp 2>/dev/null || echo "/tmp/coderifts-hook-body.$$")
209343
+ _CR_HTTP=$(curl -sS -o "$_CR_BODY" -w "%{http_code}" -X POST https://app.coderifts.com/api/v1/diff \\
209344
+ -H "Authorization: Bearer $CODERIFTS_API_KEY" \\
209345
+ -H "Content-Type: application/json" \\
209346
+ -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"
209347
+ RESULT=$(cat "$_CR_BODY" 2>/dev/null)
209348
+ rm -f "$_CR_BODY"
209349
+
209350
+ # (a) HTTP non-2xx (incl. transport fail \u2192 000) \u2192 configured-but-indeterminate
209351
+ case "$_CR_HTTP" in
209352
+ 2??) ;;
209353
+ *)
209354
+ echo "CodeRifts could not obtain a decision (HTTP \${_CR_HTTP:-000}); push blocked \u2014 set CODERIFTS_SKIP=1 to override intentionally" >&2
209355
+ exit 1
209356
+ ;;
209357
+ esac
209358
+
209359
+ # Prefer closed-set execution_action; omega_decision only when action is absent.
209360
+ # Indeterminate classes \u2192 INDETERMINATE (fail closed), never default ALLOW.
209361
+ # Severity: BLOCK/STOP \u2192 exit 1; REQUIRE_APPROVAL/WARN \u2192 warn; unknown action \u2192 halt;
209362
+ # INDETERMINATE / empty \u2192 exit 1 (configured-but-indeterminate).
209363
+ DECISION=$(echo "$RESULT" | python3 -c '
209364
+ import sys, json
209365
+ raw = sys.stdin.read()
209366
+ if raw is None or not str(raw).strip():
209367
+ print("INDETERMINATE")
209368
+ raise SystemExit(0)
209369
+ try:
209370
+ d = json.loads(raw)
209371
+ except Exception:
209372
+ # (b) 2xx but unparseable JSON
209373
+ print("INDETERMINATE")
209374
+ raise SystemExit(0)
209375
+ if not isinstance(d, dict):
209376
+ print("INDETERMINATE")
209377
+ raise SystemExit(0)
209378
+ # API error body without a decision (e.g. {"error":"internal_error"} / server_error)
209379
+ if d.get("error") and not (
209380
+ d.get("omega_decision") or d.get("decision")
209381
+ or d.get("execution_action")
209382
+ or (isinstance(d.get("decision_result"), dict) and d["decision_result"].get("execution_action"))
209383
+ ):
209384
+ print("INDETERMINATE")
209385
+ raise SystemExit(0)
209386
+ ea = None
209387
+ dr = d.get("decision_result")
209388
+ if isinstance(dr, dict) and isinstance(dr.get("execution_action"), str):
209389
+ ea = dr["execution_action"]
209390
+ elif isinstance(d.get("execution_action"), str):
209391
+ ea = d["execution_action"]
209392
+ closed = {"CONTINUE", "CONTINUE_WITH_MONITORING", "REQUEST_APPROVAL", "STOP"}
209393
+ if ea is not None and ea != "":
209394
+ if ea not in closed:
209395
+ print("UNKNOWN")
209396
+ raise SystemExit(0)
209397
+ if ea in ("CONTINUE", "CONTINUE_WITH_MONITORING"):
209398
+ print("ALLOW")
209399
+ elif ea == "STOP":
209400
+ print("BLOCK")
209401
+ else:
209402
+ print("REQUIRE_APPROVAL")
209403
+ raise SystemExit(0)
209404
+ # (c) parsed but no execution_action AND no decision \u2014 do NOT default ALLOW
209405
+ od = d.get("omega_decision") or d.get("decision")
209406
+ if od is None or od == "":
209407
+ print("INDETERMINATE")
209408
+ raise SystemExit(0)
209409
+ print(od)
209410
+ ' 2>/dev/null)
209411
+ # (d) empty DECISION (python missing / crash with empty stdout) \u2192 fail closed
209412
+ if [ -z "$DECISION" ]; then
209413
+ DECISION="INDETERMINATE"
209414
+ fi
209415
+ OMEGA=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('omega_api',0))" 2>/dev/null)
209416
+ BREAKING=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('breaking_changes',[])))" 2>/dev/null)
209417
+
209418
+ if [ "$DECISION" = "INDETERMINATE" ]; then
209419
+ echo "CodeRifts could not obtain a decision (indeterminate response); push blocked \u2014 set CODERIFTS_SKIP=1 to override intentionally" >&2
209420
+ exit 1
209421
+ fi
209422
+
209423
+ if [ "$DECISION" = "UNKNOWN" ]; then
209424
+ echo ""
209425
+ echo "========================================"
209426
+ echo " CodeRifts: PUSH BLOCKED"
209427
+ echo "========================================"
209428
+ echo " Reason: unrecognised execution_action is not permission"
209429
+ echo " Fail-closed (unknown present action)."
209430
+ echo "========================================"
209431
+ echo ""
209432
+ echo "Set CODERIFTS_SKIP=1 to override intentionally (or git push --no-verify)."
209433
+ exit 1
209434
+ fi
209435
+
209436
+ if [ "$DECISION" = "BLOCK" ]; then
209437
+ echo ""
209438
+ echo "========================================"
209439
+ echo " CodeRifts: PUSH BLOCKED"
209440
+ echo "========================================"
209441
+ echo " Risk score: $OMEGA/100"
209442
+ echo " Breaking changes: $BREAKING"
209443
+ echo " Decision: BLOCK"
209444
+ echo "========================================"
209445
+ echo ""
209446
+ echo "Fix breaking changes, set CODERIFTS_SKIP=1 to override intentionally, or use --no-verify."
209447
+ exit 1
209448
+ fi
209449
+
209450
+ if [ "$DECISION" = "REQUIRE_APPROVAL" ] || [ "$DECISION" = "WARN" ]; then
209451
+ echo "CodeRifts: WARNING \u2014 $DECISION \u2014 Risk: $OMEGA/100, Breaking: $BREAKING"
209452
+ fi
209453
+ done
209454
+
209455
+ exit 0
209456
+ `;
209457
+ function findGitDir(startDir) {
209458
+ let dir = startDir || process.cwd();
209459
+ while (dir !== path.dirname(dir)) {
209460
+ const gitDir = path.join(dir, ".git");
209461
+ if (fs.existsSync(gitDir) && fs.statSync(gitDir).isDirectory()) {
209462
+ return gitDir;
209463
+ }
209464
+ dir = path.dirname(dir);
209465
+ }
209466
+ return null;
209467
+ }
209468
+ function getHookPath(gitDir) {
209469
+ return path.join(gitDir, "hooks", "pre-push");
209470
+ }
209471
+ function isCodeRiftsHook(hookPath) {
209472
+ if (!fs.existsSync(hookPath)) return false;
209473
+ const content = fs.readFileSync(hookPath, "utf-8");
209474
+ return content.includes(HOOK_MARKER);
209475
+ }
209476
+ var CLAUDE_MATCHER = "Write|Edit|MultiEdit";
209477
+ var CLAUDE_HOOK_BIN = "coderifts claude-hook";
209478
+ function claudeSettingsPath({ global: isGlobal, cwd } = {}) {
209479
+ if (isGlobal) return path.join(os.homedir(), ".claude", "settings.json");
209480
+ return path.join(cwd || process.cwd(), ".claude", "settings.json");
209481
+ }
209482
+ function claudeHookCommand(logPath) {
209483
+ if (!logPath) return CLAUDE_HOOK_BIN;
209484
+ const abs = path.resolve(String(logPath));
209485
+ return `CODERIFTS_GUARD_EVENT_LOG=${abs} CODERIFTS_SESSION_ID=coderifts-claude-hook ${CLAUDE_HOOK_BIN}`;
209486
+ }
209487
+ function isOurPreToolUseEntry(entry, command) {
209488
+ if (!entry || typeof entry !== "object") return false;
209489
+ if (entry.matcher !== CLAUDE_MATCHER) return false;
209490
+ const hooks = Array.isArray(entry.hooks) ? entry.hooks : [];
209491
+ return hooks.some((h) => h && h.type === "command" && String(h.command || "") === command);
209492
+ }
209493
+ function installClaudeHook(opts = {}) {
209494
+ const cwd = opts.cwd || process.cwd();
209495
+ const home = opts.homeDir || os.homedir();
209496
+ const dest = opts.global ? path.join(home, ".claude", "settings.json") : path.join(cwd, ".claude", "settings.json");
209497
+ const command = claudeHookCommand(opts.logPath);
209498
+ const say = (...a) => {
209499
+ if (!opts.silent) console.log(...a);
209500
+ };
209501
+ const block = {
209502
+ matcher: CLAUDE_MATCHER,
209503
+ hooks: [{ type: "command", command, timeout: 60 }]
209504
+ };
209505
+ let existing = {};
209506
+ let hadFile = false;
209507
+ if (fs.existsSync(dest)) {
209508
+ hadFile = true;
209509
+ let raw;
209510
+ try {
209511
+ raw = fs.readFileSync(dest, "utf8");
209512
+ } catch (err) {
209513
+ const message = `Error: cannot read ${dest} (${err && err.message}) \u2014 refusing to write.`;
209514
+ console.error(message);
209515
+ process.exitCode = 1;
209516
+ return { exitCode: 1, code: "READ_FAILED", path: dest, message };
209517
+ }
209518
+ if (String(raw).trim() !== "") {
209519
+ try {
209520
+ existing = JSON.parse(raw);
209521
+ } catch (err) {
209522
+ const message = `Error: ${dest} is not valid JSON \u2014 refusing (no write). Fix or remove the file, then re-run.`;
209523
+ console.error(message);
209524
+ process.exitCode = 1;
209525
+ return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
209526
+ }
209527
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
209528
+ const message = `Error: ${dest} JSON root is not an object \u2014 refusing (no write).`;
209529
+ console.error(message);
209530
+ process.exitCode = 1;
209531
+ return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
209532
+ }
209533
+ }
209534
+ }
209535
+ const hooksRoot = existing.hooks && typeof existing.hooks === "object" && !Array.isArray(existing.hooks) ? { ...existing.hooks } : {};
209536
+ const pre = Array.isArray(hooksRoot.PreToolUse) ? hooksRoot.PreToolUse.slice() : [];
209537
+ if (pre.some((e) => isOurPreToolUseEntry(e, command))) {
209538
+ say(`Claude Code hook already installed at ${dest} (no-op).`);
209539
+ return { exitCode: 0, code: "NOOP", path: dest, wrote: false };
209540
+ }
209541
+ if (opts.dryRun) {
209542
+ return { exitCode: 0, code: "WOULD_WRITE", path: dest, wrote: false, wouldWrite: true, hadFile };
209543
+ }
209544
+ const nextPre = pre.concat([block]);
209545
+ const next = {
209546
+ ...existing,
209547
+ hooks: { ...hooksRoot, PreToolUse: nextPre }
209548
+ };
209549
+ const serialized = `${JSON.stringify(next, null, 2)}
209550
+ `;
209551
+ let backedUp;
209552
+ if (hadFile) {
209553
+ backedUp = `${dest}.bak`;
209554
+ fs.copyFileSync(dest, backedUp);
209555
+ }
209556
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
209557
+ fs.writeFileSync(dest, serialized, "utf8");
209558
+ say(`Claude Code PreToolUse hook written to ${dest}`);
209559
+ if (backedUp) say(`Backup: ${backedUp}`);
209560
+ say("matcher Write|Edit|MultiEdit \u2192 coderifts claude-hook (exit 2 = BLOCK).");
209561
+ return { exitCode: 0, code: "OK", path: dest, backedUp, wrote: true };
209562
+ }
209563
+ var CURSOR_HOOK_BIN = "coderifts cursor-hook";
209564
+ var CURSOR_MATCHER = "Write|Delete";
209565
+ function cursorHooksPath({ global: isGlobal, cwd } = {}) {
209566
+ if (isGlobal) return path.join(os.homedir(), ".cursor", "hooks.json");
209567
+ return path.join(cwd || process.cwd(), ".cursor", "hooks.json");
209568
+ }
209569
+ function cursorHookCommand(logPath) {
209570
+ if (!logPath) return CURSOR_HOOK_BIN;
209571
+ const abs = path.resolve(String(logPath));
209572
+ return `CODERIFTS_GUARD_EVENT_LOG=${abs} CODERIFTS_SESSION_ID=coderifts-cursor-hook ${CURSOR_HOOK_BIN}`;
209573
+ }
209574
+ function isOurCursorEntry(entry, command) {
209575
+ if (!entry || typeof entry !== "object") return false;
209576
+ return String(entry.command || "") === command;
209577
+ }
209578
+ function installCursorHook(opts = {}) {
209579
+ const cwd = opts.cwd || process.cwd();
209580
+ const home = opts.homeDir || os.homedir();
209581
+ const dest = opts.global ? path.join(home, ".cursor", "hooks.json") : path.join(cwd, ".cursor", "hooks.json");
209582
+ const command = cursorHookCommand(opts.logPath);
209583
+ const say = (...a) => {
209584
+ if (!opts.silent) console.log(...a);
209585
+ };
209586
+ const block = {
209587
+ command,
209588
+ matcher: CURSOR_MATCHER,
209589
+ timeout: 60,
209590
+ failClosed: true
209591
+ };
209592
+ let existing = {};
209593
+ let hadFile = false;
209594
+ if (fs.existsSync(dest)) {
209595
+ hadFile = true;
209596
+ let raw;
209597
+ try {
209598
+ raw = fs.readFileSync(dest, "utf8");
209599
+ } catch (err) {
209600
+ const message = `Error: cannot read ${dest} (${err && err.message}) \u2014 refusing to write.`;
209601
+ console.error(message);
209602
+ process.exitCode = 1;
209603
+ return { exitCode: 1, code: "READ_FAILED", path: dest, message };
209604
+ }
209605
+ if (String(raw).trim() !== "") {
209606
+ try {
209607
+ existing = JSON.parse(raw);
209608
+ } catch (_) {
209609
+ const message = `Error: ${dest} is not valid JSON \u2014 refusing (no write). Fix or remove the file, then re-run.`;
209610
+ console.error(message);
209611
+ process.exitCode = 1;
209612
+ return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
209613
+ }
209614
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
209615
+ const message = `Error: ${dest} JSON root is not an object \u2014 refusing (no write).`;
209616
+ console.error(message);
209617
+ process.exitCode = 1;
209618
+ return { exitCode: 1, code: "PARSE_FAILED", path: dest, message };
209619
+ }
209620
+ }
209621
+ }
209622
+ const hooksRoot = existing.hooks && typeof existing.hooks === "object" && !Array.isArray(existing.hooks) ? { ...existing.hooks } : {};
209623
+ const pre = Array.isArray(hooksRoot.preToolUse) ? hooksRoot.preToolUse.slice() : [];
209624
+ if (pre.some((e) => isOurCursorEntry(e, command))) {
209625
+ say(`Cursor hook already installed at ${dest} (no-op).`);
209626
+ return { exitCode: 0, code: "NOOP", path: dest, wrote: false };
209627
+ }
209628
+ if (opts.dryRun) {
209629
+ return { exitCode: 0, code: "WOULD_WRITE", path: dest, wrote: false, wouldWrite: true, hadFile };
209630
+ }
209631
+ const next = {
209632
+ ...existing,
209633
+ version: Number.isFinite(existing.version) ? existing.version : 1,
209634
+ hooks: { ...hooksRoot, preToolUse: pre.concat([block]) }
209635
+ };
209636
+ const serialized = `${JSON.stringify(next, null, 2)}
209637
+ `;
209638
+ let backedUp;
209639
+ if (hadFile) {
209640
+ backedUp = `${dest}.bak`;
209641
+ fs.copyFileSync(dest, backedUp);
209642
+ }
209643
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
209644
+ fs.writeFileSync(dest, serialized, "utf8");
209645
+ say(`Cursor preToolUse hook written to ${dest}`);
209646
+ if (backedUp) say(`Backup: ${backedUp}`);
209647
+ say(`matcher ${CURSOR_MATCHER} \u2192 ${CURSOR_HOOK_BIN} (deny = {"permission":"deny"} + exit 2; failClosed:true).`);
209648
+ return { exitCode: 0, code: "OK", path: dest, backedUp, wrote: true };
209649
+ }
209650
+ function install(options = {}) {
209651
+ if (options.global && !options.claude && !options.cursor) {
209652
+ console.error("Error: --global requires --claude or --cursor (writes ~/.claude/settings.json or ~/.cursor/hooks.json).");
209653
+ process.exitCode = 1;
209654
+ return { exitCode: 1, code: "USAGE" };
209655
+ }
209656
+ if (options.cursor) {
209657
+ const r = installCursorHook({
209658
+ global: !!options.global,
209659
+ logPath: options.log,
209660
+ cwd: options.cwd,
209661
+ homeDir: options.homeDir
209662
+ });
209663
+ if (r.exitCode !== 0) return r;
209664
+ }
209665
+ if (options.claude) {
209666
+ const r = installClaudeHook({
209667
+ global: !!options.global,
209668
+ logPath: options.log,
209669
+ cwd: options.cwd,
209670
+ homeDir: options.homeDir
209671
+ });
209672
+ if (r.exitCode !== 0) return r;
209673
+ }
209674
+ const gitDir = findGitDir(options.cwd);
209675
+ if (!gitDir) {
209676
+ if (options.claude || options.cursor) {
209677
+ console.log("Not inside a Git repository \u2014 skipped pre-push hook.");
209678
+ return { exitCode: 0, code: "OK" };
209679
+ }
209680
+ console.error("Error: Not inside a Git repository.");
209681
+ process.exitCode = 1;
209682
+ return { exitCode: 1, code: "NO_GIT" };
209683
+ }
209684
+ const hooksDir = path.join(gitDir, "hooks");
209685
+ if (!fs.existsSync(hooksDir)) {
209686
+ fs.mkdirSync(hooksDir, { recursive: true });
209687
+ }
209688
+ const hookPath = getHookPath(gitDir);
209689
+ if (fs.existsSync(hookPath) && !isCodeRiftsHook(hookPath)) {
209690
+ console.error("Error: A pre-push hook already exists and was not created by CodeRifts.");
209691
+ console.error("Remove it manually or back it up before installing.");
209692
+ process.exitCode = 1;
209693
+ return;
209694
+ }
209695
+ fs.writeFileSync(hookPath, PRE_PUSH_SCRIPT, { mode: 493 });
209696
+ console.log("CodeRifts pre-push hook installed.");
209697
+ console.log("");
209698
+ console.log("Configure your API key:");
209699
+ console.log(" git config coderifts.apiKey <your-key>");
209700
+ console.log("");
209701
+ console.log("Optionally set spec path (default: api/openapi.yaml):");
209702
+ console.log(" git config coderifts.specPath path/to/openapi.yaml");
209703
+ console.log("");
209704
+ console.log("After upgrading the coderifts CLI, re-run this command so the installed");
209705
+ console.log("hook matches the package (already-installed hooks are not auto-updated):");
209706
+ console.log(" coderifts hook install");
209707
+ console.log("");
209708
+ console.log("Agent-using repo? Run: coderifts agent-setup");
209709
+ }
209710
+ function uninstall() {
209711
+ const gitDir = findGitDir();
209712
+ if (!gitDir) {
209713
+ console.error("Error: Not inside a Git repository.");
209714
+ process.exitCode = 1;
209715
+ return;
209716
+ }
209717
+ const hookPath = getHookPath(gitDir);
209718
+ if (!fs.existsSync(hookPath)) {
209719
+ console.log("No pre-push hook found. Nothing to uninstall.");
209720
+ return;
209721
+ }
209722
+ if (!isCodeRiftsHook(hookPath)) {
209723
+ console.error("Error: The existing pre-push hook was not created by CodeRifts. Skipping.");
209724
+ process.exitCode = 1;
209725
+ return;
209726
+ }
209727
+ fs.unlinkSync(hookPath);
209728
+ console.log("CodeRifts pre-push hook removed.");
209729
+ }
209730
+ function status() {
209731
+ const gitDir = findGitDir();
209732
+ if (!gitDir) {
209733
+ console.error("Error: Not inside a Git repository.");
209734
+ process.exitCode = 1;
209735
+ return;
209736
+ }
209737
+ const hookPath = getHookPath(gitDir);
209738
+ const installed = isCodeRiftsHook(hookPath);
209739
+ console.log(`Hook: ${installed ? "installed" : "not installed"}`);
209740
+ if (installed) {
209741
+ try {
209742
+ const apiKey = execSync("git config coderifts.apiKey", { encoding: "utf-8", cwd: path.dirname(gitDir) }).trim();
209743
+ console.log(`API key: ${apiKey ? "configured" : "not configured"}`);
209744
+ } catch {
209745
+ console.log("API key: not configured");
209746
+ }
209747
+ try {
209748
+ const specPath = execSync("git config coderifts.specPath", { encoding: "utf-8", cwd: path.dirname(gitDir) }).trim();
209749
+ console.log(`Spec path: ${specPath || "api/openapi.yaml (default)"}`);
209750
+ } catch {
209751
+ console.log("Spec path: api/openapi.yaml (default)");
209752
+ }
209753
+ try {
209754
+ fs.accessSync(hookPath, fs.constants.X_OK);
209755
+ console.log("Executable: yes");
209756
+ } catch {
209757
+ console.log("Executable: no (run: chmod +x " + hookPath + ")");
209758
+ }
209759
+ }
209760
+ }
209761
+ module2.exports = {
209762
+ install,
209763
+ uninstall,
209764
+ status,
209765
+ PRE_PUSH_SCRIPT,
209766
+ HOOK_MARKER,
209767
+ findGitDir,
209768
+ isCodeRiftsHook,
209769
+ installClaudeHook,
209770
+ claudeSettingsPath,
209771
+ claudeHookCommand,
209772
+ CLAUDE_MATCHER,
209773
+ installCursorHook,
209774
+ cursorHooksPath,
209775
+ cursorHookCommand,
209776
+ CURSOR_MATCHER
209777
+ };
209778
+ }
209779
+ });
209780
+
209781
+ // src/mcp-repo-config.js
209782
+ var require_mcp_repo_config = __commonJS({
209783
+ "src/mcp-repo-config.js"(exports2, module2) {
209784
+ "use strict";
209785
+ var fs = require("fs");
209786
+ var path = require("path");
209787
+ var MCP_URL = "https://app.coderifts.com/mcp";
209788
+ var SERVER_NAME = "coderifts";
209789
+ var MCP_HOSTS = Object.freeze({
209790
+ claude: Object.freeze({
209791
+ id: "claude",
209792
+ relPath: ".mcp.json",
209793
+ label: "Claude Code",
209794
+ // Measured: type+url required. url-without-type is a config error.
209795
+ entry: Object.freeze({
209796
+ type: "http",
209797
+ url: MCP_URL,
209798
+ headers: Object.freeze({
209799
+ Authorization: "Bearer ${CODERIFTS_API_KEY}"
209800
+ })
209801
+ }),
209802
+ shapeNote: "mcpServers.coderifts type=http url (not httpUrl; type is required)"
209803
+ }),
209804
+ cursor: Object.freeze({
209805
+ id: "cursor",
209806
+ relPath: ".cursor/mcp.json",
209807
+ label: "Cursor",
209808
+ // Measured: url only — Cursor docs do not use type for remote HTTP.
209809
+ entry: Object.freeze({
209810
+ url: MCP_URL,
209811
+ headers: Object.freeze({
209812
+ Authorization: "Bearer ${env:CODERIFTS_API_KEY}"
209813
+ })
209814
+ }),
209815
+ shapeNote: "mcpServers.coderifts url (no type \u2014 Cursor remote form)"
209816
+ })
209817
+ });
209818
+ function deepEqual(a, b) {
209819
+ if (a === b) return true;
209820
+ try {
209821
+ return JSON.stringify(a) === JSON.stringify(b);
209822
+ } catch {
209823
+ return false;
209824
+ }
209825
+ }
209826
+ function planMcpMerge(existing, entry) {
209827
+ const hadFile = existing != null;
209828
+ const base = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
209829
+ const servers = base.mcpServers && typeof base.mcpServers === "object" && !Array.isArray(base.mcpServers) ? base.mcpServers : {};
209830
+ const current = Object.prototype.hasOwnProperty.call(servers, SERVER_NAME) ? servers[SERVER_NAME] : void 0;
209831
+ if (deepEqual(current, entry)) {
209832
+ return { action: "skip", next: null, reason: "already present, identical" };
209833
+ }
209834
+ const next = {
209835
+ ...base,
209836
+ mcpServers: {
209837
+ ...servers,
209838
+ [SERVER_NAME]: entry
209839
+ }
209840
+ };
209841
+ if (!hadFile) {
209842
+ return { action: "create", next, reason: "created" };
209843
+ }
209844
+ const reason = current === void 0 ? "added mcpServers.coderifts; preserved other servers" : "updated mcpServers.coderifts; preserved other servers";
209845
+ return { action: "merge", next, reason };
209846
+ }
209847
+ function serializeMcp(doc) {
209848
+ return `${JSON.stringify(doc, null, 2)}
209849
+ `;
209850
+ }
209851
+ function writeHostMcpConfig(opts) {
209852
+ const host = MCP_HOSTS[opts.host];
209853
+ if (!host) {
209854
+ return { action: "error", relPath: "", reason: `unknown host ${opts.host}`, exitCode: 1 };
209855
+ }
209856
+ const outDir = opts.outDir;
209857
+ const dest = path.join(outDir, host.relPath);
209858
+ const exists = opts.exists || fs.existsSync.bind(fs);
209859
+ const readFile = opts.readFile || ((p) => fs.readFileSync(p, "utf8"));
209860
+ const writeFile = opts.writeFile || ((p, c) => {
209861
+ fs.mkdirSync(path.dirname(p), { recursive: true });
209862
+ fs.writeFileSync(p, c, "utf8");
209863
+ });
209864
+ const copyFile = opts.copyFile || ((a, b) => fs.copyFileSync(a, b));
209865
+ const dryRun = !!opts.dryRun;
209866
+ let existing = null;
209867
+ if (exists(dest)) {
209868
+ let raw;
209869
+ try {
209870
+ raw = readFile(dest);
209871
+ } catch (err) {
209872
+ const message = `Error: cannot read ${host.relPath} (${err && err.message}) \u2014 refusing to write.`;
209873
+ return {
209874
+ action: "error",
209875
+ relPath: host.relPath,
209876
+ reason: "read failed",
209877
+ exitCode: 1,
209878
+ message,
209879
+ code: "READ_FAILED"
209880
+ };
209881
+ }
209882
+ if (String(raw).trim() !== "") {
209883
+ let parsed;
209884
+ try {
209885
+ parsed = JSON.parse(raw);
209886
+ } catch {
209887
+ const message = `Error: ${host.relPath} is not valid JSON \u2014 refusing (no write). Fix or remove the file, then re-run.`;
209888
+ return {
209889
+ action: "error",
209890
+ relPath: host.relPath,
209891
+ reason: "not valid JSON",
209892
+ exitCode: 1,
209893
+ message,
209894
+ code: "PARSE_FAILED"
209895
+ };
209896
+ }
209897
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
209898
+ const message = `Error: ${host.relPath} JSON root is not an object \u2014 refusing (no write).`;
209899
+ return {
209900
+ action: "error",
209901
+ relPath: host.relPath,
209902
+ reason: "JSON root is not an object",
209903
+ exitCode: 1,
209904
+ message,
209905
+ code: "PARSE_FAILED"
209906
+ };
209907
+ }
209908
+ existing = parsed;
209909
+ } else {
209910
+ existing = {};
209911
+ }
209912
+ }
209913
+ const plan = planMcpMerge(existing, host.entry);
209914
+ if (plan.action === "skip") {
209915
+ return { action: "skip", relPath: host.relPath, reason: plan.reason, exitCode: 0 };
209916
+ }
209917
+ if (dryRun) {
209918
+ return {
209919
+ action: plan.action,
209920
+ relPath: host.relPath,
209921
+ reason: plan.reason,
209922
+ backedUp: plan.action === "merge" ? `${host.relPath}.bak` : void 0,
209923
+ exitCode: 0,
209924
+ dryRun: true
209925
+ };
209926
+ }
209927
+ let backedUp;
209928
+ if (plan.action === "merge" && exists(dest)) {
209929
+ backedUp = `${dest}.bak`;
209930
+ copyFile(dest, backedUp);
209931
+ }
209932
+ writeFile(dest, serializeMcp(plan.next));
209933
+ return {
209934
+ action: plan.action,
209935
+ relPath: host.relPath,
209936
+ reason: plan.reason,
209937
+ backedUp: backedUp ? `${host.relPath}.bak` : void 0,
209938
+ exitCode: 0
209939
+ };
209940
+ }
209941
+ function mcpConfigPresent(outDir, hostId, deps = {}) {
209942
+ const host = MCP_HOSTS[hostId];
209943
+ if (!host) return false;
209944
+ const exists = deps.exists || fs.existsSync.bind(fs);
209945
+ const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
209946
+ const fp = path.join(outDir, host.relPath);
209947
+ if (!exists(fp)) return false;
209948
+ try {
209949
+ const doc = JSON.parse(readFile(fp));
209950
+ return planMcpMerge(doc, host.entry).action === "skip";
209951
+ } catch {
209952
+ return false;
209953
+ }
209954
+ }
209955
+ module2.exports = {
209956
+ MCP_URL,
209957
+ SERVER_NAME,
209958
+ MCP_HOSTS,
209959
+ planMcpMerge,
209960
+ serializeMcp,
209961
+ writeHostMcpConfig,
209962
+ mcpConfigPresent
209963
+ };
209964
+ }
209965
+ });
209966
+
209967
+ // src/contract-gate-workflow.js
209968
+ var require_contract_gate_workflow = __commonJS({
209969
+ "src/contract-gate-workflow.js"(exports2, module2) {
209970
+ "use strict";
209971
+ var fs = require("fs");
209972
+ var path = require("path");
209973
+ var WORKFLOW_REL = ".github/workflows/coderifts.yml";
209974
+ var GATE_ACTION = "coderifts/contract-gate@v0";
209975
+ var ENFORCEMENT_DOC = "https://github.com/coderifts/contract-gate/blob/main/ENFORCEMENT.md";
209976
+ var WORKFLOW_BODY = `# Written by \`coderifts init --agents\`. Re-run is a no-op when this file already exists.
209977
+ #
209978
+ # This workflow POSTS the Check Run named exactly:
209979
+ # CodeRifts / contract-gate
209980
+ # It does NOT enable branch protection and it does NOT make that check required.
209981
+ # Turn the check on in repo settings to enforce (${ENFORCEMENT_DOC}).
209982
+ #
209983
+ # Copied from the gate repo example (examples/contract-gate.yml) \u2014 action at @v0.
209984
+ name: CodeRifts Contract Gate
209985
+
209986
+ # Run on EVERY pull request. Do NOT add paths: filters \u2014 a required check that
209987
+ # never runs leaves the PR mergeable (see ENFORCEMENT.md, "fail-closed on absence").
209988
+ on:
209989
+ pull_request:
209990
+
209991
+ permissions:
209992
+ checks: write
209993
+ contents: read
209994
+
209995
+ jobs:
209996
+ contract-gate:
209997
+ runs-on: ubuntu-latest
209998
+ steps:
209999
+ - uses: actions/checkout@v4
210000
+ with:
210001
+ # REQUIRED: the gate derives the change set from git diff base...head.
210002
+ fetch-depth: 0
210003
+
210004
+ - uses: ${GATE_ACTION}
210005
+ with:
210006
+ api-key: \${{ secrets.CODERIFTS_API_KEY }}
210007
+ # api-url: https://app.coderifts.com # override only for self-hosted
210008
+ #
210009
+ # Optional \u2014 require a signed CWM delivery attestation (guard \u22659.2.0).
210010
+ # Honest default: false (commented). Uncomment the three lines to enforce.
210011
+ # require-verified-monitoring: 'true'
210012
+ # monitoring-attestation: \${{ vars.CODERIFTS_MONITORING_ATTESTATION }}
210013
+ # monitoring-keyring: \${{ github.workspace }}/.coderifts/monitoring-keys.json
210014
+ `;
210015
+ function writeContractGateWorkflow(opts = {}) {
210016
+ const outDir = opts.outDir;
210017
+ const dest = path.join(outDir, WORKFLOW_REL);
210018
+ const exists = opts.exists || fs.existsSync.bind(fs);
210019
+ const readFile = opts.readFile || ((p) => fs.readFileSync(p, "utf8"));
210020
+ const writeFile = opts.writeFile || ((p, c) => {
210021
+ fs.mkdirSync(path.dirname(p), { recursive: true });
210022
+ fs.writeFileSync(p, c, "utf8");
210023
+ });
210024
+ const dryRun = !!opts.dryRun;
210025
+ if (exists(dest)) {
210026
+ let onDisk = "";
210027
+ try {
210028
+ onDisk = readFile(dest);
210029
+ } catch {
210030
+ onDisk = "";
210031
+ }
210032
+ const reason = onDisk === WORKFLOW_BODY ? "already present, identical" : "exists; installer does not clobber workflows";
210033
+ return { action: "skip", relPath: WORKFLOW_REL, reason, exitCode: 0 };
210034
+ }
210035
+ if (dryRun) {
210036
+ return { action: "create", relPath: WORKFLOW_REL, reason: "created", exitCode: 0, dryRun: true };
210037
+ }
210038
+ writeFile(dest, WORKFLOW_BODY);
210039
+ return { action: "create", relPath: WORKFLOW_REL, reason: "created", exitCode: 0 };
210040
+ }
210041
+ function workflowPresent(outDir, deps = {}) {
210042
+ const exists = deps.exists || fs.existsSync.bind(fs);
210043
+ const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
210044
+ const fp = path.join(outDir, WORKFLOW_REL);
210045
+ if (!exists(fp)) return false;
210046
+ try {
210047
+ return readFile(fp).includes(GATE_ACTION);
210048
+ } catch {
210049
+ return false;
210050
+ }
210051
+ }
210052
+ module2.exports = {
210053
+ WORKFLOW_REL,
210054
+ WORKFLOW_BODY,
210055
+ GATE_ACTION,
210056
+ ENFORCEMENT_DOC,
210057
+ writeContractGateWorkflow,
210058
+ workflowPresent
210059
+ };
210060
+ }
210061
+ });
210062
+
210063
+ // src/commands/init-agents.js
210064
+ var require_init_agents = __commonJS({
210065
+ "src/commands/init-agents.js"(exports2, module2) {
210066
+ "use strict";
210067
+ var fs = require("fs");
210068
+ var path = require("path");
210069
+ var chalk = require_source();
210070
+ var { writeAgentHostFiles, AGENT_HOST_FILES } = require_agent_setup();
210071
+ var { installClaudeHook, installCursorHook } = require_hook();
210072
+ var {
210073
+ MCP_HOSTS,
210074
+ writeHostMcpConfig,
210075
+ mcpConfigPresent
210076
+ } = require_mcp_repo_config();
210077
+ var {
210078
+ WORKFLOW_REL,
210079
+ GATE_ACTION,
210080
+ ENFORCEMENT_DOC,
210081
+ writeContractGateWorkflow,
210082
+ workflowPresent
210083
+ } = require_contract_gate_workflow();
210084
+ if (process.env.NO_COLOR) chalk.level = 0;
210085
+ var USAGE = `Usage: coderifts init --agents [--hosts=claude,cursor,all] [--no-hook] [--no-workflow] [--dry-run] [--check] [--out <dir>]
210086
+
210087
+ Wire this repository for governed agent work in ONE command:
210088
+ 1. repo-level MCP config (per host: .mcp.json / .cursor/mcp.json)
210089
+ 2. generated agent rule files (reuses agent-setup)
210090
+ 3. host hook (Claude Code today; Cursor when hook install --cursor is present)
210091
+ 4. .github/workflows/coderifts.yml (coderifts/contract-gate@v0)
210092
+
210093
+ --hosts <list> claude, cursor, or all (default: all)
210094
+ --no-hook Skip host hook install
210095
+ --no-workflow Skip writing the CI workflow
210096
+ --dry-run Print the plan without writing
210097
+ --check Report which of the four pieces are present (no write)
210098
+ --out <dir> Target directory (default: cwd)
210099
+ Unknown flags exit 1 (never silently ignored).
210100
+ `;
210101
+ var SHARED_RULE = "AGENTS.md";
210102
+ var HOST_RULES = Object.freeze({
210103
+ claude: Object.freeze(["CLAUDE.md"]),
210104
+ cursor: Object.freeze([".cursor/rules/coderifts.mdc"])
210105
+ });
210106
+ var EXTRA_RULES_ON_ALL = Object.freeze([
210107
+ ".github/copilot-instructions.md",
210108
+ "coderifts-langgraph-policy.js",
210109
+ "openai-agent-instructions.md"
210110
+ ]);
210111
+ var NEXT_MANUAL_STEP_LINES = Object.freeze([
210112
+ " Next manual step",
210113
+ " Add CODERIFTS_API_KEY to the repository secrets (Settings \u2192 Secrets and variables \u2192 Actions).",
210114
+ " The CLI never writes a key anywhere."
210115
+ ]);
210116
+ var HONESTY_LINES = Object.freeze([
210117
+ " This command does NOT",
210118
+ ` \xB7 enable branch protection \u2014 ${ENFORCEMENT_DOC}`,
210119
+ " \xB7 make raw host tools inescapable (host convention, not a package guarantee)",
210120
+ ' \xB7 enforce the workflow until "CodeRifts / contract-gate" is a required check in repo settings',
210121
+ " \xB7 write Gemini MCP config (Gemini uses httpUrl at extension level, not a repo file)"
210122
+ ]);
210123
+ function parseHosts(raw) {
210124
+ if (raw == null || raw === true || String(raw).trim() === "") {
210125
+ return { hosts: ["claude", "cursor"], allRules: true };
210126
+ }
210127
+ const parts = String(raw).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
210128
+ if (parts.length === 0) {
210129
+ return { hosts: ["claude", "cursor"], allRules: true, error: `init --agents: --hosts requires a value
210130
+ ${USAGE}` };
210131
+ }
210132
+ const allowed = /* @__PURE__ */ new Set(["claude", "cursor", "all"]);
210133
+ for (const p of parts) {
210134
+ if (!allowed.has(p)) {
210135
+ return {
210136
+ hosts: [],
210137
+ allRules: false,
210138
+ error: `init --agents: unknown host "${p}" (use claude, cursor, all)
210139
+ ${USAGE}`
210140
+ };
210141
+ }
210142
+ }
210143
+ if (parts.includes("all")) {
210144
+ return { hosts: ["claude", "cursor"], allRules: true };
210145
+ }
210146
+ const hosts = [];
210147
+ for (const p of parts) {
210148
+ if (!hosts.includes(p)) hosts.push(p);
210149
+ }
210150
+ return { hosts, allRules: false };
210151
+ }
210152
+ function agentFlagsRequireAgents(options = {}) {
210153
+ 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);
210154
+ }
210155
+ var AGENT_FLAGS_REQUIRE_AGENTS = "Error: --check/--dry-run/--out/--hosts/--no-hook/--no-workflow require --agents";
210156
+ var AGENTS_NO_TEMPLATE = "Error: init --agents does not take a template. Did you mean `coderifts init --agents` (not `coderifts init agent`)?";
210157
+ function rulePathsForHosts(hosts, allRules) {
210158
+ const out = [SHARED_RULE];
210159
+ for (const h of hosts) {
210160
+ const extra = HOST_RULES[h] || [];
210161
+ for (const rel of extra) {
210162
+ if (!out.includes(rel)) out.push(rel);
210163
+ }
210164
+ }
210165
+ if (allRules) {
210166
+ for (const rel of EXTRA_RULES_ON_ALL) {
210167
+ if (!out.includes(rel)) out.push(rel);
210168
+ }
210169
+ }
210170
+ return out;
210171
+ }
210172
+ function hookRelForHost(hostId) {
210173
+ return hostId === "cursor" ? ".cursor/hooks.json" : ".claude/settings.json";
210174
+ }
210175
+ function hookNoteForHost(hostId) {
210176
+ return hostId === "cursor" ? "preToolUse \u2192 coderifts cursor-hook" : "PreToolUse \u2192 coderifts claude-hook";
210177
+ }
210178
+ function wantHook(options) {
210179
+ if (options.noHook === true) return false;
210180
+ if (options.hook === false) return false;
210181
+ return true;
210182
+ }
210183
+ function wantWorkflow(options) {
210184
+ if (options.noWorkflow === true) return false;
210185
+ if (options.workflow === false) return false;
210186
+ return true;
210187
+ }
210188
+ function padVerb(verb) {
210189
+ return String(verb).padEnd(7, " ");
210190
+ }
210191
+ function formatActionLine(action, relPath, note) {
210192
+ const verb = padVerb(action);
210193
+ const extra = note ? ` (${note})` : "";
210194
+ return ` ${verb} ${relPath}${extra}`;
210195
+ }
210196
+ function checkInitAgents(options = {}, deps = {}) {
210197
+ const outDir = options.out ? path.resolve(String(options.out)) : deps.cwd || process.cwd();
210198
+ const parsed = parseHosts(options.hosts);
210199
+ const hosts = parsed.hosts;
210200
+ const allRules = parsed.allRules;
210201
+ const fsDeps = {
210202
+ exists: deps.exists || fs.existsSync.bind(fs),
210203
+ readFile: deps.readFile || ((p) => fs.readFileSync(p, "utf8"))
210204
+ };
210205
+ const mcpPresent = [];
210206
+ const mcpMissing = [];
210207
+ for (const h of hosts) {
210208
+ const rel = MCP_HOSTS[h].relPath;
210209
+ if (mcpConfigPresent(outDir, h, fsDeps)) mcpPresent.push(rel);
210210
+ else mcpMissing.push(rel);
210211
+ }
210212
+ const rulePaths = rulePathsForHosts(hosts, allRules);
210213
+ const rulesPresent = [];
210214
+ const rulesMissing = [];
210215
+ for (const rel of rulePaths) {
210216
+ if (fsDeps.exists(path.join(outDir, rel))) rulesPresent.push(rel);
210217
+ else rulesMissing.push(rel);
210218
+ }
210219
+ const hooksPresent = [];
210220
+ const hooksMissing = [];
210221
+ const hooksWanted = wantHook(options);
210222
+ if (hooksWanted) {
210223
+ for (const h of hosts) {
210224
+ const rel = hookRelForHost(h);
210225
+ const installer = h === "cursor" ? installCursorHook : installClaudeHook;
210226
+ const r = installer({ cwd: outDir, silent: true, dryRun: true });
210227
+ if (r && r.code === "NOOP") hooksPresent.push(rel);
210228
+ else hooksMissing.push(rel);
210229
+ }
210230
+ }
210231
+ const workflowWanted = wantWorkflow(options);
210232
+ const wfPresent = workflowWanted && workflowPresent(outDir, fsDeps);
210233
+ const pieces = {
210234
+ mcp: { present: mcpPresent, missing: mcpMissing },
210235
+ rules: { present: rulesPresent, missing: rulesMissing },
210236
+ hooks: {
210237
+ wanted: hooksWanted,
210238
+ present: hooksPresent,
210239
+ missing: hooksMissing
210240
+ },
210241
+ workflow: {
210242
+ wanted: workflowWanted,
210243
+ present: wfPresent,
210244
+ missing: workflowWanted && !wfPresent ? [WORKFLOW_REL] : []
210245
+ }
210246
+ };
210247
+ const missingCount = mcpMissing.length + rulesMissing.length + hooksMissing.length + (workflowWanted && !wfPresent ? 1 : 0);
210248
+ return {
210249
+ exitCode: missingCount === 0 ? 0 : 1,
210250
+ pieces,
210251
+ allPresent: missingCount === 0,
210252
+ outDir,
210253
+ hosts
210254
+ };
210255
+ }
210256
+ function printCheck(result, log) {
210257
+ const { pieces, outDir, hosts, allPresent } = result;
210258
+ log(chalk.bold("CodeRifts init --agents --check"));
210259
+ log(` target: ${outDir}`);
210260
+ log(` hosts: ${hosts.join(", ")}`);
210261
+ log("");
210262
+ const mcpStatus = pieces.mcp.missing.length === 0 ? "present" : "missing";
210263
+ const mcpDetail = pieces.mcp.missing.length === 0 ? pieces.mcp.present.join(", ") : pieces.mcp.missing.join(", ");
210264
+ log(` MCP config: ${mcpStatus} (${mcpDetail})`);
210265
+ const rulesStatus = pieces.rules.missing.length === 0 ? "present" : "missing";
210266
+ const rulesDetail = pieces.rules.missing.length === 0 ? `${pieces.rules.present.length} files` : pieces.rules.missing.join(", ");
210267
+ log(` Agent rules: ${rulesStatus} (${rulesDetail})`);
210268
+ if (!pieces.hooks.wanted) {
210269
+ log(" Host hooks: skipped (--no-hook)");
210270
+ } else {
210271
+ const hookStatus = pieces.hooks.missing.length === 0 ? "present" : "missing";
210272
+ const hookDetail = pieces.hooks.missing.length === 0 ? pieces.hooks.present.join(", ") : pieces.hooks.missing.join(", ");
210273
+ log(` Host hooks: ${hookStatus} (${hookDetail})`);
210274
+ }
210275
+ if (!pieces.workflow.wanted) {
210276
+ log(" CI workflow: skipped (--no-workflow)");
210277
+ } else {
210278
+ log(pieces.workflow.present ? ` CI workflow: present (${WORKFLOW_REL})` : ` CI workflow: missing (${WORKFLOW_REL})`);
210279
+ }
210280
+ log("");
210281
+ if (allPresent) log(chalk.green(" All four pieces present."));
210282
+ else log(chalk.yellow(" Incomplete \u2014 run: coderifts init --agents"));
210283
+ }
210284
+ function runInitAgents(options = {}, deps = {}) {
210285
+ const log = deps.log || console.log.bind(console);
210286
+ const logErr = deps.logErr || console.error.bind(console);
210287
+ const doExit = deps.exit !== false;
210288
+ const outDir = options.out ? path.resolve(String(options.out)) : deps.cwd || process.cwd();
210289
+ const dryRun = !!options.dryRun;
210290
+ const fsDeps = {
210291
+ exists: deps.exists || fs.existsSync.bind(fs),
210292
+ readFile: deps.readFile || ((p) => fs.readFileSync(p, "utf8")),
210293
+ writeFile: deps.writeFile || ((p, c) => {
210294
+ fs.mkdirSync(path.dirname(p), { recursive: true });
210295
+ fs.writeFileSync(p, c, "utf8");
210296
+ }),
210297
+ copyFile: deps.copyFile || ((a, b) => fs.copyFileSync(a, b))
210298
+ };
210299
+ const parsed = parseHosts(options.hosts);
210300
+ if (parsed.error) {
210301
+ logErr(parsed.error.trimEnd());
210302
+ if (doExit) process.exit(1);
210303
+ return { exitCode: 1, code: "USAGE", message: parsed.error };
210304
+ }
210305
+ const hosts = parsed.hosts;
210306
+ const allRules = parsed.allRules;
210307
+ if (options.check) {
210308
+ const result2 = checkInitAgents({ ...options, out: outDir, hosts: options.hosts }, { ...deps, cwd: outDir });
210309
+ printCheck(result2, log);
210310
+ if (doExit) process.exit(result2.exitCode);
210311
+ return { ...result2, code: result2.allPresent ? "UP_TO_DATE" : "INCOMPLETE" };
210312
+ }
210313
+ const items = [];
210314
+ let fatal;
210315
+ for (const h of hosts) {
210316
+ const r = writeHostMcpConfig({
210317
+ host: h,
210318
+ outDir,
210319
+ dryRun,
210320
+ ...fsDeps
210321
+ });
210322
+ if (r.exitCode !== 0) {
210323
+ fatal = r;
210324
+ break;
210325
+ }
210326
+ items.push({
210327
+ section: "mcp",
210328
+ action: r.action,
210329
+ relPath: r.relPath,
210330
+ note: r.action === "skip" ? r.reason : `${MCP_HOSTS[h].shapeNote}${r.action === "merge" ? "; other servers preserved" : ""}`,
210331
+ backedUp: r.backedUp
210332
+ });
210333
+ }
210334
+ if (fatal) {
210335
+ printPartialAndError(items, fatal, { outDir, hosts, dryRun, log, logErr });
210336
+ if (doExit) process.exit(1);
210337
+ return { exitCode: 1, code: fatal.code || "ERROR", message: fatal.message, items };
210338
+ }
210339
+ const rulePaths = rulePathsForHosts(hosts, allRules);
210340
+ const ruleSummary = writeAgentHostFiles({
210341
+ outDir,
210342
+ force: false,
210343
+ files: AGENT_HOST_FILES,
210344
+ paths: rulePaths,
210345
+ exists: fsDeps.exists,
210346
+ writeFile: fsDeps.writeFile,
210347
+ dryRun
210348
+ });
210349
+ for (const rel of ruleSummary.written) {
210350
+ items.push({ section: "rules", action: "create", relPath: rel, note: "generated agent-host rule" });
210351
+ }
210352
+ for (const rel of ruleSummary.skipped) {
210353
+ items.push({ section: "rules", action: "skip", relPath: rel, note: "exists; installer does not overwrite" });
210354
+ }
210355
+ if (!wantHook(options)) {
210356
+ for (const h of hosts) {
210357
+ items.push({
210358
+ section: "hooks",
210359
+ action: "skip",
210360
+ relPath: hookRelForHost(h),
210361
+ note: "--no-hook"
210362
+ });
210363
+ }
210364
+ } else {
210365
+ for (const h of hosts) {
210366
+ const installer = h === "cursor" ? installCursorHook : installClaudeHook;
210367
+ const r = installer({
210368
+ cwd: outDir,
210369
+ silent: true,
210370
+ dryRun
210371
+ });
210372
+ if (r.exitCode !== 0) {
210373
+ fatal = r;
210374
+ break;
210375
+ }
210376
+ const rel = hookRelForHost(h);
210377
+ if (r.code === "NOOP") {
210378
+ items.push({ section: "hooks", action: "skip", relPath: rel, note: "already installed" });
210379
+ } else if (r.backedUp || r.hadFile) {
210380
+ items.push({
210381
+ section: "hooks",
210382
+ action: "merge",
210383
+ relPath: rel,
210384
+ note: hookNoteForHost(h),
210385
+ backedUp: r.backedUp ? `${rel}.bak` : dryRun ? `${rel}.bak` : void 0
210386
+ });
210387
+ } else {
210388
+ items.push({ section: "hooks", action: "create", relPath: rel, note: hookNoteForHost(h) });
210389
+ }
210390
+ }
210391
+ }
210392
+ if (fatal) {
210393
+ printPartialAndError(items, fatal, { outDir, hosts, dryRun, log, logErr });
210394
+ if (doExit) process.exit(1);
210395
+ return { exitCode: 1, code: fatal.code || "ERROR", message: fatal.message, items };
210396
+ }
210397
+ if (!wantWorkflow(options)) {
210398
+ items.push({ section: "workflow", action: "skip", relPath: WORKFLOW_REL, note: "--no-workflow" });
210399
+ } else {
210400
+ const r = writeContractGateWorkflow({ outDir, dryRun, ...fsDeps });
210401
+ items.push({
210402
+ section: "workflow",
210403
+ action: r.action,
210404
+ relPath: r.relPath,
210405
+ note: r.action === "create" ? `${GATE_ACTION}` : r.reason
210406
+ });
210407
+ }
210408
+ const created = items.filter((i) => i.action === "create").length;
210409
+ const merged = items.filter((i) => i.action === "merge").length;
210410
+ const skipped = items.filter((i) => i.action === "skip").length;
210411
+ const unchanged = created === 0 && merged === 0;
210412
+ const summaryLines = buildSummaryLines({
210413
+ outDir,
210414
+ hosts,
210415
+ dryRun,
210416
+ items,
210417
+ created,
210418
+ merged,
210419
+ skipped,
210420
+ unchanged
210421
+ });
210422
+ for (const line of summaryLines) log(line);
210423
+ const result = {
210424
+ exitCode: 0,
210425
+ code: unchanged ? "NOOP" : "OK",
210426
+ outDir,
210427
+ hosts,
210428
+ dryRun,
210429
+ created,
210430
+ merged,
210431
+ skipped,
210432
+ items,
210433
+ summary: summaryLines.join("\n")
210434
+ };
210435
+ if (doExit) process.exit(0);
210436
+ return result;
210437
+ }
210438
+ function buildSummaryLines({ outDir, hosts, dryRun, items, created, merged, skipped, unchanged }) {
210439
+ const lines = [];
210440
+ lines.push(chalk.bold("CodeRifts init --agents"));
210441
+ lines.push(` target: ${outDir}`);
210442
+ lines.push(` hosts: ${hosts.join(", ")}`);
210443
+ if (dryRun) {
210444
+ lines.push("");
210445
+ lines.push(chalk.yellow(" DRY-RUN \u2014 no files written"));
210446
+ }
210447
+ lines.push("");
210448
+ const sections = [
210449
+ ["mcp", "MCP config"],
210450
+ ["rules", "Agent rules"],
210451
+ ["hooks", "Host hooks"],
210452
+ ["workflow", "CI workflow"]
210453
+ ];
210454
+ for (const [key, title] of sections) {
210455
+ lines.push(` ${title}`);
210456
+ const group = items.filter((i) => i.section === key);
210457
+ for (const it of group) {
210458
+ const verb = it.action === "create" ? "created" : it.action === "merge" ? "merged" : "skipped";
210459
+ const color = it.action === "skip" ? chalk.yellow : chalk.green;
210460
+ lines.push(color(formatActionLine(verb, it.relPath, it.note)));
210461
+ if (it.backedUp) {
210462
+ lines.push(chalk.dim(formatActionLine("backup", it.backedUp, "")));
210463
+ }
210464
+ }
210465
+ }
210466
+ lines.push("");
210467
+ lines.push(chalk.dim(` ${created} created, ${merged} merged, ${skipped} skipped`));
210468
+ if (unchanged && !dryRun) {
210469
+ lines.push(chalk.dim(" Re-run changed nothing."));
210470
+ }
210471
+ if (items.some((i) => i.backedUp)) {
210472
+ lines.push(chalk.dim(" Do not commit *.bak (may contain prior contents, including secrets)."));
210473
+ }
210474
+ lines.push("");
210475
+ for (const line of NEXT_MANUAL_STEP_LINES) lines.push(chalk.dim(line));
210476
+ lines.push("");
210477
+ for (const line of HONESTY_LINES) {
210478
+ lines.push(line === HONESTY_LINES[0] ? chalk.bold(line) : chalk.dim(line));
210479
+ }
210480
+ return lines;
210481
+ }
210482
+ function printPartialAndError(items, fatal, { outDir, hosts, dryRun, log, logErr }) {
210483
+ if (items.length > 0) {
210484
+ const created = items.filter((i) => i.action === "create").length;
210485
+ const merged = items.filter((i) => i.action === "merge").length;
210486
+ const skipped = items.filter((i) => i.action === "skip").length;
210487
+ log(chalk.yellow(" Partial write (stopped on error) \u2014 already-written pieces:"));
210488
+ for (const line of buildSummaryLines({
210489
+ outDir,
210490
+ hosts,
210491
+ dryRun,
210492
+ items,
210493
+ created,
210494
+ merged,
210495
+ skipped,
210496
+ unchanged: false
210497
+ })) {
210498
+ log(line);
210499
+ }
210500
+ }
210501
+ logErr(fatal.message || fatal.reason || "init --agents failed");
210502
+ }
210503
+ module2.exports = {
210504
+ runInitAgents,
210505
+ parseHosts,
210506
+ checkInitAgents,
210507
+ rulePathsForHosts,
210508
+ wantHook,
210509
+ wantWorkflow,
210510
+ agentFlagsRequireAgents,
210511
+ AGENT_FLAGS_REQUIRE_AGENTS,
210512
+ AGENTS_NO_TEMPLATE,
210513
+ USAGE,
210514
+ NEXT_MANUAL_STEP_LINES,
210515
+ HONESTY_LINES,
210516
+ HOST_RULES,
210517
+ EXTRA_RULES_ON_ALL,
210518
+ SHARED_RULE,
210519
+ buildSummaryLines
210520
+ };
210521
+ }
210522
+ });
210523
+
208975
210524
  // src/commands/init.js
208976
210525
  var require_init2 = __commonJS({
208977
210526
  "src/commands/init.js"(exports2, module2) {
@@ -235450,472 +236999,6 @@ var require_status = __commonJS({
235450
236999
  }
235451
237000
  });
235452
237001
 
235453
- // src/commands/hook.js
235454
- var require_hook = __commonJS({
235455
- "src/commands/hook.js"(exports2, module2) {
235456
- "use strict";
235457
- var fs = require("fs");
235458
- 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);
235709
- }
235710
- return null;
235711
- }
235712
- function getHookPath(gitDir) {
235713
- return path.join(gitDir, "hooks", "pre-push");
235714
- }
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);
235719
- }
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");
235725
- }
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}`;
235730
- }
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);
235736
- }
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 };
235758
- }
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
- }
235774
- }
235775
- }
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 };
235781
- }
235782
- const nextPre = pre.concat([block]);
235783
- const next = {
235784
- ...existing,
235785
- hooks: { ...hooksRoot, PreToolUse: nextPre }
235786
- };
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
- }
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;
235815
- }
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" };
235821
- }
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;
235836
- }
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");
235851
- }
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.");
235871
- }
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");
235888
- }
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)");
235894
- }
235895
- try {
235896
- fs.accessSync(hookPath, fs.constants.X_OK);
235897
- console.log("Executable: yes");
235898
- } catch {
235899
- console.log("Executable: no (run: chmod +x " + hookPath + ")");
235900
- }
235901
- }
235902
- }
235903
- 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
235915
- };
235916
- }
235917
- });
235918
-
235919
237002
  // src/commands/enforce.js
235920
237003
  var require_enforce = __commonJS({
235921
237004
  "src/commands/enforce.js"(exports2, module2) {
@@ -236179,216 +237262,6 @@ var require_enforce = __commonJS({
236179
237262
  }
236180
237263
  });
236181
237264
 
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
237265
  // src/copilot-mcp.embedded.js
236393
237266
  var require_copilot_mcp_embedded = __commonJS({
236394
237267
  "src/copilot-mcp.embedded.js"(exports2, module2) {
@@ -237329,6 +238202,130 @@ var require_outcome = __commonJS({
237329
238202
  }
237330
238203
  });
237331
238204
 
238205
+ // src/commands/cursor-hook.js
238206
+ var require_cursor_hook = __commonJS({
238207
+ "src/commands/cursor-hook.js"(exports2, module2) {
238208
+ "use strict";
238209
+ var { runClaudeHook } = require_claude_hook();
238210
+ var USAGE = `Usage: coderifts cursor-hook
238211
+
238212
+ Cursor preToolUse adapter. Reads Cursor's hook JSON on stdin, emits
238213
+ { permission, user_message, agent_message } on stdout.
238214
+
238215
+ allow -> the mutation proceeds (non-governed paths are silent)
238216
+ deny -> the mutation is blocked; user_message carries the WHY,
238217
+ agent_message carries the remediation
238218
+
238219
+ REQUIRE_APPROVAL maps to deny: Cursor does not enforce "ask" for preToolUse,
238220
+ so surfacing it as ask would let a human-approval decision through.
238221
+ Unparseable stdin denies (fail-closed).
238222
+ `;
238223
+ var CURSOR_WRITE_TOOLS = Object.freeze(["Write", "Delete"]);
238224
+ var CURSOR_MATCHER = CURSOR_WRITE_TOOLS.join("|");
238225
+ function parseCursorStdin(raw) {
238226
+ if (raw == null || String(raw).trim() === "") return { ok: false, reason: "empty stdin" };
238227
+ let obj;
238228
+ try {
238229
+ obj = JSON.parse(String(raw));
238230
+ } catch (_) {
238231
+ return { ok: false, reason: "stdin is not JSON" };
238232
+ }
238233
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
238234
+ return { ok: false, reason: "stdin JSON is not an object" };
238235
+ }
238236
+ const toolName = typeof obj.tool_name === "string" ? obj.tool_name : null;
238237
+ if (!toolName) return { ok: false, reason: "missing tool_name" };
238238
+ const toolInput = obj.tool_input && typeof obj.tool_input === "object" && !Array.isArray(obj.tool_input) ? obj.tool_input : null;
238239
+ if (!toolInput) return { ok: false, reason: "missing tool_input" };
238240
+ return { ok: true, payload: obj, toolName, toolInput };
238241
+ }
238242
+ function toClaudeShape(toolName, toolInput) {
238243
+ const filePath = toolInput.file_path || toolInput.path || toolInput.target_file || null;
238244
+ const content = toolInput.content !== void 0 ? toolInput.content : toolInput.contents !== void 0 ? toolInput.contents : void 0;
238245
+ const out = { ...toolInput };
238246
+ if (filePath) out.file_path = filePath;
238247
+ if (content !== void 0) out.content = content;
238248
+ if (toolName === "Delete" && out.content === void 0) out.content = "";
238249
+ return { tool_name: toolName === "Delete" ? "Write" : "Write", tool_input: out };
238250
+ }
238251
+ function emit(permission, { userMessage, agentMessage } = {}, deps = {}) {
238252
+ const write = deps.stdout || ((s) => process.stdout.write(s));
238253
+ const body = { permission };
238254
+ if (permission === "deny") {
238255
+ body.user_message = userMessage || "CodeRifts blocked this change.";
238256
+ body.agent_message = agentMessage || "Blocked by CodeRifts. Do not retry the same change.";
238257
+ }
238258
+ write(`${JSON.stringify(body)}
238259
+ `);
238260
+ return permission === "deny" ? 2 : 0;
238261
+ }
238262
+ function denyMessages(whyText, detail = {}) {
238263
+ const decision = detail.decision || null;
238264
+ const severity = detail.severity || null;
238265
+ const head = decision ? `CodeRifts ${decision}: this change is not authorized.` : "CodeRifts blocked this change.";
238266
+ const body = String(whyText || "").trim();
238267
+ const userMessage = body || `${head}${detail.reason ? ` (${detail.reason})` : ""}`;
238268
+ 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.`;
238269
+ return { userMessage, agentMessage };
238270
+ }
238271
+ async function runCursorHook(o = {}) {
238272
+ const argv = Array.isArray(o.argv) ? o.argv : [];
238273
+ const deps = o.deps || {};
238274
+ if (argv.includes("--help") || argv.includes("-h")) {
238275
+ (deps.stdout || ((s) => process.stdout.write(s)))(USAGE);
238276
+ return 0;
238277
+ }
238278
+ const parsed = parseCursorStdin(o.stdin);
238279
+ if (!parsed.ok) {
238280
+ return emit("deny", {
238281
+ userMessage: `CodeRifts could not read the Cursor hook payload (${parsed.reason}) \u2014 refusing (fail-closed).`,
238282
+ agentMessage: "CodeRifts could not evaluate this action and refused it. Do not retry blindly; check the hook installation."
238283
+ }, deps);
238284
+ }
238285
+ if (!CURSOR_WRITE_TOOLS.includes(parsed.toolName)) {
238286
+ return emit("allow", {}, deps);
238287
+ }
238288
+ const claudeStdin = JSON.stringify(toClaudeShape(parsed.toolName, parsed.toolInput));
238289
+ const whyLines = [];
238290
+ let detail;
238291
+ try {
238292
+ detail = await runClaudeHook(
238293
+ { argv: [], env: o.env || process.env },
238294
+ {
238295
+ ...deps.inner || {},
238296
+ readStdin: () => claudeStdin,
238297
+ errLog: (m) => {
238298
+ whyLines.push(String(m));
238299
+ },
238300
+ env: o.env || process.env
238301
+ }
238302
+ );
238303
+ } catch (err) {
238304
+ return emit("deny", {
238305
+ userMessage: `CodeRifts hook error: ${err && err.message} \u2014 refusing (fail-closed).`,
238306
+ agentMessage: "The CodeRifts hook failed to evaluate this action. It was refused rather than allowed."
238307
+ }, deps);
238308
+ }
238309
+ const d = detail && typeof detail === "object" ? detail : {};
238310
+ const exitCode = Number.isFinite(d.exitCode) ? d.exitCode : 0;
238311
+ if (exitCode === 2) {
238312
+ return emit("deny", denyMessages(whyLines.join("\n"), d), deps);
238313
+ }
238314
+ return emit("allow", {}, deps);
238315
+ }
238316
+ module2.exports = {
238317
+ runCursorHook,
238318
+ parseCursorStdin,
238319
+ toClaudeShape,
238320
+ denyMessages,
238321
+ emit,
238322
+ CURSOR_WRITE_TOOLS,
238323
+ CURSOR_MATCHER,
238324
+ USAGE
238325
+ };
238326
+ }
238327
+ });
238328
+
237332
238329
  // corpus/vectors-mcp-fpfn.json
237333
238330
  var require_vectors_mcp_fpfn = __commonJS({
237334
238331
  "corpus/vectors-mcp-fpfn.json"(exports2, module2) {
@@ -239006,7 +240003,37 @@ program.command("registry-gate [dir]").description("Admit a directory of OpenAPI
239006
240003
  process.exitCode = code;
239007
240004
  process.exit(code);
239008
240005
  });
239009
- program.command("init [template]").description("Generate a .coderifts.yml from a policy template (startup, growth, fintech, public-api, microservices)").action(async (template) => {
240006
+ 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) => {
240007
+ const {
240008
+ runInitAgents,
240009
+ agentFlagsRequireAgents,
240010
+ AGENT_FLAGS_REQUIRE_AGENTS,
240011
+ AGENTS_NO_TEMPLATE
240012
+ } = require_init_agents();
240013
+ if (options.agents) {
240014
+ if (template) {
240015
+ console.error(AGENTS_NO_TEMPLATE);
240016
+ process.exitCode = 1;
240017
+ return;
240018
+ }
240019
+ const result = runInitAgents({
240020
+ hosts: options.hosts,
240021
+ hook: options.hook,
240022
+ workflow: options.workflow,
240023
+ dryRun: !!options.dryRun,
240024
+ check: !!options.check,
240025
+ out: options.out
240026
+ });
240027
+ if (result && typeof result.exitCode === "number") {
240028
+ process.exitCode = result.exitCode;
240029
+ }
240030
+ return;
240031
+ }
240032
+ if (agentFlagsRequireAgents(options)) {
240033
+ console.error(AGENT_FLAGS_REQUIRE_AGENTS);
240034
+ process.exitCode = 1;
240035
+ return;
240036
+ }
239010
240037
  const { init } = require_init2();
239011
240038
  await init(template);
239012
240039
  });
@@ -239103,11 +240130,31 @@ ${USAGE}
239103
240130
  process.exitCode = result.exitCode;
239104
240131
  }
239105
240132
  });
240133
+ program.command("cursor-hook").description(
240134
+ '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.'
240135
+ ).addHelpText("after", () => {
240136
+ const { USAGE } = require_cursor_hook();
240137
+ return `
240138
+ ${USAGE}
240139
+ `;
240140
+ }).action(async () => {
240141
+ const { runCursorHook } = require_cursor_hook();
240142
+ const fsMod = require("fs");
240143
+ let stdin = "";
240144
+ try {
240145
+ stdin = fsMod.readFileSync(0, "utf8");
240146
+ } catch {
240147
+ stdin = "";
240148
+ }
240149
+ const code = await runCursorHook({ argv: process.argv.slice(3), stdin });
240150
+ if (typeof code === "number") process.exitCode = code;
240151
+ });
239106
240152
  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) => {
240153
+ 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
240154
  const { install } = require_hook();
239109
240155
  install({
239110
240156
  claude: !!options.claude,
240157
+ cursor: !!options.cursor,
239111
240158
  global: !!options.global,
239112
240159
  log: options.log
239113
240160
  });