coderifts 6.0.1 → 6.1.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cli.js +136 -16
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.1.0
4
+
5
+ ### Added
6
+
7
+ - **enforce --check now reads GitHub rulesets.** The evaluator existed with zero production call sites: a ruleset-governed repository reported required_check NOT_VERIFIED while its merges were gated. Rulesets are readable where classic protection is not (200 vs 401), so required_check can now reach VERIFIED on an unreadable classic response.
8
+
9
+ ## 6.0.2
10
+
11
+ ### Fixed
12
+
13
+ - **init --agents wrote 8 of 9 embedded files.** The skill file joined the single source on 2026-08-25 but was never added to EXTRA_RULES_ON_ALL, a second hand-maintained list. Every release since shipped an installer that under-writes, and agent-setup --check was correctly reporting DRIFT the whole time.
14
+ - **The strict workflow pins actions/checkout too.** It runs first and fetches the code every later step reads; the least pinned step in the strict chain was the most load-bearing.
15
+
3
16
  ## 6.0.1
4
17
 
5
18
  ### Changed
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: "6.0.1",
3031
+ version: "6.1.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",
@@ -227483,6 +227483,25 @@ var require_gate_pin = __commonJS({
227483
227483
  }
227484
227484
  });
227485
227485
 
227486
+ // src/generated/action-pins.json
227487
+ var require_action_pins = __commonJS({
227488
+ "src/generated/action-pins.json"(exports2, module2) {
227489
+ module2.exports = {
227490
+ actions: [
227491
+ {
227492
+ action: "actions/checkout",
227493
+ sha: "11d5960a326750d5838078e36cf38b85af677262",
227494
+ tag: "v4",
227495
+ exact_tag: "v4.4.0",
227496
+ resolved_from: "https://github.com/actions/checkout refs/tags/v4",
227497
+ resolved_at: "2026-08-27T13:27:52Z"
227498
+ }
227499
+ ],
227500
+ note: "STRICT template pins these third-party actions to full commit SHAs (only a full SHA is immutable). The DEFAULT template deliberately stays on moving tags. A SHA pin does not receive upstream security fixes until someone bumps it \u2014 Dependabot raises that PR, and a pin nobody bumps is worse than a tag because it rots silently. Re-run scripts/generate-action-pins.js."
227501
+ };
227502
+ }
227503
+ });
227504
+
227486
227505
  // src/contract-gate-workflow.js
227487
227506
  var require_contract_gate_workflow = __commonJS({
227488
227507
  "src/contract-gate-workflow.js"(exports2, module2) {
@@ -227492,6 +227511,17 @@ var require_contract_gate_workflow = __commonJS({
227492
227511
  var WORKFLOW_REL = ".github/workflows/coderifts.yml";
227493
227512
  var GATE_ACTION = "coderifts/contract-gate@v0";
227494
227513
  var GATE_PIN = require_gate_pin();
227514
+ var ACTION_PINS = require_action_pins();
227515
+ function pinnedActionRef(action) {
227516
+ const entry = (ACTION_PINS.actions || []).find((a) => a && a.action === action);
227517
+ if (!entry || !/^[0-9a-f]{40}$/.test(String(entry.sha || ""))) {
227518
+ throw new Error(
227519
+ `contract-gate-workflow: no usable SHA pin for ${action} in src/generated/action-pins.json \u2014 run scripts/generate-action-pins.js. Refusing to emit an unpinned STRICT workflow.`
227520
+ );
227521
+ }
227522
+ return `${action}@${entry.sha} # ${entry.exact_tag || entry.tag}`;
227523
+ }
227524
+ var CHECKOUT_STRICT = pinnedActionRef("actions/checkout");
227495
227525
  if (typeof GATE_PIN.sha !== "string" || !/^[0-9a-f]{40}$/.test(GATE_PIN.sha)) {
227496
227526
  throw new Error(
227497
227527
  "contract-gate-workflow: generated/gate-pin.json carries no valid 40-hex sha \u2014 re-run scripts/generate-gate-pin.js. Refusing to emit an unpinned STRICT workflow."
@@ -227526,6 +227556,10 @@ jobs:
227526
227556
  contract-gate:
227527
227557
  runs-on: ubuntu-latest
227528
227558
  steps:
227559
+ # actions/checkout stays on the MOVING TAG @v4 here, deliberately \u2014 the same trade this
227560
+ # template makes for the gate itself. You get upstream fixes without editing this file, and
227561
+ # you accept that the bytes can change under you. \`coderifts init --agents --strict\` pins a
227562
+ # full commit SHA for both. Do not "fix" this to a SHA and do not "fix" strict back to a tag.
227529
227563
  - uses: actions/checkout@v4
227530
227564
  with:
227531
227565
  # REQUIRED: the gate derives the change set from git diff base...head.
@@ -227582,7 +227616,14 @@ jobs:
227582
227616
  contract-gate:
227583
227617
  runs-on: ubuntu-latest
227584
227618
  steps:
227585
- - uses: actions/checkout@v4
227619
+ # PINNED, like the gate below it. \`@v4\` is a tag GitHub moves on every release of the
227620
+ # action, so it is not a pin \u2014 and checkout runs FIRST, fetching the code every later step
227621
+ # reads. A strict chain whose first step can change under it is not strict.
227622
+ # MAINTENANCE, stated because a pin has a cost: a SHA-pinned action receives no upstream
227623
+ # fixes, security included, until someone bumps it. Dependabot understands this form and
227624
+ # raises that PR. WITHOUT Dependabot the obligation is yours, and a pin nobody bumps is worse
227625
+ # than a tag, because a tag rots visibly and a pin rots silently.
227626
+ - uses: ${CHECKOUT_STRICT}
227586
227627
  with:
227587
227628
  # REQUIRED: the gate derives the change set from git diff base...head.
227588
227629
  fetch-depth: 0
@@ -227666,6 +227707,9 @@ jobs:
227666
227707
  GATE_ACTION,
227667
227708
  GATE_ACTION_STRICT,
227668
227709
  GATE_PIN,
227710
+ ACTION_PINS,
227711
+ CHECKOUT_STRICT,
227712
+ pinnedActionRef,
227669
227713
  ENFORCEMENT_DOC,
227670
227714
  writeContractGateWorkflow,
227671
227715
  workflowPresent
@@ -227722,7 +227766,8 @@ var require_init_agents = __commonJS({
227722
227766
  var EXTRA_RULES_ON_ALL = Object.freeze([
227723
227767
  ".github/copilot-instructions.md",
227724
227768
  "coderifts-langgraph-policy.js",
227725
- "openai-agent-instructions.md"
227769
+ "openai-agent-instructions.md",
227770
+ "skills/api-governance/SKILL.md"
227726
227771
  ]);
227727
227772
  var NEXT_MANUAL_STEP_LINES = Object.freeze([
227728
227773
  " Next manual step",
@@ -254151,7 +254196,9 @@ var require_github_enforcement = __commonJS({
254151
254196
  });
254152
254197
  function verifiedRequiresBinding(layer2) {
254153
254198
  if (!layer2 || layer2.status !== STATUS.VERIFIED) return true;
254154
- if (layer2.id === "required_check") return layer2.reason_code === REASON.ISSUER_BOUND;
254199
+ if (layer2.id === "required_check") {
254200
+ return layer2.reason_code === REASON.ISSUER_BOUND || layer2.reason_code === REASON.RULESET_ISSUER_BOUND;
254201
+ }
254155
254202
  if (layer2.id === "workflow_contract_gate" || layer2.id === "workflow_deploy_gate") {
254156
254203
  return layer2.reason_code === REASON.ACTIVE_JOB;
254157
254204
  }
@@ -254603,15 +254650,48 @@ var require_github_enforcement = __commonJS({
254603
254650
  environmentRead,
254604
254651
  environmentName,
254605
254652
  local,
254606
- auth
254653
+ auth,
254654
+ rulesetsRead = null,
254655
+ branch = null
254607
254656
  } = {}) {
254608
254657
  const layers = [];
254609
254658
  const localFiles = local || { contractGateFiles: [], deployGateFiles: [] };
254659
+ const rulesetVerdict = evaluateRuleset(rulesetsRead, branch);
254660
+ const rulesetUpgrade = (classicLayerFields) => {
254661
+ if (rulesetVerdict.status !== STATUS.VERIFIED) return null;
254662
+ return {
254663
+ status: STATUS.VERIFIED,
254664
+ reason: `${rulesetVerdict.reason} Classic branch protection did not establish this: ${classicLayerFields.reason}`,
254665
+ reason_code: rulesetVerdict.reason_code,
254666
+ endpoint: "GET /repos/{owner}/{repo}/rulesets",
254667
+ app_id: rulesetVerdict.app_id,
254668
+ ruleset_names: rulesetVerdict.ruleset_names,
254669
+ surfaces_inspected: ["classic_branch_protection", "rulesets"]
254670
+ };
254671
+ };
254672
+ const pushRequiredCheck = (classicFields) => {
254673
+ const up = rulesetUpgrade(classicFields);
254674
+ layers.push(layer("required_check", up ? up.status : classicFields.status, {
254675
+ ...up || classicFields,
254676
+ ruleset_status: rulesetVerdict.status,
254677
+ ruleset_reason_code: rulesetVerdict.reason_code,
254678
+ surfaces_inspected: [
254679
+ "classic_branch_protection",
254680
+ rulesetsRead == null ? "rulesets:unread" : "rulesets"
254681
+ ]
254682
+ }));
254683
+ };
254610
254684
  const noAuth = !auth || auth.source === "none" || !auth.ok;
254611
254685
  const prot = protectionRead || null;
254612
254686
  if (noAuth && !prot) {
254613
254687
  const why = auth && auth.reason || NO_TOKEN_INSTRUCTION;
254614
- for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
254688
+ pushRequiredCheck({
254689
+ status: STATUS.UNVERIFIABLE,
254690
+ reason: why,
254691
+ endpoint: null,
254692
+ missing_scope: APP_INSTALLATION.missing_scope
254693
+ });
254694
+ for (const id of ["enforce_admins", "strict_status_checks"]) {
254615
254695
  layers.push(layer(id, STATUS.UNVERIFIABLE, {
254616
254696
  reason: why,
254617
254697
  endpoint: null,
@@ -254620,13 +254700,21 @@ var require_github_enforcement = __commonJS({
254620
254700
  }
254621
254701
  } else if (!prot) {
254622
254702
  const why = "branch protection was not queried";
254623
- for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
254703
+ pushRequiredCheck({ status: STATUS.UNVERIFIABLE, reason: why, endpoint: null });
254704
+ for (const id of ["enforce_admins", "strict_status_checks"]) {
254624
254705
  layers.push(layer(id, STATUS.UNVERIFIABLE, { reason: why, endpoint: null }));
254625
254706
  }
254626
254707
  } else if (prot.status === 403) {
254627
254708
  const why = "GitHub HTTP 403 \u2014 missing scope administration:read (branch protection). Grant Administration: Read on the token, or use a repo-admin token.";
254628
254709
  const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
254629
- for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
254710
+ pushRequiredCheck({
254711
+ status: STATUS.UNVERIFIABLE,
254712
+ reason: why,
254713
+ endpoint,
254714
+ missing_scope: "administration:read",
254715
+ http_status: 403
254716
+ });
254717
+ for (const id of ["enforce_admins", "strict_status_checks"]) {
254630
254718
  layers.push(layer(id, STATUS.UNVERIFIABLE, {
254631
254719
  reason: why,
254632
254720
  endpoint,
@@ -254636,11 +254724,12 @@ var require_github_enforcement = __commonJS({
254636
254724
  }
254637
254725
  } else if (prot.status === 404) {
254638
254726
  const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
254639
- layers.push(layer("required_check", STATUS.NOT_VERIFIED, {
254727
+ pushRequiredCheck({
254728
+ status: STATUS.NOT_VERIFIED,
254640
254729
  reason: `classic branch protection is absent \u2014 required check "${CHECK_NAME}" is not required`,
254641
254730
  endpoint,
254642
254731
  http_status: 404
254643
- }));
254732
+ });
254644
254733
  layers.push(layer("enforce_admins", STATUS.NOT_VERIFIED, {
254645
254734
  reason: "classic branch protection is absent \u2014 administrators are unrestricted",
254646
254735
  endpoint,
@@ -254654,7 +254743,13 @@ var require_github_enforcement = __commonJS({
254654
254743
  } else if (prot.status >= 400 || prot.status === 0) {
254655
254744
  const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
254656
254745
  const why = `GitHub HTTP ${prot.status || 0} reading branch protection \u2014 cannot verify`;
254657
- for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
254746
+ pushRequiredCheck({
254747
+ status: STATUS.UNVERIFIABLE,
254748
+ reason: redactSecrets(why),
254749
+ endpoint,
254750
+ http_status: prot.status
254751
+ });
254752
+ for (const id of ["enforce_admins", "strict_status_checks"]) {
254658
254753
  layers.push(layer(id, STATUS.UNVERIFIABLE, {
254659
254754
  reason: redactSecrets(why),
254660
254755
  endpoint,
@@ -254665,14 +254760,15 @@ var require_github_enforcement = __commonJS({
254665
254760
  const protection = prot.body && typeof prot.body === "object" ? prot.body : {};
254666
254761
  const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
254667
254762
  const required = evaluateRequiredCheck(protection);
254668
- layers.push(layer("required_check", required.status, {
254763
+ pushRequiredCheck({
254764
+ status: required.status,
254669
254765
  reason: required.reason,
254670
254766
  reason_code: required.reason_code,
254671
254767
  endpoint,
254672
254768
  required_contexts: required.required_contexts,
254673
254769
  app_id: required.app_id,
254674
254770
  ...required.expected_app_id != null ? { expected_app_id: required.expected_app_id } : {}
254675
- }));
254771
+ });
254676
254772
  const admins = enforceAdminsEnabled(protection);
254677
254773
  layers.push(layer("enforce_admins", admins ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
254678
254774
  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",
@@ -254840,11 +254936,27 @@ var require_github_enforcement = __commonJS({
254840
254936
  const protPath = resolvedBranch ? `repos/${owner}/${repo}/branches/${encodeURIComponent(resolvedBranch)}/protection` : protectionPath;
254841
254937
  const protectionRead = client && protPath ? await client.get(protPath) : null;
254842
254938
  const environmentRead = client && envPath ? await client.get(envPath) : null;
254939
+ let rulesetsRead = null;
254940
+ if (client) {
254941
+ const list = await client.get(`repos/${owner}/${repo}/rulesets`);
254942
+ if (list && list.status === 200 && Array.isArray(list.body)) {
254943
+ const detailed = [];
254944
+ let anyDetailFailed = false;
254945
+ for (const rs of list.body) {
254946
+ if (!rs || rs.id == null) continue;
254947
+ const one = await client.get(`repos/${owner}/${repo}/rulesets/${rs.id}`);
254948
+ if (one && one.status === 200 && one.body) detailed.push(one.body);
254949
+ else anyDetailFailed = true;
254950
+ }
254951
+ rulesetsRead = anyDetailFailed ? null : detailed;
254952
+ }
254953
+ }
254843
254954
  return {
254844
254955
  branch: resolvedBranch,
254845
254956
  repoRead,
254846
254957
  protectionRead,
254847
- environmentRead
254958
+ environmentRead,
254959
+ rulesetsRead
254848
254960
  };
254849
254961
  }
254850
254962
  module2.exports = {
@@ -255808,6 +255920,7 @@ var require_enforce_check = __commonJS({
255808
255920
  let branch = options.branch ? String(options.branch).trim() : null;
255809
255921
  let protectionRead = null;
255810
255922
  let environmentRead = null;
255923
+ let rulesetsRead = null;
255811
255924
  if (auth.ok) {
255812
255925
  const client = deps.githubClient || createGitHubHttpClient({
255813
255926
  token: auth.token,
@@ -255823,13 +255936,18 @@ var require_enforce_check = __commonJS({
255823
255936
  branch = queried.branch || branch;
255824
255937
  protectionRead = queried.protectionRead;
255825
255938
  environmentRead = queried.environmentRead;
255939
+ rulesetsRead = queried.rulesetsRead;
255826
255940
  }
255827
255941
  const evaluated = evaluateLayers({
255828
255942
  protectionRead,
255829
255943
  environmentRead,
255830
255944
  environmentName,
255831
255945
  local,
255832
- auth
255946
+ auth,
255947
+ // roadmap 1052a — the ruleset surface, which reads without a credential and can establish
255948
+ // required_check where classic branch protection is unreadable.
255949
+ rulesetsRead,
255950
+ branch
255833
255951
  });
255834
255952
  const report = {
255835
255953
  command: "enforce --check",
@@ -255849,7 +255967,9 @@ var require_enforce_check = __commonJS({
255849
255967
  // response or to our grading of it.
255850
255968
  responses: {
255851
255969
  protection: responseDigest(protectionRead),
255852
- environment: responseDigest(environmentRead)
255970
+ environment: responseDigest(environmentRead),
255971
+ // null here means NOT READ, which is different from an empty ruleset list.
255972
+ rulesets: rulesetsRead == null ? null : { count: rulesetsRead.length }
255853
255973
  },
255854
255974
  ...evaluated
255855
255975
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderifts",
3
- "version": "6.0.1",
3
+ "version": "6.1.0",
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",