@catladder/cli 0.0.0-main-e76cf00d → 0.0.0-main-bd7c0346

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 (24) hide show
  1. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithub.js +31 -9
  2. package/dist/apps/cli/src/apps/cli/commands/project/doctor/checkGithub.js.map +1 -1
  3. package/dist/apps/cli/src/apps/cli/commands/project/githubMergeGating.d.ts +70 -0
  4. package/dist/apps/cli/src/apps/cli/commands/project/githubMergeGating.js +71 -0
  5. package/dist/apps/cli/src/apps/cli/commands/project/githubMergeGating.js.map +1 -0
  6. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupGithub.d.ts +9 -7
  7. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupGithub.js +77 -31
  8. package/dist/apps/cli/src/apps/cli/commands/project/setup/setupGithub.js.map +1 -1
  9. package/dist/apps/cli/src/release/changesetsReleaseJob.js +19 -1
  10. package/dist/apps/cli/src/release/changesetsReleaseJob.js.map +1 -1
  11. package/dist/apps/cli/src/utils/github.d.ts +1 -1
  12. package/dist/apps/cli/src/utils/github.js.map +1 -1
  13. package/dist/bundles/catci/index.js +1 -1
  14. package/dist/bundles/cli/index.js +186 -40
  15. package/dist/bundles/skills/catladder-migrate-ci-backend/SKILL.md +11 -7
  16. package/dist/skills/catladder-migrate-ci-backend/SKILL.md +11 -7
  17. package/dist/tsconfig.tsbuildinfo +1 -1
  18. package/package.json +1 -1
  19. package/src/apps/cli/commands/project/__tests__/githubMergeGating.test.ts +112 -0
  20. package/src/apps/cli/commands/project/doctor/checkGithub.ts +44 -8
  21. package/src/apps/cli/commands/project/githubMergeGating.ts +90 -0
  22. package/src/apps/cli/commands/project/setup/setupGithub.ts +105 -26
  23. package/src/release/changesetsReleaseJob.ts +19 -1
  24. package/src/utils/github.ts +1 -1
@@ -619,6 +619,7 @@ const pipeline_1 = __nccwpck_require__(53695);
619
619
  const commandSecretsSyncGithub_1 = __nccwpck_require__(27565);
620
620
  const concurrency_1 = __nccwpck_require__(59723);
621
621
  const github_1 = __nccwpck_require__(41353);
622
+ const githubMergeGating_1 = __nccwpck_require__(14966);
622
623
  /** marker for repo-level (environment-less) targets, see collectSecretTargets */
623
624
  const REPO_LEVEL = "";
624
625
  /**
@@ -722,10 +723,13 @@ const checkGithub = async (report, config) => {
722
723
  exports.checkGithub = checkGithub;
723
724
  /**
724
725
  * verifies the merge gating `project setup` configures: auto-merge
725
- * enabled and the generated `catladder ✅` aggregate check required on
726
- * the default branch. Without it a PR is mergeable while its checks
727
- * are still running — gitlab ships this behavior built-in, github
728
- * silently does not.
726
+ * enabled and the `catladder merge gating` ruleset requiring the
727
+ * generated `catladder ✅` aggregate check on the default branch (with
728
+ * the bypass that lets the release job push). Without the check a PR
729
+ * is mergeable while its checks are still running — gitlab ships this
730
+ * behavior built-in, github silently does not. The check required via
731
+ * classic branch protection instead is flagged: that setup rejects the
732
+ * release job's own push (GH006).
729
733
  */
730
734
  const checkGithubMergeGating = async (report, repo) => {
731
735
  var _a, _b;
@@ -744,17 +748,35 @@ const checkGithubMergeGating = async (report, repo) => {
744
748
  report.fail("auto-merge disabled — the merge-when-pipeline-succeeds button cannot appear", "run: catladder project setup");
745
749
  }
746
750
  const branch = repoInfo.default_branch;
751
+ let rulesets;
752
+ try {
753
+ rulesets = await (0, github_1.ghApiJson)("GET", `repos/${repo}/rulesets?per_page=100`);
754
+ }
755
+ catch (e) {
756
+ report.warn(`could not list rulesets of ${repo} (${e.message})`);
757
+ return;
758
+ }
759
+ const named = rulesets.find((r) => r.name === githubMergeGating_1.MERGE_GATING_RULESET_NAME);
760
+ const ruleset = named
761
+ ? await (0, github_1.ghApiJson)("GET", `repos/${repo}/rulesets/${named.id}`).catch(() => null)
762
+ : null;
763
+ if (ruleset && (0, githubMergeGating_1.rulesetProvidesMergeGating)(ruleset)) {
764
+ report.ok(`ruleset '${githubMergeGating_1.MERGE_GATING_RULESET_NAME}': '${pipeline_1.AGGREGATE_CHECK_JOB_NAME}' required on ${branch}, release job can push`);
765
+ }
766
+ else if (ruleset) {
767
+ report.fail(`ruleset '${githubMergeGating_1.MERGE_GATING_RULESET_NAME}' exists but is incomplete (inactive, check not required, or release-job bypass missing)`, "run: catladder project setup");
768
+ }
769
+ else {
770
+ report.fail(`no '${githubMergeGating_1.MERGE_GATING_RULESET_NAME}' ruleset on ${repo} — PRs can merge while the pipeline is red or still running`, "run: catladder project setup");
771
+ }
747
772
  const protection = await (0, github_1.ghApiJson)("GET", `repos/${repo}/branches/${branch}/protection`).catch(() => null);
748
773
  const checks = (_b = (_a = protection === null || protection === void 0 ? void 0 : protection.required_status_checks) === null || _a === void 0 ? void 0 : _a.checks) !== null && _b !== void 0 ? _b : [];
749
774
  if (checks.some((c) => c.context === pipeline_1.AGGREGATE_CHECK_JOB_NAME)) {
750
- report.ok(`'${pipeline_1.AGGREGATE_CHECK_JOB_NAME}' required on ${branch}`);
751
- }
752
- else {
753
- report.fail(`'${pipeline_1.AGGREGATE_CHECK_JOB_NAME}' is not a required check on ${branch} — PRs can merge while the pipeline is red or still running`, "run: catladder project setup");
775
+ report.fail(`'${pipeline_1.AGGREGATE_CHECK_JOB_NAME}' is still required via classic branch protection on ${branch} — that rejects the release job's push (GH006)`, "run: catladder project setup");
754
776
  }
755
777
  const stale = checks.filter((c) => c.context !== pipeline_1.AGGREGATE_CHECK_JOB_NAME);
756
778
  if (stale.length > 0) {
757
- report.warn(`${branch} requires ${stale.length} additional named check(s) (${stale
779
+ report.warn(`${branch} requires ${stale.length} additional named check(s) via classic branch protection (${stale
758
780
  .map((c) => c.context)
759
781
  .join(", ")}) — job names change with components; prefer only '${pipeline_1.AGGREGATE_CHECK_JOB_NAME}'`);
760
782
  }
@@ -950,6 +972,84 @@ exports.doctorProject = doctorProject;
950
972
 
951
973
  /***/ }),
952
974
 
975
+ /***/ 14966:
976
+ /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
977
+
978
+ "use strict";
979
+
980
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
981
+ exports.rulesetProvidesMergeGating = exports.desiredMergeGatingRuleset = exports.MERGE_GATING_RULESET_NAME = exports.GITHUB_ACTIONS_APP_ID = void 0;
982
+ const pipeline_1 = __nccwpck_require__(53695);
983
+ /**
984
+ * github merge gating — shared between `project setup` (writes it) and
985
+ * `project doctor` (verifies it).
986
+ *
987
+ * The gating lives in a repository RULESET, not classic branch
988
+ * protection: only rulesets can carry bypass actors, and the release
989
+ * job needs one. It pushes the release commit and tag straight to the
990
+ * default branch with the workflow token — under classic protection
991
+ * the required `catladder ✅` check rejects that push (GH006), because
992
+ * the fresh release commit cannot have a passing check yet. The bypass
993
+ * covers the GitHub Actions app, i.e. pushes made by workflows, while
994
+ * PRs and humans stay gated. (Gitlab's counterpart is the
995
+ * maintainer-role project access token, which may push to protected
996
+ * branches by default — the same trade-off, made by the platform.)
997
+ */
998
+ /** the app id of GitHub Actions — the actor workflow tokens act as */
999
+ exports.GITHUB_ACTIONS_APP_ID = 15368;
1000
+ exports.MERGE_GATING_RULESET_NAME = "catladder merge gating";
1001
+ /** the ruleset `project setup` creates (and restores on drift) */
1002
+ const desiredMergeGatingRuleset = () => ({
1003
+ name: exports.MERGE_GATING_RULESET_NAME,
1004
+ target: "branch",
1005
+ enforcement: "active",
1006
+ conditions: { ref_name: { include: ["~DEFAULT_BRANCH"], exclude: [] } },
1007
+ rules: [
1008
+ {
1009
+ type: "required_status_checks",
1010
+ parameters: {
1011
+ strict_required_status_checks_policy: false,
1012
+ do_not_enforce_on_create: false,
1013
+ required_status_checks: [
1014
+ {
1015
+ context: pipeline_1.AGGREGATE_CHECK_JOB_NAME,
1016
+ integration_id: exports.GITHUB_ACTIONS_APP_ID,
1017
+ },
1018
+ ],
1019
+ },
1020
+ },
1021
+ ],
1022
+ bypass_actors: [
1023
+ {
1024
+ actor_id: exports.GITHUB_ACTIONS_APP_ID,
1025
+ actor_type: "Integration",
1026
+ bypass_mode: "always",
1027
+ },
1028
+ ],
1029
+ });
1030
+ exports.desiredMergeGatingRuleset = desiredMergeGatingRuleset;
1031
+ /**
1032
+ * whether a ruleset provides working merge gating: active, requires
1033
+ * the aggregate check, and lets the release job (GitHub Actions app)
1034
+ * bypass it. Extra rules or bypass actors someone added are fine.
1035
+ */
1036
+ const rulesetProvidesMergeGating = (ruleset) => {
1037
+ var _a, _b;
1038
+ return ruleset.enforcement === "active" &&
1039
+ ((_a = ruleset.rules) !== null && _a !== void 0 ? _a : []).some((rule) => {
1040
+ var _a, _b;
1041
+ return rule.type === "required_status_checks" &&
1042
+ ((_b = (_a = rule.parameters) === null || _a === void 0 ? void 0 : _a.required_status_checks) !== null && _b !== void 0 ? _b : []).some((check) => check.context === pipeline_1.AGGREGATE_CHECK_JOB_NAME);
1043
+ }) &&
1044
+ ((_b = ruleset.bypass_actors) !== null && _b !== void 0 ? _b : []).some((actor) => actor.actor_type === "Integration" &&
1045
+ actor.actor_id === exports.GITHUB_ACTIONS_APP_ID &&
1046
+ actor.bypass_mode === "always");
1047
+ };
1048
+ exports.rulesetProvidesMergeGating = rulesetProvidesMergeGating;
1049
+ //# sourceMappingURL=githubMergeGating.js.map
1050
+
1051
+ /***/ }),
1052
+
953
1053
  /***/ 2231:
954
1054
  /***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
955
1055
 
@@ -1436,6 +1536,7 @@ exports.setupGithub = void 0;
1436
1536
  const pipeline_1 = __nccwpck_require__(53695);
1437
1537
  const commandSecretsSyncGithub_1 = __nccwpck_require__(27565);
1438
1538
  const github_1 = __nccwpck_require__(41353);
1539
+ const githubMergeGating_1 = __nccwpck_require__(14966);
1439
1540
  /**
1440
1541
  * configures github merge gating — the sane default gitlab ships
1441
1542
  * built-in and a fresh github repository lacks entirely: without it a
@@ -1443,18 +1544,19 @@ const github_1 = __nccwpck_require__(41353);
1443
1544
  * auto-merge button never appears.
1444
1545
  *
1445
1546
  * - repo settings: allow auto-merge, delete merged head branches
1446
- * - branch protection on the default branch: require the generated
1547
+ * - a repository ruleset on the default branch requiring the generated
1447
1548
  * `catladder ✅` aggregate check (the one stable context — individual
1448
- * job names change with every component add/rename)
1549
+ * job names change with every component add/rename), with a bypass
1550
+ * for the GitHub Actions app so the release job can push the release
1551
+ * commit and tag (see githubMergeGating.ts for the full rationale)
1449
1552
  *
1450
- * Deliberately non-destructive: existing protection settings
1451
- * (enforce-admins, required reviews, restrictions, strictness) are
1452
- * preserved; the aggregate context is merged into the required checks
1453
- * rather than replacing them. Admins can still bypass per PR — this is
1454
- * a default, not a cage.
1553
+ * Earlier versions required the check via classic branch protection,
1554
+ * which has no bypass list — the release job's direct push to the
1555
+ * default branch bounced off catladder's own gating (GH006). Setup
1556
+ * migrates that away: the aggregate context is removed from an
1557
+ * existing classic rule, everything else in it is preserved.
1455
1558
  */
1456
1559
  const setupGithub = async (instance, config) => {
1457
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
1458
1560
  if (!(0, pipeline_1.getEnabledPipelineTypes)(config).includes("github")) {
1459
1561
  return;
1460
1562
  }
@@ -1479,33 +1581,75 @@ const setupGithub = async (instance, config) => {
1479
1581
  instance.log("✅ auto-merge already enabled");
1480
1582
  }
1481
1583
  const branch = repoInfo.default_branch;
1482
- const protection = await (0, github_1.ghApiJson)("GET", `repos/${repo}/branches/${branch}/protection`).catch(() => null); // 404: branch not protected yet
1483
- const existingChecks = (_b = (_a = protection === null || protection === void 0 ? void 0 : protection.required_status_checks) === null || _a === void 0 ? void 0 : _a.checks) !== null && _b !== void 0 ? _b : [];
1484
- if (existingChecks.some((c) => c.context === pipeline_1.AGGREGATE_CHECK_JOB_NAME)) {
1485
- instance.log(`✅ '${pipeline_1.AGGREGATE_CHECK_JOB_NAME}' already required on ${branch}`);
1584
+ const rulesets = await (0, github_1.ghApiJson)("GET", `repos/${repo}/rulesets?per_page=100`);
1585
+ const existing = rulesets.find((r) => r.name === githubMergeGating_1.MERGE_GATING_RULESET_NAME);
1586
+ if (!existing) {
1587
+ await (0, github_1.ghApiJson)("POST", `repos/${repo}/rulesets`, (0, githubMergeGating_1.desiredMergeGatingRuleset)());
1588
+ instance.log(`✅ ruleset '${githubMergeGating_1.MERGE_GATING_RULESET_NAME}': '${pipeline_1.AGGREGATE_CHECK_JOB_NAME}' required on ${branch} — merges wait for the pipeline, the release job can push`);
1589
+ }
1590
+ else {
1591
+ const detail = await (0, github_1.ghApiJson)("GET", `repos/${repo}/rulesets/${existing.id}`);
1592
+ if ((0, githubMergeGating_1.rulesetProvidesMergeGating)(detail)) {
1593
+ instance.log(`✅ ruleset '${githubMergeGating_1.MERGE_GATING_RULESET_NAME}' already in place on ${branch}`);
1594
+ }
1595
+ else {
1596
+ await (0, github_1.ghApiJson)("PUT", `repos/${repo}/rulesets/${existing.id}`, (0, githubMergeGating_1.desiredMergeGatingRuleset)());
1597
+ instance.log(`✅ ruleset '${githubMergeGating_1.MERGE_GATING_RULESET_NAME}' restored — '${pipeline_1.AGGREGATE_CHECK_JOB_NAME}' required, release-job bypass in place`);
1598
+ }
1599
+ }
1600
+ await migrateAwayFromClassicProtection(instance, repo, branch);
1601
+ };
1602
+ exports.setupGithub = setupGithub;
1603
+ /**
1604
+ * removes the aggregate check from a classic branch protection rule
1605
+ * set up by earlier catladder versions — with the check now required
1606
+ * via the ruleset, the classic one would still reject the release
1607
+ * job's push. Everything else in the rule is preserved; the whole rule
1608
+ * is deleted when the check was all it carried.
1609
+ */
1610
+ const migrateAwayFromClassicProtection = async (instance, repo, branch) => {
1611
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
1612
+ const protection = await (0, github_1.ghApiJson)("GET", `repos/${repo}/branches/${branch}/protection`).catch(() => null); // 404: branch not protected
1613
+ const checks = (_b = (_a = protection === null || protection === void 0 ? void 0 : protection.required_status_checks) === null || _a === void 0 ? void 0 : _a.checks) !== null && _b !== void 0 ? _b : [];
1614
+ if (!checks.some((c) => c.context === pipeline_1.AGGREGATE_CHECK_JOB_NAME)) {
1615
+ return;
1616
+ }
1617
+ const remaining = checks.filter((c) => c.context !== pipeline_1.AGGREGATE_CHECK_JOB_NAME);
1618
+ const carriesAnythingElse = remaining.length > 0 ||
1619
+ ((_c = protection === null || protection === void 0 ? void 0 : protection.enforce_admins) === null || _c === void 0 ? void 0 : _c.enabled) ||
1620
+ (protection === null || protection === void 0 ? void 0 : protection.required_pull_request_reviews) ||
1621
+ (protection === null || protection === void 0 ? void 0 : protection.restrictions) ||
1622
+ ((_d = protection === null || protection === void 0 ? void 0 : protection.required_linear_history) === null || _d === void 0 ? void 0 : _d.enabled) ||
1623
+ ((_e = protection === null || protection === void 0 ? void 0 : protection.required_conversation_resolution) === null || _e === void 0 ? void 0 : _e.enabled) ||
1624
+ ((_f = protection === null || protection === void 0 ? void 0 : protection.required_signatures) === null || _f === void 0 ? void 0 : _f.enabled) ||
1625
+ ((_g = protection === null || protection === void 0 ? void 0 : protection.lock_branch) === null || _g === void 0 ? void 0 : _g.enabled) ||
1626
+ ((_h = protection === null || protection === void 0 ? void 0 : protection.allow_force_pushes) === null || _h === void 0 ? void 0 : _h.enabled) ||
1627
+ ((_j = protection === null || protection === void 0 ? void 0 : protection.allow_deletions) === null || _j === void 0 ? void 0 : _j.enabled);
1628
+ if (!carriesAnythingElse) {
1629
+ await (0, github_1.ghApiJson)("DELETE", `repos/${repo}/branches/${branch}/protection`);
1630
+ instance.log(`✅ classic branch protection on ${branch} removed — '${pipeline_1.AGGREGATE_CHECK_JOB_NAME}' was all it carried, and it is now required via the ruleset`);
1486
1631
  return;
1487
1632
  }
1488
- // merge into whatever protection exists; PUT replaces the whole rule,
1489
- // so every preserved setting must be carried over explicitly
1633
+ // PUT replaces the whole rule, so every preserved setting must be
1634
+ // carried over explicitly
1490
1635
  await (0, github_1.ghApiJson)("PUT", `repos/${repo}/branches/${branch}/protection`, {
1491
- required_status_checks: {
1492
- strict: (_d = (_c = protection === null || protection === void 0 ? void 0 : protection.required_status_checks) === null || _c === void 0 ? void 0 : _c.strict) !== null && _d !== void 0 ? _d : false,
1493
- checks: [
1494
- ...existingChecks.map(({ context, app_id }) => ({
1636
+ required_status_checks: remaining.length > 0
1637
+ ? {
1638
+ strict: (_l = (_k = protection === null || protection === void 0 ? void 0 : protection.required_status_checks) === null || _k === void 0 ? void 0 : _k.strict) !== null && _l !== void 0 ? _l : false,
1639
+ checks: remaining.map(({ context, app_id }) => ({
1495
1640
  context,
1496
1641
  ...(app_id !== undefined ? { app_id } : {}),
1497
1642
  })),
1498
- { context: pipeline_1.AGGREGATE_CHECK_JOB_NAME },
1499
- ],
1500
- },
1501
- enforce_admins: (_f = (_e = protection === null || protection === void 0 ? void 0 : protection.enforce_admins) === null || _e === void 0 ? void 0 : _e.enabled) !== null && _f !== void 0 ? _f : false,
1643
+ }
1644
+ : null,
1645
+ enforce_admins: (_o = (_m = protection === null || protection === void 0 ? void 0 : protection.enforce_admins) === null || _m === void 0 ? void 0 : _m.enabled) !== null && _o !== void 0 ? _o : false,
1502
1646
  required_pull_request_reviews: (protection === null || protection === void 0 ? void 0 : protection.required_pull_request_reviews)
1503
1647
  ? {
1504
- required_approving_review_count: (_g = protection.required_pull_request_reviews
1505
- .required_approving_review_count) !== null && _g !== void 0 ? _g : 0,
1506
- dismiss_stale_reviews: (_h = protection.required_pull_request_reviews.dismiss_stale_reviews) !== null && _h !== void 0 ? _h : false,
1507
- require_code_owner_reviews: (_j = protection.required_pull_request_reviews
1508
- .require_code_owner_reviews) !== null && _j !== void 0 ? _j : false,
1648
+ required_approving_review_count: (_p = protection.required_pull_request_reviews
1649
+ .required_approving_review_count) !== null && _p !== void 0 ? _p : 0,
1650
+ dismiss_stale_reviews: (_q = protection.required_pull_request_reviews.dismiss_stale_reviews) !== null && _q !== void 0 ? _q : false,
1651
+ require_code_owner_reviews: (_r = protection.required_pull_request_reviews
1652
+ .require_code_owner_reviews) !== null && _r !== void 0 ? _r : false,
1509
1653
  }
1510
1654
  : null,
1511
1655
  restrictions: (protection === null || protection === void 0 ? void 0 : protection.restrictions)
@@ -1515,12 +1659,14 @@ const setupGithub = async (instance, config) => {
1515
1659
  apps: protection.restrictions.apps.map((a) => a.slug),
1516
1660
  }
1517
1661
  : null,
1518
- allow_force_pushes: (_l = (_k = protection === null || protection === void 0 ? void 0 : protection.allow_force_pushes) === null || _k === void 0 ? void 0 : _k.enabled) !== null && _l !== void 0 ? _l : false,
1519
- allow_deletions: (_o = (_m = protection === null || protection === void 0 ? void 0 : protection.allow_deletions) === null || _m === void 0 ? void 0 : _m.enabled) !== null && _o !== void 0 ? _o : false,
1662
+ required_linear_history: (_t = (_s = protection === null || protection === void 0 ? void 0 : protection.required_linear_history) === null || _s === void 0 ? void 0 : _s.enabled) !== null && _t !== void 0 ? _t : false,
1663
+ required_conversation_resolution: (_v = (_u = protection === null || protection === void 0 ? void 0 : protection.required_conversation_resolution) === null || _u === void 0 ? void 0 : _u.enabled) !== null && _v !== void 0 ? _v : false,
1664
+ lock_branch: (_x = (_w = protection === null || protection === void 0 ? void 0 : protection.lock_branch) === null || _w === void 0 ? void 0 : _w.enabled) !== null && _x !== void 0 ? _x : false,
1665
+ allow_force_pushes: (_z = (_y = protection === null || protection === void 0 ? void 0 : protection.allow_force_pushes) === null || _y === void 0 ? void 0 : _y.enabled) !== null && _z !== void 0 ? _z : false,
1666
+ allow_deletions: (_1 = (_0 = protection === null || protection === void 0 ? void 0 : protection.allow_deletions) === null || _0 === void 0 ? void 0 : _0.enabled) !== null && _1 !== void 0 ? _1 : false,
1520
1667
  });
1521
- instance.log(`✅ branch protection on ${branch}: '${pipeline_1.AGGREGATE_CHECK_JOB_NAME}' required (merges now wait for the pipeline)`);
1668
+ instance.log(`✅ '${pipeline_1.AGGREGATE_CHECK_JOB_NAME}' removed from classic branch protection on ${branch} — now required via the ruleset; the rest of the rule is preserved`);
1522
1669
  };
1523
- exports.setupGithub = setupGithub;
1524
1670
  //# sourceMappingURL=setupGithub.js.map
1525
1671
 
1526
1672
  /***/ }),
@@ -260,13 +260,17 @@ now-wrong `gitRemote`, regenerate once more, and finish with
260
260
  PR is mergeable the moment it opens, checks still running, and the
261
261
  auto-merge button does not even appear. `yarn catladder project setup`
262
262
  configures it: auto-merge + delete-merged-branches on the repo, and a
263
- branch protection rule on the default branch requiring the generated
264
- **`catladder ✅`** aggregate check (the one stable context — never
265
- require individual job names, they change with every component
266
- add/rename and a stale required name blocks its own PR forever).
267
- `project doctor` verifies it. Two things stay human choices: "Do not
268
- allow bypassing" (leave it off — generated CI means a generation bug
269
- can only be fixed via the admin bypass) and required reviews.
263
+ repository **ruleset** (`catladder merge gating`) on the default
264
+ branch requiring the generated **`catladder ✅`** aggregate check
265
+ (the one stable context — never require individual job names, they
266
+ change with every component add/rename and a stale required name
267
+ blocks its own PR forever). The ruleset carries a bypass for the
268
+ GitHub Actions app: the release job pushes the release commit and
269
+ tag straight to the default branch with the workflow token, which a
270
+ required check would otherwise reject (GH006 — classic branch
271
+ protection has no bypass list, which is why setup uses a ruleset and
272
+ migrates old classic-protection gating away). `project doctor`
273
+ verifies all of it. Required reviews stay a human choice.
270
274
  - Tell the team where the pipeline lives now and how manual actions
271
275
  work there (on GitHub, manual jobs are dispatch workflows in the
272
276
  Actions sidebar — `🚀 catladder create release`, `▶️ catladder deploy`,
@@ -260,13 +260,17 @@ now-wrong `gitRemote`, regenerate once more, and finish with
260
260
  PR is mergeable the moment it opens, checks still running, and the
261
261
  auto-merge button does not even appear. `yarn catladder project setup`
262
262
  configures it: auto-merge + delete-merged-branches on the repo, and a
263
- branch protection rule on the default branch requiring the generated
264
- **`catladder ✅`** aggregate check (the one stable context — never
265
- require individual job names, they change with every component
266
- add/rename and a stale required name blocks its own PR forever).
267
- `project doctor` verifies it. Two things stay human choices: "Do not
268
- allow bypassing" (leave it off — generated CI means a generation bug
269
- can only be fixed via the admin bypass) and required reviews.
263
+ repository **ruleset** (`catladder merge gating`) on the default
264
+ branch requiring the generated **`catladder ✅`** aggregate check
265
+ (the one stable context — never require individual job names, they
266
+ change with every component add/rename and a stale required name
267
+ blocks its own PR forever). The ruleset carries a bypass for the
268
+ GitHub Actions app: the release job pushes the release commit and
269
+ tag straight to the default branch with the workflow token, which a
270
+ required check would otherwise reject (GH006 — classic branch
271
+ protection has no bypass list, which is why setup uses a ruleset and
272
+ migrates old classic-protection gating away). `project doctor`
273
+ verifies all of it. Required reviews stay a human choice.
270
274
  - Tell the team where the pipeline lives now and how manual actions
271
275
  work there (on GitHub, manual jobs are dispatch workflows in the
272
276
  Actions sidebar — `🚀 catladder create release`, `▶️ catladder deploy`,