coderifts 8.6.1 → 8.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 8.6.3 - 2026-09-12
6
+
7
+ js-yaml moves to 4.3.2 in the shipped dependency set. The generated strict workflow pins contract-gate v0.9.5, following the gate release. The package metadata points at the public site rather than the private app repository.
8
+
9
+
10
+ ## 8.6.2 - 2026-09-10
11
+
12
+ The generated strict workflow pins contract-gate v0.9.3 (f288c4b), following the gate release. The Atomic-V2 installer teaches ENFORCING_ATOMIC_V2 with the v2 factors required, not Strict.
13
+
14
+
5
15
  ## 8.6.1 - 2026-09-09
6
16
 
7
17
  The generated strict workflow pins contract-gate v0.9.2 (12a2673), following the gate release.
@@ -1,5 +1,5 @@
1
1
  {
2
- "$comment": "GENERATED by scripts/build.js. sha256 over src/**.js + bin/**.js (path + bytes). test/build-freshness.test.js recomputes it; a mismatch means dist/cli.js is stale.",
3
- "sha256": "d6989104c6fcc12b512fc6dee0dd50970cd88364d9e2bec09c6c50fb019c5f14",
4
- "file_count": 49
2
+ "$comment": "GENERATED by scripts/build.js. sha256 over src/** + bin/** (path + bytes), for every extension esbuild bundles — .js AND .json. scripts/lib/source-fingerprint.js is the single implementation; test/build-freshness.test.js recomputes it from there rather than from a copy. A mismatch means dist/cli.js is stale.",
3
+ "sha256": "161a9dee4c63f32c2a90dda17091ef50d667bcc084bdd25ae62b1cfe804eea68",
4
+ "file_count": 51
5
5
  }
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: "8.6.1",
3031
+ version: "8.6.3",
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",
@@ -3056,13 +3056,12 @@ var require_package = __commonJS({
3056
3056
  "swagger",
3057
3057
  "devtools"
3058
3058
  ],
3059
- repository: {
3060
- type: "git",
3061
- url: "git+https://github.com/coderifts/app.git",
3062
- directory: "packages/cli"
3063
- },
3059
+ repository: "https://coderifts.com",
3064
3060
  homepage: "https://coderifts.com",
3065
- bugs: "https://github.com/coderifts/app/issues",
3061
+ bugs: {
3062
+ url: "https://coderifts.com",
3063
+ email: "support@coderifts.com"
3064
+ },
3066
3065
  engines: {
3067
3066
  node: ">=20.0.0"
3068
3067
  },
@@ -3080,7 +3079,7 @@ var require_package = __commonJS({
3080
3079
  "cli-table3": "^0.6.4",
3081
3080
  commander: "^12.0.0",
3082
3081
  inquirer: "^8.2.6",
3083
- "js-yaml": "^4.3.1",
3082
+ "js-yaml": "^4.3.2",
3084
3083
  "openapi-diff": "^0.24.1",
3085
3084
  ora: "^5.4.1"
3086
3085
  },
@@ -11701,16 +11700,21 @@ var require_loader = __commonJS({
11701
11700
  state.result += _result;
11702
11701
  }
11703
11702
  }
11703
+ function chargeMergeWork(state) {
11704
+ state.totalMergeKeys++;
11705
+ if (state.maxTotalMergeKeys !== -1 && state.totalMergeKeys > state.maxTotalMergeKeys) {
11706
+ throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
11707
+ }
11708
+ }
11704
11709
  function mergeMappings(state, destination, source, overridableKeys) {
11705
11710
  if (!common.isObject(source)) {
11706
11711
  throwError(state, "cannot merge mappings; the provided source object is unacceptable");
11707
11712
  }
11713
+ chargeMergeWork(state);
11708
11714
  const sourceKeys = Object.keys(source);
11709
11715
  for (let index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
11710
11716
  const key = sourceKeys[index];
11711
- if (state.maxTotalMergeKeys !== -1 && ++state.totalMergeKeys > state.maxTotalMergeKeys) {
11712
- throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
11713
- }
11717
+ chargeMergeWork(state);
11714
11718
  if (!_hasOwnProperty.call(destination, key)) {
11715
11719
  setProperty(destination, key, source[key]);
11716
11720
  overridableKeys[key] = true;
@@ -11738,6 +11742,9 @@ var require_loader = __commonJS({
11738
11742
  }
11739
11743
  if (keyTag === "tag:yaml.org,2002:merge") {
11740
11744
  if (Array.isArray(valueNode)) {
11745
+ if (valueNode.length > 100) {
11746
+ throwError(state, "abnormal merge sequence size");
11747
+ }
11741
11748
  for (let index = 0, quantity = valueNode.length; index < quantity; index += 1) {
11742
11749
  mergeMappings(state, _result, valueNode[index], overridableKeys);
11743
11750
  }
@@ -163516,16 +163523,21 @@ var require_loader2 = __commonJS({
163516
163523
  state.result += _result;
163517
163524
  }
163518
163525
  }
163526
+ function chargeMergeWork(state) {
163527
+ state.totalMergeKeys++;
163528
+ if (state.maxTotalMergeKeys !== -1 && state.totalMergeKeys > state.maxTotalMergeKeys) {
163529
+ throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
163530
+ }
163531
+ }
163519
163532
  function mergeMappings(state, destination, source, overridableKeys) {
163520
163533
  if (!common.isObject(source)) {
163521
163534
  throwError(state, "cannot merge mappings; the provided source object is unacceptable");
163522
163535
  }
163536
+ chargeMergeWork(state);
163523
163537
  const sourceKeys = Object.keys(source);
163524
163538
  for (let index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
163525
163539
  const key = sourceKeys[index];
163526
- if (state.maxTotalMergeKeys !== -1 && ++state.totalMergeKeys > state.maxTotalMergeKeys) {
163527
- throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
163528
- }
163540
+ chargeMergeWork(state);
163529
163541
  if (!_hasOwnProperty.call(destination, key)) {
163530
163542
  setProperty(destination, key, source[key]);
163531
163543
  overridableKeys[key] = true;
@@ -163553,6 +163565,9 @@ var require_loader2 = __commonJS({
163553
163565
  }
163554
163566
  if (keyTag === "tag:yaml.org,2002:merge") {
163555
163567
  if (Array.isArray(valueNode)) {
163568
+ if (valueNode.length > 100) {
163569
+ throwError(state, "abnormal merge sequence size");
163570
+ }
163556
163571
  for (let index = 0, quantity = valueNode.length; index < quantity; index += 1) {
163557
163572
  mergeMappings(state, _result, valueNode[index], overridableKeys);
163558
163573
  }
@@ -231520,10 +231535,10 @@ var require_gate_pin = __commonJS({
231520
231535
  "src/generated/gate-pin.json"(exports2, module2) {
231521
231536
  module2.exports = {
231522
231537
  repo: "coderifts/contract-gate",
231523
- sha: "12a2673d9bf094a1a97cb1387348018332a5c33f",
231524
- tag: "v0.9.2",
231538
+ sha: "15c19a6373ad69f5a8aaee992b69bc38baf98f14",
231539
+ tag: "v0.9.5",
231525
231540
  resolved_from: "https://github.com/coderifts/contract-gate refs/tags/v0",
231526
- resolved_at: "2026-09-09T18:05:11Z",
231541
+ resolved_at: "2026-09-12T19:02:41Z",
231527
231542
  note: "STRICT template pins this full commit SHA (GitHub: only a full SHA is immutable). v0 is a tag we move on every release, so it is NOT a pin. Re-run scripts/generate-gate-pin.js on each gate release; Dependabot bumps adopters."
231528
231543
  };
231529
231544
  }
@@ -233296,7 +233311,29 @@ var require_agent_host_files_embedded = __commonJS({
233296
233311
  ".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',
233297
233312
  ".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'
233298
233313
  });
233299
- module2.exports = { AGENT_HOST_FILES, AGENT_HOST_PATHS };
233314
+ var AGENT_HOST_FILES_ATOMIC_V2 = Object.freeze({
233315
+ "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\nWhen `execution_action` is REQUEST_APPROVAL or STOP, surface the detected patterns and the blast radius, and propose the safer path \u2014 deprecate-then-remove, an additive change, or a new version \u2014 rather than shipping the break.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call `preflight_change_set` for: a documentation-only change (README, guides, comments) with no contract artifact content change; a static readiness score (a different capability, not a change-set decision); or to verify a receipt you already hold (that is `verify_receipt` \u2014 see the companion-tools rule).\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\nThe CodeRifts MCP server exposes exactly three tools \u2014 `preflight_change_set`, `verify_receipt`, `get_decision_details`. Do not invent or assume others.\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 \u2014 risk only, `may_execute` is always false \u2014 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`. This install is locked with `profile: ENFORCING_ATOMIC_V2` on withCodeRifts \u2014 the fail-closed conjunction plus the atomic execution factors below. Do not weaken it to `ENFORCING_STRICT`.\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\nThis install runs the atomic execution profile, so these are REQUIRED here, not optional: a `cr.exec.v2` execution grant, the state challenge it binds, an executor that consumes the `state_nonce` exactly once, an executor attestation over the committed state, and a readback of the resulting state. A step you skipped is not a step that passed \u2014 see `docs/grant-versions.md`.\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',
233316
+ "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\nWhen `execution_action` is REQUEST_APPROVAL or STOP, surface the detected patterns and the blast radius, and propose the safer path \u2014 deprecate-then-remove, an additive change, or a new version \u2014 rather than shipping the break.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call `preflight_change_set` for: a documentation-only change (README, guides, comments) with no contract artifact content change; a static readiness score (a different capability, not a change-set decision); or to verify a receipt you already hold (that is `verify_receipt` \u2014 see the companion-tools rule).\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\nThe CodeRifts MCP server exposes exactly three tools \u2014 `preflight_change_set`, `verify_receipt`, `get_decision_details`. Do not invent or assume others.\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 \u2014 risk only, `may_execute` is always false \u2014 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`. This install is locked with `profile: ENFORCING_ATOMIC_V2` on withCodeRifts \u2014 the fail-closed conjunction plus the atomic execution factors below. Do not weaken it to `ENFORCING_STRICT`.\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\nThis install runs the atomic execution profile, so these are REQUIRED here, not optional: a `cr.exec.v2` execution grant, the state challenge it binds, an executor that consumes the `state_nonce` exactly once, an executor attestation over the committed state, and a readback of the resulting state. A step you skipped is not a step that passed \u2014 see `docs/grant-versions.md`.\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',
233317
+ ".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\nWhen `execution_action` is REQUEST_APPROVAL or STOP, surface the detected patterns and the blast radius, and propose the safer path \u2014 deprecate-then-remove, an additive change, or a new version \u2014 rather than shipping the break.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call `preflight_change_set` for: a documentation-only change (README, guides, comments) with no contract artifact content change; a static readiness score (a different capability, not a change-set decision); or to verify a receipt you already hold (that is `verify_receipt` \u2014 see the companion-tools rule).\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\nThe CodeRifts MCP server exposes exactly three tools \u2014 `preflight_change_set`, `verify_receipt`, `get_decision_details`. Do not invent or assume others.\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 \u2014 risk only, `may_execute` is always false \u2014 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`. This install is locked with `profile: ENFORCING_ATOMIC_V2` on withCodeRifts \u2014 the fail-closed conjunction plus the atomic execution factors below. Do not weaken it to `ENFORCING_STRICT`.\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\nThis install runs the atomic execution profile, so these are REQUIRED here, not optional: a `cr.exec.v2` execution grant, the state challenge it binds, an executor that consumes the `state_nonce` exactly once, an executor attestation over the committed state, and a readback of the resulting state. A step you skipped is not a step that passed \u2014 see `docs/grant-versions.md`.\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',
233318
+ ".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\nWhen `execution_action` is REQUEST_APPROVAL or STOP, surface the detected patterns and the blast radius, and propose the safer path \u2014 deprecate-then-remove, an additive change, or a new version \u2014 rather than shipping the break.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call `preflight_change_set` for: a documentation-only change (README, guides, comments) with no contract artifact content change; a static readiness score (a different capability, not a change-set decision); or to verify a receipt you already hold (that is `verify_receipt` \u2014 see the companion-tools rule).\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\nThe CodeRifts MCP server exposes exactly three tools \u2014 `preflight_change_set`, `verify_receipt`, `get_decision_details`. Do not invent or assume others.\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 \u2014 risk only, `may_execute` is always false \u2014 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`. This install is locked with `profile: ENFORCING_ATOMIC_V2` on withCodeRifts \u2014 the fail-closed conjunction plus the atomic execution factors below. Do not weaken it to `ENFORCING_STRICT`.\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\nThis install runs the atomic execution profile, so these are REQUIRED here, not optional: a `cr.exec.v2` execution grant, the state challenge it binds, an executor that consumes the `state_nonce` exactly once, an executor attestation over the committed state, and a readback of the resulting state. A step you skipped is not a step that passed \u2014 see `docs/grant-versions.md`.\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',
233319
+ "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\\nWhen `execution_action` is REQUEST_APPROVAL or STOP, surface the detected patterns and the blast radius, and propose the safer path \u2014 deprecate-then-remove, an additive change, or a new version \u2014 rather than shipping the break.\\n\\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not \\"proceed with caution\\" without monitoring.\\n\\nDo not call `preflight_change_set` for: a documentation-only change (README, guides, comments) with no contract artifact content change; a static readiness score (a different capability, not a change-set decision); or to verify a receipt you already hold (that is `verify_receipt` \u2014 see the companion-tools rule).\\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\\nThe CodeRifts MCP server exposes exactly three tools \u2014 `preflight_change_set`, `verify_receipt`, `get_decision_details`. Do not invent or assume others.\\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 \u2014 risk only, `may_execute` is always false \u2014 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`. This install is locked with `profile: ENFORCING_ATOMIC_V2` on withCodeRifts \u2014 the fail-closed conjunction plus the atomic execution factors below. Do not weaken it to `ENFORCING_STRICT`.\\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\\nThis install runs the atomic execution profile, so these are REQUIRED here, not optional: a `cr.exec.v2` execution grant, the state challenge it binds, an executor that consumes the `state_nonce` exactly once, an executor attestation over the committed state, and a readback of the resulting state. A step you skipped is not a step that passed \u2014 see `docs/grant-versions.md`.\\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',
233320
+ "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\nWhen `execution_action` is REQUEST_APPROVAL or STOP, surface the detected patterns and the blast radius, and propose the safer path \u2014 deprecate-then-remove, an additive change, or a new version \u2014 rather than shipping the break.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call `preflight_change_set` for: a documentation-only change (README, guides, comments) with no contract artifact content change; a static readiness score (a different capability, not a change-set decision); or to verify a receipt you already hold (that is `verify_receipt` \u2014 see the companion-tools rule).\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\nThe CodeRifts MCP server exposes exactly three tools \u2014 `preflight_change_set`, `verify_receipt`, `get_decision_details`. Do not invent or assume others.\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 \u2014 risk only, `may_execute` is always false \u2014 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`. This install is locked with `profile: ENFORCING_ATOMIC_V2` on withCodeRifts \u2014 the fail-closed conjunction plus the atomic execution factors below. Do not weaken it to `ENFORCING_STRICT`.\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\nThis install runs the atomic execution profile, so these are REQUIRED here, not optional: a `cr.exec.v2` execution grant, the state challenge it binds, an executor that consumes the `state_nonce` exactly once, an executor attestation over the committed state, and a readback of the resulting state. A step you skipped is not a step that passed \u2014 see `docs/grant-versions.md`.\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',
233321
+ "skills/api-governance/SKILL.md": '---\nname: api-governance\ndescription: Before merging or shipping any API or tool-contract change: preflight the change set, then branch on execution_action.\n---\n\n# CodeRifts API Governance\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\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\nWhen `execution_action` is REQUEST_APPROVAL or STOP, surface the detected patterns and the blast radius, and propose the safer path \u2014 deprecate-then-remove, an additive change, or a new version \u2014 rather than shipping the break.\n\n`CONTINUE_WITH_MONITORING` requires a wired monitoring sink (`monitoringSinkWired`). It is not "proceed with caution" without monitoring.\n\nDo not call `preflight_change_set` for: a documentation-only change (README, guides, comments) with no contract artifact content change; a static readiness score (a different capability, not a change-set decision); or to verify a receipt you already hold (that is `verify_receipt` \u2014 see the companion-tools rule).\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\nThe CodeRifts MCP server exposes exactly three tools \u2014 `preflight_change_set`, `verify_receipt`, `get_decision_details`. Do not invent or assume others.\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 \u2014 risk only, `may_execute` is always false \u2014 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`. This install is locked with `profile: ENFORCING_ATOMIC_V2` on withCodeRifts \u2014 the fail-closed conjunction plus the atomic execution factors below. Do not weaken it to `ENFORCING_STRICT`.\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\nThis install runs the atomic execution profile, so these are REQUIRED here, not optional: a `cr.exec.v2` execution grant, the state challenge it binds, an executor that consumes the `state_nonce` exactly once, an executor attestation over the committed state, and a readback of the resulting state. A step you skipped is not a step that passed \u2014 see `docs/grant-versions.md`.\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',
233322
+ ".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',
233323
+ ".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'
233324
+ });
233325
+ var AGENT_HOST_PROFILES = Object.freeze({ strict: "ENFORCING_STRICT", atomic_v2: "ENFORCING_ATOMIC_V2" });
233326
+ var AGENT_HOST_FILES_BY_PROFILE = Object.freeze({
233327
+ "ENFORCING_STRICT": AGENT_HOST_FILES,
233328
+ "ENFORCING_ATOMIC_V2": AGENT_HOST_FILES_ATOMIC_V2
233329
+ });
233330
+ module2.exports = {
233331
+ AGENT_HOST_FILES,
233332
+ AGENT_HOST_FILES_ATOMIC_V2,
233333
+ AGENT_HOST_FILES_BY_PROFILE,
233334
+ AGENT_HOST_PROFILES,
233335
+ AGENT_HOST_PATHS
233336
+ };
233300
233337
  }
233301
233338
  });
233302
233339
 
@@ -235113,6 +235150,12 @@ var require_init_agents = __commonJS({
235113
235150
  var path = require("path");
235114
235151
  var chalk = require_source();
235115
235152
  var { writeAgentHostFiles, AGENT_HOST_FILES } = require_agent_setup();
235153
+ var {
235154
+ AGENT_HOST_FILES_BY_PROFILE,
235155
+ AGENT_HOST_PROFILES
235156
+ } = require_agent_host_files_embedded();
235157
+ var STRICT_PROFILE = AGENT_HOST_PROFILES.strict;
235158
+ var ATOMIC_V2_PROFILE = AGENT_HOST_PROFILES.atomic_v2;
235116
235159
  var { installClaudeHook, installCursorHook } = require_hook();
235117
235160
  var {
235118
235161
  MCP_HOSTS,
@@ -235446,11 +235489,16 @@ ${USAGE}`
235446
235489
  if (doExit) process.exit(1);
235447
235490
  return { exitCode: 1, code: fatal.code || "ERROR", message: fatal.message, items };
235448
235491
  }
235492
+ const ruleProfile = options.atomicV2 === true ? ATOMIC_V2_PROFILE : STRICT_PROFILE;
235493
+ const ruleFiles = AGENT_HOST_FILES_BY_PROFILE[ruleProfile];
235494
+ if (!ruleFiles) {
235495
+ throw new Error(`init --agents: no agent-host rule bundle for profile ${ruleProfile}`);
235496
+ }
235449
235497
  const rulePaths = rulePathsForHosts(hosts, allRules);
235450
235498
  const ruleSummary = writeAgentHostFiles({
235451
235499
  outDir,
235452
235500
  force: false,
235453
- files: AGENT_HOST_FILES,
235501
+ files: ruleFiles,
235454
235502
  paths: rulePaths,
235455
235503
  exists: fsDeps.exists,
235456
235504
  writeFile: fsDeps.writeFile,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderifts",
3
- "version": "8.6.1",
3
+ "version": "8.6.3",
4
4
  "description": "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
5
5
  "author": "CodeRifts <hello@coderifts.com>",
6
6
  "license": "MIT",
@@ -28,13 +28,12 @@
28
28
  "swagger",
29
29
  "devtools"
30
30
  ],
31
- "repository": {
32
- "type": "git",
33
- "url": "git+https://github.com/coderifts/app.git",
34
- "directory": "packages/cli"
35
- },
31
+ "repository": "https://coderifts.com",
36
32
  "homepage": "https://coderifts.com",
37
- "bugs": "https://github.com/coderifts/app/issues",
33
+ "bugs": {
34
+ "url": "https://coderifts.com",
35
+ "email": "support@coderifts.com"
36
+ },
38
37
  "engines": {
39
38
  "node": ">=20.0.0"
40
39
  },
@@ -52,7 +51,7 @@
52
51
  "cli-table3": "^0.6.4",
53
52
  "commander": "^12.0.0",
54
53
  "inquirer": "^8.2.6",
55
- "js-yaml": "^4.3.1",
54
+ "js-yaml": "^4.3.2",
56
55
  "openapi-diff": "^0.24.1",
57
56
  "ora": "^5.4.1"
58
57
  },
package/scripts/build.js CHANGED
@@ -11,6 +11,7 @@ const path = require('path');
11
11
  const fs = require('fs');
12
12
  const crypto = require('crypto');
13
13
  const esbuild = require('esbuild');
14
+ const { sourceFingerprint } = require('./lib/source-fingerprint');
14
15
 
15
16
  const stub = path.join(__dirname, '..', 'src', 'logger-stub.js');
16
17
  const appLogger = path.resolve(__dirname, '..', '..', '..', 'src', 'logger.js');
@@ -52,33 +53,16 @@ esbuild.build({
52
53
  // CONTENT-ADDRESSED, not timestamps: mtimes move on checkout, rebase and copy without the bytes
53
54
  // changing, and a freshness check that cries wolf gets deleted.
54
55
  const root = path.join(__dirname, '..');
55
- const files = [];
56
- const walk = (dir) => {
57
- if (!fs.existsSync(dir)) return;
58
- for (const e of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
59
- if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
60
- const full = path.join(dir, e.name);
61
- if (e.isDirectory()) walk(full);
62
- else if (e.name.endsWith('.js')) files.push(full);
63
- }
64
- };
65
- walk(path.join(root, 'src'));
66
- walk(path.join(root, 'bin'));
67
- const h = crypto.createHash('sha256');
68
- for (const f of files.sort()) {
69
- h.update(path.relative(root, f));
70
- h.update('\0');
71
- h.update(fs.readFileSync(f));
72
- h.update('\0');
73
- }
74
- const sha256 = h.digest('hex');
56
+ const { sha256, file_count } = sourceFingerprint(root);
75
57
  fs.writeFileSync(path.join(root, 'dist', '.build-source-sha'), `${JSON.stringify({
76
- $comment: 'GENERATED by scripts/build.js. sha256 over src/**.js + bin/**.js (path + bytes). '
77
- + 'test/build-freshness.test.js recomputes it; a mismatch means dist/cli.js is stale.',
58
+ $comment: 'GENERATED by scripts/build.js. sha256 over src/** + bin/** (path + bytes), for '
59
+ + 'every extension esbuild bundles .js AND .json. scripts/lib/source-fingerprint.js is '
60
+ + 'the single implementation; test/build-freshness.test.js recomputes it from there rather '
61
+ + 'than from a copy. A mismatch means dist/cli.js is stale.',
78
62
  sha256,
79
- file_count: files.length,
63
+ file_count,
80
64
  }, null, 2)}\n`);
81
- process.stdout.write(`build: source fingerprint ${sha256.slice(0, 12)}… over ${files.length} files\n`);
65
+ process.stdout.write(`build: source fingerprint ${sha256.slice(0, 12)}… over ${file_count} files\n`);
82
66
  }).catch((err) => {
83
67
  console.error(err);
84
68
  process.exit(1);
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ * The source fingerprint dist/cli.js is stamped with, and the one test/build-freshness.test.js
5
+ * recomputes. ONE implementation, required by both.
6
+ *
7
+ * WHY IT LIVES HERE. The walk used to exist twice — once in scripts/build.js and once inside the
8
+ * test, whose comment said the copy was "kept in step by the assertions below". No assertion
9
+ * compared them, so "kept in step" was a hope. MEASURED 2026-09-12: it had already failed in one
10
+ * direction and was about to fail in the other. A freshness gate whose recomputation is a COPY of
11
+ * the thing it checks can drift from it silently, which is the one failure a freshness gate must
12
+ * not have.
13
+ *
14
+ * .json COUNTS. esbuild inlines src/generated/gate-pin.json into dist/cli.js, so it is shipped
15
+ * runtime rather than configuration beside it. With a .js-only walk the pin moved from v0.9.3 to
16
+ * v0.9.4 while the marker stayed green and dist kept naming v0.9.3 — the marker's own comment
17
+ * promises that a mismatch means dist is stale, and for a bundled JSON it did not deliver.
18
+ */
19
+
20
+ const path = require('path');
21
+ const fs = require('fs');
22
+ const crypto = require('crypto');
23
+
24
+ /** Extensions that end up INSIDE the bundle. Anything here changes the shipped runtime. */
25
+ const BUNDLED_EXT = Object.freeze(['.js', '.json']);
26
+
27
+ /**
28
+ * sha256 over src/** + bin/** (path + bytes), content-addressed — never mtime: mtimes move on
29
+ * checkout, rebase and copy with no byte change, and a freshness check that cries wolf gets
30
+ * deleted.
31
+ * @param {string} root package root
32
+ */
33
+ function sourceFingerprint(root) {
34
+ const files = [];
35
+ const walk = (dir) => {
36
+ if (!fs.existsSync(dir)) return;
37
+ for (const e of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
38
+ if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
39
+ const full = path.join(dir, e.name);
40
+ if (e.isDirectory()) walk(full);
41
+ else if (BUNDLED_EXT.some((x) => e.name.endsWith(x))) files.push(full);
42
+ }
43
+ };
44
+ walk(path.join(root, 'src'));
45
+ walk(path.join(root, 'bin'));
46
+ const h = crypto.createHash('sha256');
47
+ for (const f of files.sort()) {
48
+ h.update(path.relative(root, f));
49
+ h.update('\0');
50
+ h.update(fs.readFileSync(f));
51
+ h.update('\0');
52
+ }
53
+ return { sha256: h.digest('hex'), file_count: files.length };
54
+ }
55
+
56
+ module.exports = { sourceFingerprint, BUNDLED_EXT };