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/CHANGELOG.md +42 -0
- package/README.md +36 -0
- package/bin/coderifts.js +66 -4
- package/dist/cli.js +1726 -679
- package/package.json +1 -1
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.
|
|
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 (
|
|
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
|
|
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
|
});
|