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