dorfl 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advance-drivers.d.ts +1 -1
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +15 -3
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +76 -4
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-treeless-publish.d.ts +23 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -1
- package/dist/advance-treeless-publish.js +41 -0
- package/dist/advance-treeless-publish.js.map +1 -1
- package/dist/advance.d.ts +59 -7
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +389 -82
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +30 -2
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +50 -0
- package/dist/advancing-lock.js.map +1 -1
- package/dist/agent-launch.d.ts +12 -0
- package/dist/agent-launch.d.ts.map +1 -1
- package/dist/agent-launch.js +22 -12
- package/dist/agent-launch.js.map +1 -1
- package/dist/agent-stop.d.ts +40 -2
- package/dist/agent-stop.d.ts.map +1 -1
- package/dist/agent-stop.js +30 -2
- package/dist/agent-stop.js.map +1 -1
- package/dist/apply-decide.d.ts +17 -3
- package/dist/apply-decide.d.ts.map +1 -1
- package/dist/apply-decide.js +36 -7
- package/dist/apply-decide.js.map +1 -1
- package/dist/apply-merge-action.d.ts +17 -5
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +43 -8
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-persist.d.ts +57 -26
- package/dist/apply-persist.d.ts.map +1 -1
- package/dist/apply-persist.js +166 -37
- package/dist/apply-persist.js.map +1 -1
- package/dist/apply-stuck-action.d.ts +151 -0
- package/dist/apply-stuck-action.d.ts.map +1 -0
- package/dist/apply-stuck-action.js +125 -0
- package/dist/apply-stuck-action.js.map +1 -0
- package/dist/brand.d.ts +12 -1
- package/dist/brand.d.ts.map +1 -1
- package/dist/brand.js +2 -1
- package/dist/brand.js.map +1 -1
- package/dist/cli-spinner.d.ts +1 -1
- package/dist/cli-spinner.js +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +320 -63
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.js +1 -1
- package/dist/complete.d.ts +6 -3
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +91 -37
- package/dist/complete.js.map +1 -1
- package/dist/config.d.ts +55 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +56 -4
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +22 -0
- package/dist/continue-branch.js.map +1 -1
- package/dist/decision-engine.d.ts +38 -10
- package/dist/decision-engine.d.ts.map +1 -1
- package/dist/decision-engine.js +10 -6
- package/dist/decision-engine.js.map +1 -1
- package/dist/do-autopick.d.ts.map +1 -1
- package/dist/do-autopick.js +11 -1
- package/dist/do-autopick.js.map +1 -1
- package/dist/do-config.d.ts +1 -1
- package/dist/do.d.ts +49 -56
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +419 -30
- package/dist/do.js.map +1 -1
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +12 -4
- package/dist/env-config.js.map +1 -1
- package/dist/failure-cause.d.ts +3 -2
- package/dist/failure-cause.d.ts.map +1 -1
- package/dist/failure-cause.js +28 -1
- package/dist/failure-cause.js.map +1 -1
- package/dist/format.d.ts +6 -6
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +10 -27
- package/dist/format.js.map +1 -1
- package/dist/frontmatter.d.ts +16 -4
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +27 -1
- package/dist/frontmatter.js.map +1 -1
- package/dist/gc.d.ts +65 -6
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +126 -9
- package/dist/gc.js.map +1 -1
- package/dist/github.d.ts +14 -0
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +73 -0
- package/dist/github.js.map +1 -1
- package/dist/harness.d.ts +25 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/install-ci-branch-protection.d.ts +132 -38
- package/dist/install-ci-branch-protection.d.ts.map +1 -1
- package/dist/install-ci-branch-protection.js +190 -48
- package/dist/install-ci-branch-protection.js.map +1 -1
- package/dist/install-ci-core.d.ts +21 -1
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +10 -3
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +32 -0
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +73 -0
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-skills.d.ts +123 -0
- package/dist/install-skills.d.ts.map +1 -0
- package/dist/install-skills.js +100 -0
- package/dist/install-skills.js.map +1 -0
- package/dist/intake-trigger-template.js +1 -1
- package/dist/intake.d.ts +17 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +23 -7
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +10 -7
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +174 -60
- package/dist/integration-core.js.map +1 -1
- package/dist/integrator.d.ts +1 -1
- package/dist/integrator.d.ts.map +1 -1
- package/dist/integrator.js +19 -3
- package/dist/integrator.js.map +1 -1
- package/dist/isolation.d.ts +2 -2
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +9 -0
- package/dist/isolation.js.map +1 -1
- package/dist/item-lock.d.ts +196 -129
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +329 -254
- package/dist/item-lock.js.map +1 -1
- package/dist/ledger-write.d.ts +31 -26
- package/dist/ledger-write.d.ts.map +1 -1
- package/dist/ledger-write.js +100 -122
- package/dist/ledger-write.js.map +1 -1
- package/dist/lifecycle-gather.d.ts +17 -0
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +7 -1
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/lifecycle-pools.d.ts +40 -3
- package/dist/lifecycle-pools.d.ts.map +1 -1
- package/dist/lifecycle-pools.js +26 -6
- package/dist/lifecycle-pools.js.map +1 -1
- package/dist/merge-question-surfacer.d.ts +15 -1
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +18 -3
- package/dist/merge-question-surfacer.js.map +1 -1
- package/dist/migrate-stuck-locks.d.ts +129 -0
- package/dist/migrate-stuck-locks.d.ts.map +1 -0
- package/dist/migrate-stuck-locks.js +355 -0
- package/dist/migrate-stuck-locks.js.map +1 -0
- package/dist/needs-attention.d.ts +293 -16
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +532 -57
- package/dist/needs-attention.js.map +1 -1
- package/dist/orphan-sidecar.d.ts +8 -4
- package/dist/orphan-sidecar.d.ts.map +1 -1
- package/dist/orphan-sidecar.js +35 -2
- package/dist/orphan-sidecar.js.map +1 -1
- package/dist/pi-harness.d.ts +16 -0
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +82 -2
- package/dist/pi-harness.js.map +1 -1
- package/dist/placement.d.ts +3 -3
- package/dist/placement.js +1 -1
- package/dist/prd-to-spec.d.ts.map +1 -1
- package/dist/prd-to-spec.js +9 -5
- package/dist/prd-to-spec.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
- package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
- package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
- package/dist/protocol/TASKING-PROTOCOL.md +3 -1
- package/dist/protocol/WORK-CONTRACT.md +22 -18
- package/dist/protocol/task-template.md +1 -1
- package/dist/reap-branches.d.ts +12 -9
- package/dist/reap-branches.d.ts.map +1 -1
- package/dist/reap-branches.js +25 -7
- package/dist/reap-branches.js.map +1 -1
- package/dist/recover-isolated.d.ts +12 -0
- package/dist/recover-isolated.d.ts.map +1 -1
- package/dist/recover-isolated.js +5 -0
- package/dist/recover-isolated.js.map +1 -1
- package/dist/repo-config.d.ts +23 -2
- package/dist/repo-config.d.ts.map +1 -1
- package/dist/repo-config.js +53 -7
- package/dist/repo-config.js.map +1 -1
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +18 -2
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-verdict.js +1 -1
- package/dist/review-verdict.js.map +1 -1
- package/dist/run.d.ts +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +54 -19
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +17 -8
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +22 -8
- package/dist/scan.js.map +1 -1
- package/dist/select-priority.d.ts +2 -2
- package/dist/select-priority.js +1 -1
- package/dist/select-priority.js.map +1 -1
- package/dist/sidecar-apply.js +1 -1
- package/dist/sidecar-apply.js.map +1 -1
- package/dist/sidecar.d.ts +45 -10
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +82 -2
- package/dist/sidecar.js.map +1 -1
- package/dist/skills/answer-questions/SKILL.md +89 -0
- package/dist/skills/capture-signal/SKILL.md +52 -0
- package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
- package/dist/skills/drive-tasks/SKILL.md +218 -0
- package/dist/skills/from-idea/SKILL.md +83 -0
- package/dist/skills/merge-prs/SKILL.md +70 -0
- package/dist/skills/orchestrate/SKILL.md +101 -0
- package/dist/skills/promote/SKILL.md +35 -0
- package/dist/skills/review/SKILL.md +16 -0
- package/dist/skills/setup/SKILL.md +258 -0
- package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
- package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
- package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
- package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
- package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
- package/dist/skills/setup/protocol/spec-template.md +71 -0
- package/dist/skills/setup/protocol/task-template.md +65 -0
- package/dist/skills/surface-questions/SKILL.md +16 -0
- package/dist/skills/to-spec/SKILL.md +34 -0
- package/dist/skills/to-task/SKILL.md +19 -0
- package/dist/skills/triage-observations/SKILL.md +78 -0
- package/dist/skills/work/SKILL.md +51 -0
- package/dist/slug-namespace.d.ts +5 -5
- package/dist/slug-namespace.js +5 -5
- package/dist/slug-namespace.js.map +1 -1
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +2 -3
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +51 -57
- package/dist/start.js.map +1 -1
- package/dist/surface-gate.d.ts.map +1 -1
- package/dist/surface-gate.js +10 -3
- package/dist/surface-gate.js.map +1 -1
- package/dist/surface-persist.js +1 -1
- package/dist/surface-persist.js.map +1 -1
- package/dist/tasker-review-loop.d.ts +4 -3
- package/dist/tasker-review-loop.d.ts.map +1 -1
- package/dist/tasker-review-loop.js.map +1 -1
- package/dist/tasking-lock.d.ts +2 -2
- package/dist/tasking-lock.d.ts.map +1 -1
- package/dist/tasking-lock.js +53 -32
- package/dist/tasking-lock.js.map +1 -1
- package/dist/tasking.d.ts +40 -6
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +224 -12
- package/dist/tasking.js.map +1 -1
- package/dist/triage-persist.d.ts +10 -6
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +34 -4
- package/dist/triage-persist.js.map +1 -1
- package/dist/vendor/incur/agents.d.ts +58 -0
- package/dist/vendor/incur/agents.d.ts.map +1 -0
- package/dist/vendor/incur/agents.js +343 -0
- package/dist/vendor/incur/agents.js.map +1 -0
- package/dist/verify-workflow-template.js +1 -1
- package/dist/watch-session.d.ts +11 -3
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +94 -7
- package/dist/watch-session.js.map +1 -1
- package/dist/work-layout.d.ts +7 -3
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +6 -3
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/package.json +2 -2
- package/src/advance-drivers.ts +16 -3
- package/src/advance-lifecycle-template.ts +89 -4
- package/src/advance-treeless-publish.ts +45 -0
- package/src/advance.ts +467 -87
- package/src/advancing-lock.ts +96 -1
- package/src/agent-launch.ts +37 -12
- package/src/agent-stop.ts +60 -2
- package/src/apply-decide.ts +36 -7
- package/src/apply-merge-action.ts +45 -7
- package/src/apply-persist.ts +228 -54
- package/src/apply-stuck-action.ts +260 -0
- package/src/brand.ts +14 -2
- package/src/cli-spinner.ts +1 -1
- package/src/cli.ts +401 -70
- package/src/close-job-template.ts +1 -1
- package/src/complete.ts +110 -52
- package/src/config.ts +107 -13
- package/src/continue-branch.ts +22 -0
- package/src/decision-engine.ts +55 -17
- package/src/do-autopick.ts +12 -1
- package/src/do-config.ts +1 -1
- package/src/do.ts +511 -38
- package/src/env-config.ts +12 -4
- package/src/failure-cause.ts +30 -1
- package/src/format.ts +10 -30
- package/src/frontmatter.ts +35 -5
- package/src/gc.ts +172 -9
- package/src/github.ts +78 -0
- package/src/harness.ts +25 -0
- package/src/index.ts +10 -0
- package/src/install-ci-branch-protection.ts +282 -57
- package/src/install-ci-core.ts +30 -3
- package/src/install-ci-github.ts +86 -0
- package/src/install-skills.ts +166 -0
- package/src/intake-trigger-template.ts +1 -1
- package/src/intake.ts +46 -11
- package/src/integration-core.ts +199 -65
- package/src/integrator.ts +22 -9
- package/src/isolation.ts +11 -2
- package/src/item-lock.ts +485 -341
- package/src/ledger-write.ts +135 -150
- package/src/lifecycle-gather.ts +29 -6
- package/src/lifecycle-pools.ts +67 -9
- package/src/merge-question-surfacer.ts +28 -4
- package/src/migrate-stuck-locks.ts +451 -0
- package/src/needs-attention.ts +857 -84
- package/src/orphan-sidecar.ts +47 -6
- package/src/pi-harness.ts +82 -2
- package/src/placement.ts +3 -3
- package/src/prd-to-spec.ts +13 -5
- package/src/reap-branches.ts +38 -14
- package/src/recover-isolated.ts +17 -0
- package/src/repo-config.ts +55 -6
- package/src/repo-mirror.ts +23 -1
- package/src/review-verdict.ts +1 -1
- package/src/run.ts +64 -18
- package/src/scan.ts +22 -8
- package/src/select-priority.ts +3 -3
- package/src/sidecar-apply.ts +1 -1
- package/src/sidecar.ts +138 -11
- package/src/slug-namespace.ts +7 -7
- package/src/spec-complete.ts +2 -3
- package/src/start.ts +53 -59
- package/src/surface-gate.ts +10 -3
- package/src/surface-persist.ts +1 -1
- package/src/tasker-review-loop.ts +4 -3
- package/src/tasking-lock.ts +52 -36
- package/src/tasking.ts +261 -17
- package/src/triage-persist.ts +56 -12
- package/src/vendor/incur/LICENSE +21 -0
- package/src/vendor/incur/README.md +19 -0
- package/src/vendor/incur/agents.ts +392 -0
- package/src/verify-workflow-template.ts +1 -1
- package/src/watch-session.ts +102 -7
- package/src/work-layout.ts +6 -3
- package/src/workspace.ts +1 -1
|
@@ -34,28 +34,44 @@
|
|
|
34
34
|
* change can flip it knowingly; the spec calls this "pick the cheaper of the
|
|
35
35
|
* two and record the choice."
|
|
36
36
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* DEFAULT BRANCH — NOT hardcoded to `main`. The GitHub adapter resolves the
|
|
38
|
+
* repo's real default branch ({@link CIProviderContext.getDefaultBranch}, via
|
|
39
|
+
* `gh repo view --json defaultBranchRef`); the orchestrator threads it into
|
|
40
|
+
* BOTH the classic PUT and the printed `gh api` fallback so a `master`-defaulted
|
|
41
|
+
* (or otherwise-renamed) repo is protected on the correct branch instead of
|
|
42
|
+
* 404-ing a hardcoded `main`. The lookup falls back to {@link
|
|
43
|
+
* DEFAULT_PROTECTED_BRANCH} (`main`) only when it fails/empties, and that
|
|
44
|
+
* fallback is LOGGED so it is visible. An explicit `branch` option still wins
|
|
45
|
+
* over auto-detection (a repo whose protected branch is not its default).
|
|
40
46
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
47
|
+
* DEADLOCK GUARD — a required check that has NEVER reported blocks every merge
|
|
48
|
+
* (GitHub treats an unreported required check as pending), so a PR opened before
|
|
49
|
+
* `verify.yml` first ran would deadlock. The guard is a RUNTIME MECHANISM, not a
|
|
50
|
+
* log line: the required-`verify` check is enforced through a branch RULESET
|
|
51
|
+
* ({@link buildBranchProtectionRuleset}, `POST /repos/{owner}/{repo}/rulesets`)
|
|
52
|
+
* whose `required_status_checks` rule carries `do_not_enforce_on_create: true` —
|
|
53
|
+
* GitHub's native "require this check, but do not fail refs created before the
|
|
54
|
+
* rule / before the check first ran" toggle. The ruleset targets the resolved
|
|
55
|
+
* default branch via `conditions.ref_name`.
|
|
45
56
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
57
|
+
* WHY RULESET FOR THE GUARD BUT CLASSIC PUT FOR THE BASE (nit-a preserved).
|
|
58
|
+
* The base protection stays a single classic PUT
|
|
59
|
+
* ({@link buildBranchProtectionSpec}: `strict: true` = "require branches up to
|
|
60
|
+
* date before merging") because that endpoint is the simplest way to express
|
|
61
|
+
* up-to-date-ness and nit-a ratified the classic-PUT choice. ONLY the deadlock-
|
|
62
|
+
* prone piece — the required-status-check gate — moves to a ruleset, because the
|
|
63
|
+
* `do_not_enforce_on_create` create-exemption exists ONLY on rulesets, not on
|
|
64
|
+
* the classic `required_status_checks` object. Keeping the required check in the
|
|
65
|
+
* classic PUT AND adding the ruleset would double-enforce it and NULLIFY the
|
|
66
|
+
* exemption (the classic copy has no exemption), so the required check lives in
|
|
67
|
+
* the ruleset ALONE; the classic PUT keeps `strict` with an EMPTY `checks`
|
|
68
|
+
* array (valid: "require up to date", no named required check at the classic
|
|
69
|
+
* layer). This is exactly "only the deadlock-guard aspect needs the ruleset" —
|
|
70
|
+
* not a full base-protection migration. Chosen over the pre-run mechanism
|
|
71
|
+
* (`gh workflow run` + wait) because `writeArtifacts` writes `verify.yml`
|
|
72
|
+
* LOCALLY and does NOT push, so at install time the workflow is not yet on the
|
|
73
|
+
* default branch and a pre-run has nothing to dispatch. The ruleset toggle needs
|
|
74
|
+
* no pushed workflow and no wait, so it is the only viable runtime guard here.
|
|
59
75
|
*/
|
|
60
76
|
|
|
61
77
|
import {VERIFY_CHECK_CONTEXT} from './verify-workflow-template.js';
|
|
@@ -64,15 +80,17 @@ import type {CIProviderContext} from './install-ci-core.js';
|
|
|
64
80
|
export {VERIFY_CHECK_CONTEXT};
|
|
65
81
|
|
|
66
82
|
/**
|
|
67
|
-
* The branch-protection request body the GitHub adapter PUTs at
|
|
83
|
+
* The BASE branch-protection request body the GitHub adapter PUTs at
|
|
68
84
|
* `/repos/{owner}/{repo}/branches/{branch}/protection`. Shape mirrors the REST
|
|
69
85
|
* docs (the GitHub PUT REQUIRES `enforce_admins`, `required_pull_request_reviews`,
|
|
70
86
|
* and `restrictions` to be present; `null` for "not configured"). We set only
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
87
|
+
* `strict: true` (= "require branches up to date before merging") with an EMPTY
|
|
88
|
+
* `checks` array — the required `${VERIFY_CHECK_CONTEXT}` check is NOT here; it
|
|
89
|
+
* lives in the deadlock-guard RULESET ({@link buildBranchProtectionRuleset}) so
|
|
90
|
+
* its `do_not_enforce_on_create` create-exemption is meaningful (see module
|
|
91
|
+
* header). The other required fields stay `null` so install-ci does not silently
|
|
92
|
+
* impose unrelated gates (reviews, admin enforcement, push restrictions) the
|
|
93
|
+
* user did not opt into.
|
|
76
94
|
*/
|
|
77
95
|
export interface BranchProtectionSpec {
|
|
78
96
|
required_status_checks: {
|
|
@@ -85,16 +103,15 @@ export interface BranchProtectionSpec {
|
|
|
85
103
|
}
|
|
86
104
|
|
|
87
105
|
/**
|
|
88
|
-
* Build the Tier-1 branch-protection spec the GitHub adapter PUTs
|
|
89
|
-
* `
|
|
90
|
-
*
|
|
91
|
-
* agreement, so the two cannot drift.
|
|
106
|
+
* Build the Tier-1 BASE branch-protection spec the GitHub adapter PUTs
|
|
107
|
+
* (`strict: true`, no named required check — the required check is the ruleset's
|
|
108
|
+
* job, see {@link buildBranchProtectionRuleset} and the module header).
|
|
92
109
|
*/
|
|
93
110
|
export function buildBranchProtectionSpec(): BranchProtectionSpec {
|
|
94
111
|
return {
|
|
95
112
|
required_status_checks: {
|
|
96
113
|
strict: true,
|
|
97
|
-
checks: [
|
|
114
|
+
checks: [],
|
|
98
115
|
},
|
|
99
116
|
enforce_admins: null,
|
|
100
117
|
required_pull_request_reviews: null,
|
|
@@ -102,6 +119,70 @@ export function buildBranchProtectionSpec(): BranchProtectionSpec {
|
|
|
102
119
|
};
|
|
103
120
|
}
|
|
104
121
|
|
|
122
|
+
/** The name the deadlock-guard ruleset is created under (stable/idempotent-ish). */
|
|
123
|
+
export const BRANCH_PROTECTION_RULESET_NAME = 'dorfl-verify-required';
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The deadlock-guard RULESET body the GitHub adapter POSTs at
|
|
127
|
+
* `/repos/{owner}/{repo}/rulesets`. It carries the required-`${VERIFY_CHECK_CONTEXT}`
|
|
128
|
+
* status-check rule with `do_not_enforce_on_create: true` — GitHub's native
|
|
129
|
+
* "require this check, but do not fail refs that predate the rule / the check's
|
|
130
|
+
* first run" toggle (the runtime deadlock guard). `enforcement: 'active'` +
|
|
131
|
+
* `conditions.ref_name` scope it to the resolved default branch. The required
|
|
132
|
+
* `context` is the SAME {@link VERIFY_CHECK_CONTEXT} constant the emitted verify
|
|
133
|
+
* workflow names its job after — by construction, so the two cannot drift.
|
|
134
|
+
*/
|
|
135
|
+
export interface BranchProtectionRuleset {
|
|
136
|
+
name: string;
|
|
137
|
+
target: 'branch';
|
|
138
|
+
enforcement: 'active';
|
|
139
|
+
conditions: {
|
|
140
|
+
ref_name: {
|
|
141
|
+
include: string[];
|
|
142
|
+
exclude: string[];
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
rules: {
|
|
146
|
+
type: 'required_status_checks';
|
|
147
|
+
parameters: {
|
|
148
|
+
strict_required_status_checks_policy: boolean;
|
|
149
|
+
do_not_enforce_on_create: true;
|
|
150
|
+
required_status_checks: {context: string}[];
|
|
151
|
+
};
|
|
152
|
+
}[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Build the deadlock-guard ruleset for `branch` (the resolved default branch).
|
|
157
|
+
* The `ref_name` include uses the exact-ref form (`refs/heads/<branch>`) so the
|
|
158
|
+
* rule targets only that branch. `do_not_enforce_on_create: true` is the guard.
|
|
159
|
+
*/
|
|
160
|
+
export function buildBranchProtectionRuleset(
|
|
161
|
+
branch: string = DEFAULT_PROTECTED_BRANCH,
|
|
162
|
+
): BranchProtectionRuleset {
|
|
163
|
+
return {
|
|
164
|
+
name: BRANCH_PROTECTION_RULESET_NAME,
|
|
165
|
+
target: 'branch',
|
|
166
|
+
enforcement: 'active',
|
|
167
|
+
conditions: {
|
|
168
|
+
ref_name: {
|
|
169
|
+
include: [`refs/heads/${branch}`],
|
|
170
|
+
exclude: [],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
rules: [
|
|
174
|
+
{
|
|
175
|
+
type: 'required_status_checks',
|
|
176
|
+
parameters: {
|
|
177
|
+
strict_required_status_checks_policy: true,
|
|
178
|
+
do_not_enforce_on_create: true,
|
|
179
|
+
required_status_checks: [{context: VERIFY_CHECK_CONTEXT}],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
105
186
|
/**
|
|
106
187
|
* The repo's default branch the protection rule targets (the rule the spec
|
|
107
188
|
* names: protect `main` so a propose-mode PR cannot be merged stale). Exposed
|
|
@@ -132,6 +213,26 @@ export function formatManualBranchProtectionCommand(
|
|
|
132
213
|
);
|
|
133
214
|
}
|
|
134
215
|
|
|
216
|
+
/**
|
|
217
|
+
* The exact `gh api` command the user runs themselves to create the deadlock-
|
|
218
|
+
* guard RULESET when install-ci cannot auto-configure (no admin scope). Quoted
|
|
219
|
+
* body is the same {@link BranchProtectionRuleset} the auto path POSTs, so
|
|
220
|
+
* reproducing the call by copy-paste is byte-equivalent to a successful auto
|
|
221
|
+
* run. Pure string, no I/O.
|
|
222
|
+
*/
|
|
223
|
+
export function formatManualBranchRulesetCommand(
|
|
224
|
+
repo: string,
|
|
225
|
+
branch: string = DEFAULT_PROTECTED_BRANCH,
|
|
226
|
+
): string {
|
|
227
|
+
const body = JSON.stringify(buildBranchProtectionRuleset(branch));
|
|
228
|
+
return (
|
|
229
|
+
`echo '${body}' | gh api -X POST ` +
|
|
230
|
+
`-H "Accept: application/vnd.github+json" ` +
|
|
231
|
+
`repos/${repo}/rulesets ` +
|
|
232
|
+
`--input -`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
135
236
|
/** Options for {@link installCIBranchProtectionStep}. */
|
|
136
237
|
export interface BranchProtectionStepOptions {
|
|
137
238
|
/** The CI-provider seam (GitHub adapter in production; stub in tests). */
|
|
@@ -140,7 +241,13 @@ export interface BranchProtectionStepOptions {
|
|
|
140
241
|
fake?: boolean;
|
|
141
242
|
/** Sink for human-facing progress lines. */
|
|
142
243
|
log: (line: string) => void;
|
|
143
|
-
/**
|
|
244
|
+
/**
|
|
245
|
+
* Explicitly override the branch the rule targets. When OMITTED, the step
|
|
246
|
+
* auto-detects the repo's default branch via
|
|
247
|
+
* {@link CIProviderContext.getDefaultBranch}, falling back to
|
|
248
|
+
* {@link DEFAULT_PROTECTED_BRANCH} (`main`) and logging the fallback. An
|
|
249
|
+
* explicit value always wins over auto-detection.
|
|
250
|
+
*/
|
|
144
251
|
branch?: string;
|
|
145
252
|
}
|
|
146
253
|
|
|
@@ -168,41 +275,105 @@ export interface BranchProtectionStepResult {
|
|
|
168
275
|
error?: string;
|
|
169
276
|
/** The branch the step targeted (echoed for tests / logs). */
|
|
170
277
|
branch: string;
|
|
278
|
+
/**
|
|
279
|
+
* Whether the branch was AUTO-DETECTED (`true`) or came from an explicit
|
|
280
|
+
* `branch` option / the `main` fallback (`false`). Echoed for tests / logs.
|
|
281
|
+
*/
|
|
282
|
+
branchAutoDetected?: boolean;
|
|
283
|
+
/**
|
|
284
|
+
* The deadlock-guard RULESET outcome (only on the admin auto path):
|
|
285
|
+
* - `set` — the ruleset POST succeeded;
|
|
286
|
+
* - `failed` — the ruleset POST was rejected (detail in `rulesetError`);
|
|
287
|
+
* - `skipped-no-seam` — the provider does not implement the ruleset seam.
|
|
288
|
+
* Absent on the non-admin / skipped paths.
|
|
289
|
+
*/
|
|
290
|
+
rulesetStatus?: 'set' | 'failed' | 'skipped-no-seam';
|
|
291
|
+
/** The ruleset failure detail when `rulesetStatus === 'failed'`. */
|
|
292
|
+
rulesetError?: string;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Resolve the branch the protection targets. An EXPLICIT `branch` option always
|
|
297
|
+
* wins; otherwise auto-detect the repo's default branch via
|
|
298
|
+
* {@link CIProviderContext.getDefaultBranch}, falling back to `main` (LOGGED)
|
|
299
|
+
* when the lookup fails/empties. Returns the branch + whether it was
|
|
300
|
+
* auto-detected (for the result / log lines).
|
|
301
|
+
*/
|
|
302
|
+
async function resolveTargetBranch(
|
|
303
|
+
ctx: CIProviderContext,
|
|
304
|
+
explicit: string | undefined,
|
|
305
|
+
log: (line: string) => void,
|
|
306
|
+
): Promise<{branch: string; autoDetected: boolean}> {
|
|
307
|
+
if (explicit !== undefined) {
|
|
308
|
+
return {branch: explicit, autoDetected: false};
|
|
309
|
+
}
|
|
310
|
+
const detected = ctx.getDefaultBranch
|
|
311
|
+
? await ctx.getDefaultBranch().catch(() => undefined)
|
|
312
|
+
: undefined;
|
|
313
|
+
if (detected) {
|
|
314
|
+
return {branch: detected, autoDetected: true};
|
|
315
|
+
}
|
|
316
|
+
log(
|
|
317
|
+
`branch protection: could not detect the default branch; ` +
|
|
318
|
+
`falling back to \`${DEFAULT_PROTECTED_BRANCH}\`.`,
|
|
319
|
+
);
|
|
320
|
+
return {branch: DEFAULT_PROTECTED_BRANCH, autoDetected: false};
|
|
171
321
|
}
|
|
172
322
|
|
|
173
323
|
/**
|
|
174
324
|
* The orchestrator install-ci calls AFTER artifacts are written + secrets are
|
|
175
|
-
* orchestrated:
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
325
|
+
* orchestrated:
|
|
326
|
+
*
|
|
327
|
+
* 1. RESOLVE the target branch (explicit option > auto-detected default
|
|
328
|
+
* branch > `main` fallback), so the PUT + printed fallback target the real
|
|
329
|
+
* default branch (see module header, "DEFAULT BRANCH");
|
|
330
|
+
* 2. detect admin scope (cheapest path: {@link
|
|
331
|
+
* CIProviderContext.getRepoAdminScope});
|
|
332
|
+
* 3. ADMIN: PUT the base protection ({@link
|
|
333
|
+
* CIProviderContext.setBranchProtection}) AND create the deadlock-guard
|
|
334
|
+
* ruleset ({@link CIProviderContext.setBranchRuleset}); NON-ADMIN: print
|
|
335
|
+
* the exact ready-to-run `gh api` commands (PUT + ruleset POST) + the
|
|
336
|
+
* manual UI fallback.
|
|
179
337
|
*
|
|
180
|
-
* Never throws — a `setBranchProtection` rejection is reported as `failed
|
|
181
|
-
*
|
|
182
|
-
*
|
|
338
|
+
* Never throws — a `setBranchProtection` rejection is reported as `failed`, a
|
|
339
|
+
* `setBranchRuleset` rejection as `rulesetStatus: 'failed'`, so install-ci's
|
|
340
|
+
* later steps still run and the user sees the failure detail AND the manual
|
|
341
|
+
* command to retry by hand.
|
|
183
342
|
*/
|
|
184
343
|
export async function installCIBranchProtectionStep(
|
|
185
344
|
options: BranchProtectionStepOptions,
|
|
186
345
|
): Promise<BranchProtectionStepResult> {
|
|
187
|
-
const branch = options.branch ?? DEFAULT_PROTECTED_BRANCH;
|
|
188
346
|
const {ctx, log, fake} = options;
|
|
189
347
|
|
|
190
348
|
if (fake) {
|
|
191
|
-
|
|
192
|
-
|
|
349
|
+
const target = options.branch ?? DEFAULT_PROTECTED_BRANCH;
|
|
350
|
+
log(`branch protection: skipped (--fake; would have targeted ${target})`);
|
|
351
|
+
return {status: 'skipped-fake', branch: target};
|
|
193
352
|
}
|
|
194
353
|
if (!ctx.setBranchProtection) {
|
|
195
354
|
log(
|
|
196
355
|
'branch protection: skipped (provider does not implement the seam — non-GitHub host).',
|
|
197
356
|
);
|
|
198
|
-
return {
|
|
357
|
+
return {
|
|
358
|
+
status: 'skipped-no-seam',
|
|
359
|
+
branch: options.branch ?? DEFAULT_PROTECTED_BRANCH,
|
|
360
|
+
};
|
|
199
361
|
}
|
|
200
362
|
if (!ctx.repo) {
|
|
201
363
|
log('branch protection: skipped (repo unknown — pass --repo to enable).');
|
|
202
|
-
return {
|
|
364
|
+
return {
|
|
365
|
+
status: 'skipped-no-repo',
|
|
366
|
+
branch: options.branch ?? DEFAULT_PROTECTED_BRANCH,
|
|
367
|
+
};
|
|
203
368
|
}
|
|
204
369
|
|
|
205
370
|
const repo = ctx.repo;
|
|
371
|
+
const {branch, autoDetected} = await resolveTargetBranch(
|
|
372
|
+
ctx,
|
|
373
|
+
options.branch,
|
|
374
|
+
log,
|
|
375
|
+
);
|
|
376
|
+
|
|
206
377
|
// Cheapest scope-detection: one repo-permissions read (see module header).
|
|
207
378
|
// `undefined` ⇒ unknown ⇒ treat as non-admin (instruct, never silently
|
|
208
379
|
// attempt a call that would fail with 403 anyway).
|
|
@@ -213,20 +384,30 @@ export async function installCIBranchProtectionStep(
|
|
|
213
384
|
if (adminScope !== true) {
|
|
214
385
|
log('branch protection: no admin-scoped credential detected.');
|
|
215
386
|
log(
|
|
216
|
-
` install-ci will NOT call the GitHub API. Run
|
|
387
|
+
` install-ci will NOT call the GitHub API. Run these yourself (admin token required):`,
|
|
217
388
|
);
|
|
218
389
|
log(` ${formatManualBranchProtectionCommand(repo, branch)}`);
|
|
390
|
+
log(
|
|
391
|
+
` Then create the deadlock-guard ruleset (required check \`${VERIFY_CHECK_CONTEXT}\`, ` +
|
|
392
|
+
`do_not_enforce_on_create):`,
|
|
393
|
+
);
|
|
394
|
+
log(` ${formatManualBranchRulesetCommand(repo, branch)}`);
|
|
219
395
|
log(
|
|
220
396
|
` Manual UI fallback: https://github.com/${repo}/settings/branches ` +
|
|
221
397
|
`→ "Add branch protection rule" → branch name pattern \`${branch}\` ` +
|
|
222
|
-
`→ enable "Require
|
|
223
|
-
|
|
398
|
+
`→ enable "Require branches to be up to date", then add a RULESET ` +
|
|
399
|
+
`requiring \`${VERIFY_CHECK_CONTEXT}\` with "Do not require ... on creation".`,
|
|
224
400
|
);
|
|
225
401
|
log(
|
|
226
402
|
` (The ambient CI \`GITHUB_TOKEN\` is NEVER admin; a human admin's \`gh\` ` +
|
|
227
403
|
`or a configured PAT/App token with "Administration: write" is required.)`,
|
|
228
404
|
);
|
|
229
|
-
return {
|
|
405
|
+
return {
|
|
406
|
+
status: 'instructed',
|
|
407
|
+
adminScope,
|
|
408
|
+
branch,
|
|
409
|
+
branchAutoDetected: autoDetected,
|
|
410
|
+
};
|
|
230
411
|
}
|
|
231
412
|
|
|
232
413
|
const spec = buildBranchProtectionSpec();
|
|
@@ -234,15 +415,8 @@ export async function installCIBranchProtectionStep(
|
|
|
234
415
|
await ctx.setBranchProtection(branch, spec);
|
|
235
416
|
log(
|
|
236
417
|
`branch protection: set on ${repo}@${branch} ` +
|
|
237
|
-
`(
|
|
418
|
+
`(strict: true = require branches up to date).`,
|
|
238
419
|
);
|
|
239
|
-
log(
|
|
240
|
-
` NOTE: PRs opened BEFORE \`verify.yml\` lands on \`${branch}\` have ` +
|
|
241
|
-
`no \`${VERIFY_CHECK_CONTEXT}\` check waiting. Push/merge \`verify.yml\` ` +
|
|
242
|
-
`to \`${branch}\` first; existing in-flight PRs may need a no-op push ` +
|
|
243
|
-
`(or close/reopen) to re-trigger the check.`,
|
|
244
|
-
);
|
|
245
|
-
return {status: 'set', adminScope, branch};
|
|
246
420
|
} catch (err) {
|
|
247
421
|
const error = err instanceof Error ? err.message : String(err);
|
|
248
422
|
log(`branch protection: FAILED (${error})`);
|
|
@@ -250,6 +424,57 @@ export async function installCIBranchProtectionStep(
|
|
|
250
424
|
` Retry by hand (admin token required):\n` +
|
|
251
425
|
` ${formatManualBranchProtectionCommand(repo, branch)}`,
|
|
252
426
|
);
|
|
253
|
-
return {
|
|
427
|
+
return {
|
|
428
|
+
status: 'failed',
|
|
429
|
+
adminScope,
|
|
430
|
+
error,
|
|
431
|
+
branch,
|
|
432
|
+
branchAutoDetected: autoDetected,
|
|
433
|
+
};
|
|
254
434
|
}
|
|
435
|
+
|
|
436
|
+
// The deadlock guard: the required `verify` check lives in a RULESET with
|
|
437
|
+
// `do_not_enforce_on_create: true` (see module header), NOT in the classic
|
|
438
|
+
// PUT above — so a ref that predates the rule / the check's first run is not
|
|
439
|
+
// deadlocked by a never-reported required check.
|
|
440
|
+
let rulesetStatus: 'set' | 'failed' | 'skipped-no-seam';
|
|
441
|
+
let rulesetError: string | undefined;
|
|
442
|
+
if (!ctx.setBranchRuleset) {
|
|
443
|
+
rulesetStatus = 'skipped-no-seam';
|
|
444
|
+
log(
|
|
445
|
+
` deadlock guard: provider does not implement the ruleset seam; ` +
|
|
446
|
+
`required check \`${VERIFY_CHECK_CONTEXT}\` NOT enforced.`,
|
|
447
|
+
);
|
|
448
|
+
} else {
|
|
449
|
+
try {
|
|
450
|
+
await ctx.setBranchRuleset(buildBranchProtectionRuleset(branch));
|
|
451
|
+
rulesetStatus = 'set';
|
|
452
|
+
log(
|
|
453
|
+
`deadlock-guard ruleset: created on ${repo}@${branch} ` +
|
|
454
|
+
`(required check \`${VERIFY_CHECK_CONTEXT}\`, do_not_enforce_on_create: true).`,
|
|
455
|
+
);
|
|
456
|
+
log(
|
|
457
|
+
` NOTE: still push/merge \`verify.yml\` to \`${branch}\` so the check ` +
|
|
458
|
+
`can report on new PRs; the ruleset's create-exemption keeps existing ` +
|
|
459
|
+
`in-flight refs from deadlocking on the not-yet-run check.`,
|
|
460
|
+
);
|
|
461
|
+
} catch (err) {
|
|
462
|
+
rulesetError = err instanceof Error ? err.message : String(err);
|
|
463
|
+
rulesetStatus = 'failed';
|
|
464
|
+
log(`deadlock-guard ruleset: FAILED (${rulesetError})`);
|
|
465
|
+
log(
|
|
466
|
+
` Retry by hand (admin token required):\n` +
|
|
467
|
+
` ${formatManualBranchRulesetCommand(repo, branch)}`,
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
status: 'set',
|
|
474
|
+
adminScope,
|
|
475
|
+
branch,
|
|
476
|
+
branchAutoDetected: autoDetected,
|
|
477
|
+
rulesetStatus,
|
|
478
|
+
rulesetError,
|
|
479
|
+
};
|
|
255
480
|
}
|
package/src/install-ci-core.ts
CHANGED
|
@@ -152,7 +152,7 @@ export const DEFAULT_HARNESS: HarnessAdapter = 'pi';
|
|
|
152
152
|
* draining steadily rather than stampeding the model-provider rate limit + the
|
|
153
153
|
* arbiter CAS. Tunable via `install-ci --max-parallel <n>` / the config file.
|
|
154
154
|
*/
|
|
155
|
-
export const DEFAULT_MAX_PARALLEL =
|
|
155
|
+
export const DEFAULT_MAX_PARALLEL = 2;
|
|
156
156
|
|
|
157
157
|
/** The default install source: the published CLI via `npm install -g`. */
|
|
158
158
|
export const DEFAULT_INSTALL_SOURCE: InstallSource = 'registry';
|
|
@@ -212,6 +212,26 @@ export interface CIProviderContext {
|
|
|
212
212
|
* Throws on failure (caller catches + logs).
|
|
213
213
|
*/
|
|
214
214
|
setBranchProtection?(branch: string, spec: unknown): Promise<void>;
|
|
215
|
+
/**
|
|
216
|
+
* OPTIONALLY report the repo's DEFAULT BRANCH name (GitHub:
|
|
217
|
+
* `gh repo view --json defaultBranchRef`). Used by the Tier-1 branch-
|
|
218
|
+
* protection step so the PUT and the printed `gh api` fallback both target
|
|
219
|
+
* the repo's real default branch (a `master`-defaulted repo would 404 a
|
|
220
|
+
* hardcoded `main`). Returns `undefined` when the lookup fails / is empty
|
|
221
|
+
* (caller falls back to `main` and logs it). Absent on a provider that has
|
|
222
|
+
* no default-branch concept.
|
|
223
|
+
*/
|
|
224
|
+
getDefaultBranch?(): Promise<string | undefined>;
|
|
225
|
+
/**
|
|
226
|
+
* OPTIONALLY create/replace a branch RULESET (GitHub:
|
|
227
|
+
* `POST /repos/{owner}/{repo}/rulesets`) carrying the deadlock-guard
|
|
228
|
+
* `required_status_checks` rule with `do_not_enforce_on_create: true`. The
|
|
229
|
+
* `ruleset` body (`BranchProtectionRuleset` in
|
|
230
|
+
* `install-ci-branch-protection.ts`) is typed as `unknown` here to keep this
|
|
231
|
+
* core seam free of the branch-protection module's import. Absent on a non-
|
|
232
|
+
* GitHub provider. Throws on failure (caller catches + logs).
|
|
233
|
+
*/
|
|
234
|
+
setBranchRuleset?(ruleset: unknown): Promise<void>;
|
|
215
235
|
/**
|
|
216
236
|
* OPTIONALLY render the user's project-setup payload (the value of
|
|
217
237
|
* {@link CIConfigFile.projectSetup}[providerId]) to the NATIVE-SYNTAX
|
|
@@ -787,7 +807,7 @@ ${indent(modelsJsonStr, 8)}
|
|
|
787
807
|
if (config.installSource === 'workspace') {
|
|
788
808
|
installSteps = `\
|
|
789
809
|
- name: Setup pnpm
|
|
790
|
-
uses: pnpm/action-setup@
|
|
810
|
+
uses: pnpm/action-setup@v5
|
|
791
811
|
|
|
792
812
|
- name: Add pnpm global bin to PATH
|
|
793
813
|
shell: bash
|
|
@@ -903,9 +923,16 @@ runs:
|
|
|
903
923
|
using: composite
|
|
904
924
|
steps:
|
|
905
925
|
${projectSetupBlock} - name: Setup Node.js
|
|
906
|
-
uses: actions/setup-node@
|
|
926
|
+
uses: actions/setup-node@v5
|
|
907
927
|
with:
|
|
908
928
|
node-version: '22'
|
|
929
|
+
# setup-node@v5 auto-enables package-manager caching, which probes the
|
|
930
|
+
# project package manager DURING its own run when a lockfile is present,
|
|
931
|
+
# before that manager is on PATH, so the probe crashes ("Unable to
|
|
932
|
+
# locate executable file"). dorfl provisions ONLY its own runtime and
|
|
933
|
+
# leaves the project package-manager cache to the project (toolchain
|
|
934
|
+
# boundary), so disable it explicitly.
|
|
935
|
+
package-manager-cache: false
|
|
909
936
|
|
|
910
937
|
- name: Configure git identity
|
|
911
938
|
shell: bash
|
package/src/install-ci-github.ts
CHANGED
|
@@ -239,6 +239,66 @@ export class GitHubCIContext implements CIProviderContext {
|
|
|
239
239
|
return undefined;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Detect the repo's DEFAULT BRANCH via
|
|
244
|
+
* `gh repo view --json defaultBranchRef --jq .defaultBranchRef.name`. Returns
|
|
245
|
+
* `undefined` when `gh` is unavailable / the repo is unknown / the call fails
|
|
246
|
+
* / the name is empty — the caller falls back to `main` (and logs it) so a
|
|
247
|
+
* `master`-defaulted repo is never silently protected on a hardcoded `main`.
|
|
248
|
+
*/
|
|
249
|
+
async getDefaultBranch(): Promise<string | undefined> {
|
|
250
|
+
if (!this.repo) {
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
const result = this.runGh([
|
|
254
|
+
'repo',
|
|
255
|
+
'view',
|
|
256
|
+
'--json',
|
|
257
|
+
'defaultBranchRef',
|
|
258
|
+
'--jq',
|
|
259
|
+
'.defaultBranchRef.name',
|
|
260
|
+
]);
|
|
261
|
+
if (result === undefined || result.status !== 0) {
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
const name = result.stdout.trim();
|
|
265
|
+
return name === '' ? undefined : name;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Create the deadlock-guard RULESET via
|
|
270
|
+
* `POST /repos/{owner}/{repo}/rulesets`. The `ruleset` body is JSON-piped on
|
|
271
|
+
* stdin (`gh api --input -`) so the nested rule shape carries cleanly. Throws
|
|
272
|
+
* on a non-zero exit; caller logs + falls back. See the module header of
|
|
273
|
+
* `install-ci-branch-protection.ts` for why the required-check guard uses a
|
|
274
|
+
* ruleset while base protection stays a classic PUT.
|
|
275
|
+
*/
|
|
276
|
+
async setBranchRuleset(ruleset: unknown): Promise<void> {
|
|
277
|
+
if (!this.repo) {
|
|
278
|
+
throw new Error('cannot create branch ruleset: repo unknown');
|
|
279
|
+
}
|
|
280
|
+
const args = [
|
|
281
|
+
'api',
|
|
282
|
+
'-X',
|
|
283
|
+
'POST',
|
|
284
|
+
'-H',
|
|
285
|
+
'Accept: application/vnd.github+json',
|
|
286
|
+
`repos/${this.repo}/rulesets`,
|
|
287
|
+
'--input',
|
|
288
|
+
'-',
|
|
289
|
+
];
|
|
290
|
+
const result = this.runGhWithInput(args, JSON.stringify(ruleset));
|
|
291
|
+
if (result === undefined) {
|
|
292
|
+
throw new Error('gh not available; cannot create branch ruleset');
|
|
293
|
+
}
|
|
294
|
+
if (result.status !== 0) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`gh api POST repos/${this.repo}/rulesets failed: ` +
|
|
297
|
+
(result.stderr.trim() || 'unknown error'),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
242
302
|
/**
|
|
243
303
|
* Configure Tier-1 branch protection via
|
|
244
304
|
* `PUT /repos/{owner}/{repo}/branches/{branch}/protection`. The `spec` body
|
|
@@ -322,10 +382,16 @@ export class MemoryCIProviderContext implements CIProviderContext {
|
|
|
322
382
|
readonly repoSettings = new Map<string, boolean>();
|
|
323
383
|
/** Branch-protection spec recorded in memory (branch → spec). */
|
|
324
384
|
readonly branchProtections = new Map<string, unknown>();
|
|
385
|
+
/** Deadlock-guard rulesets recorded in memory (append-only). */
|
|
386
|
+
readonly rulesets: unknown[] = [];
|
|
325
387
|
/** What `getRepoAdminScope()` should return (tests configure). */
|
|
326
388
|
private readonly adminScope: boolean | undefined;
|
|
389
|
+
/** What `getDefaultBranch()` should return (tests configure). */
|
|
390
|
+
private readonly defaultBranch: string | undefined;
|
|
327
391
|
/** Whether `setBranchProtection` should throw (tests configure). */
|
|
328
392
|
private readonly branchProtectionError: string | undefined;
|
|
393
|
+
/** Whether `setBranchRuleset` should throw (tests configure). */
|
|
394
|
+
private readonly branchRulesetError: string | undefined;
|
|
329
395
|
|
|
330
396
|
constructor(options: {
|
|
331
397
|
workDir: string;
|
|
@@ -333,8 +399,15 @@ export class MemoryCIProviderContext implements CIProviderContext {
|
|
|
333
399
|
ghAvailable?: boolean;
|
|
334
400
|
/** Fixture admin-scope verdict; `undefined` ⇒ unknown. */
|
|
335
401
|
adminScope?: boolean;
|
|
402
|
+
/**
|
|
403
|
+
* Fixture default-branch name; `undefined` ⇒ lookup "fails" so the caller
|
|
404
|
+
* exercises its `main` fallback (models a `gh` that could not resolve it).
|
|
405
|
+
*/
|
|
406
|
+
defaultBranch?: string;
|
|
336
407
|
/** When set, `setBranchProtection` throws with this message (failure path). */
|
|
337
408
|
branchProtectionError?: string;
|
|
409
|
+
/** When set, `setBranchRuleset` throws with this message (failure path). */
|
|
410
|
+
branchRulesetError?: string;
|
|
338
411
|
/**
|
|
339
412
|
* The provider id this stub claims (default `github`). Tests of the
|
|
340
413
|
* project-setup seam may override to assert the orchestrator's
|
|
@@ -347,7 +420,9 @@ export class MemoryCIProviderContext implements CIProviderContext {
|
|
|
347
420
|
this.repo = options.repo;
|
|
348
421
|
this.ghAvailable = options.ghAvailable ?? false;
|
|
349
422
|
this.adminScope = options.adminScope;
|
|
423
|
+
this.defaultBranch = options.defaultBranch;
|
|
350
424
|
this.branchProtectionError = options.branchProtectionError;
|
|
425
|
+
this.branchRulesetError = options.branchRulesetError;
|
|
351
426
|
this.providerId = options.providerId ?? GITHUB_PROVIDER_ID;
|
|
352
427
|
}
|
|
353
428
|
|
|
@@ -367,10 +442,21 @@ export class MemoryCIProviderContext implements CIProviderContext {
|
|
|
367
442
|
return this.adminScope;
|
|
368
443
|
}
|
|
369
444
|
|
|
445
|
+
async getDefaultBranch(): Promise<string | undefined> {
|
|
446
|
+
return this.defaultBranch;
|
|
447
|
+
}
|
|
448
|
+
|
|
370
449
|
async setBranchProtection(branch: string, spec: unknown): Promise<void> {
|
|
371
450
|
if (this.branchProtectionError !== undefined) {
|
|
372
451
|
throw new Error(this.branchProtectionError);
|
|
373
452
|
}
|
|
374
453
|
this.branchProtections.set(branch, spec);
|
|
375
454
|
}
|
|
455
|
+
|
|
456
|
+
async setBranchRuleset(ruleset: unknown): Promise<void> {
|
|
457
|
+
if (this.branchRulesetError !== undefined) {
|
|
458
|
+
throw new Error(this.branchRulesetError);
|
|
459
|
+
}
|
|
460
|
+
this.rulesets.push(ruleset);
|
|
461
|
+
}
|
|
376
462
|
}
|