dorfl 0.14.2 → 0.15.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-ci-template.d.ts +17 -16
- package/dist/advance-ci-template.d.ts.map +1 -1
- package/dist/advance-ci-template.js +42 -53
- package/dist/advance-ci-template.js.map +1 -1
- package/dist/advance-drivers.d.ts +8 -0
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +28 -4
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-isolated.d.ts.map +1 -1
- package/dist/advance-isolated.js +3 -2
- package/dist/advance-isolated.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts +41 -40
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +309 -489
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-loop-driver.d.ts.map +1 -1
- package/dist/advance-loop-driver.js +3 -2
- package/dist/advance-loop-driver.js.map +1 -1
- package/dist/advance-treeless-publish.d.ts +31 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -1
- package/dist/advance-treeless-publish.js +40 -0
- package/dist/advance-treeless-publish.js.map +1 -1
- package/dist/advance.d.ts +63 -3
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +80 -12
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +9 -0
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +21 -1
- package/dist/advancing-lock.js.map +1 -1
- package/dist/agent-env.d.ts +61 -0
- package/dist/agent-env.d.ts.map +1 -0
- package/dist/agent-env.js +144 -0
- package/dist/agent-env.js.map +1 -0
- package/dist/agent-stop.d.ts +35 -0
- package/dist/agent-stop.d.ts.map +1 -1
- package/dist/agent-stop.js +62 -0
- package/dist/agent-stop.js.map +1 -1
- package/dist/apply-merge-action.d.ts +76 -34
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +339 -129
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-stuck-action.js +2 -2
- package/dist/apply-stuck-action.js.map +1 -1
- package/dist/ci-agent-result.d.ts +151 -0
- package/dist/ci-agent-result.d.ts.map +1 -0
- package/dist/ci-agent-result.js +314 -0
- package/dist/ci-agent-result.js.map +1 -0
- package/dist/ci-handoff-apply.d.ts +162 -0
- package/dist/ci-handoff-apply.d.ts.map +1 -0
- package/dist/ci-handoff-apply.js +497 -0
- package/dist/ci-handoff-apply.js.map +1 -0
- package/dist/ci-handoff-format.d.ts +369 -0
- package/dist/ci-handoff-format.d.ts.map +1 -0
- package/dist/ci-handoff-format.js +646 -0
- package/dist/ci-handoff-format.js.map +1 -0
- package/dist/ci-handoff-lfs.d.ts +146 -0
- package/dist/ci-handoff-lfs.d.ts.map +1 -0
- package/dist/ci-handoff-lfs.js +397 -0
- package/dist/ci-handoff-lfs.js.map +1 -0
- package/dist/ci-handoff.d.ts +99 -0
- package/dist/ci-handoff.d.ts.map +1 -0
- package/dist/ci-handoff.js +277 -0
- package/dist/ci-handoff.js.map +1 -0
- package/dist/ci-lock-outputs.d.ts +93 -0
- package/dist/ci-lock-outputs.d.ts.map +1 -0
- package/dist/ci-lock-outputs.js +223 -0
- package/dist/ci-lock-outputs.js.map +1 -0
- package/dist/ci-phase-build.d.ts +178 -0
- package/dist/ci-phase-build.d.ts.map +1 -0
- package/dist/ci-phase-build.js +989 -0
- package/dist/ci-phase-build.js.map +1 -0
- package/dist/ci-phase-driver.d.ts +247 -0
- package/dist/ci-phase-driver.d.ts.map +1 -0
- package/dist/ci-phase-driver.js +370 -0
- package/dist/ci-phase-driver.js.map +1 -0
- package/dist/ci-phase-intake.d.ts +172 -0
- package/dist/ci-phase-intake.d.ts.map +1 -0
- package/dist/ci-phase-intake.js +555 -0
- package/dist/ci-phase-intake.js.map +1 -0
- package/dist/ci-phase-tasking.d.ts +205 -0
- package/dist/ci-phase-tasking.d.ts.map +1 -0
- package/dist/ci-phase-tasking.js +906 -0
- package/dist/ci-phase-tasking.js.map +1 -0
- package/dist/ci-phase-treeless.d.ts +224 -0
- package/dist/ci-phase-treeless.d.ts.map +1 -0
- package/dist/ci-phase-treeless.js +1482 -0
- package/dist/ci-phase-treeless.js.map +1 -0
- package/dist/ci-read-token.d.ts +47 -0
- package/dist/ci-read-token.d.ts.map +1 -0
- package/dist/ci-read-token.js +80 -0
- package/dist/ci-read-token.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +447 -22
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.d.ts +10 -1
- package/dist/close-job-template.d.ts.map +1 -1
- package/dist/close-job-template.js +46 -14
- package/dist/close-job-template.js.map +1 -1
- package/dist/close-job.d.ts +20 -5
- package/dist/close-job.d.ts.map +1 -1
- package/dist/close-job.js +65 -12
- package/dist/close-job.js.map +1 -1
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +20 -1
- package/dist/complete.js.map +1 -1
- package/dist/config.d.ts +41 -44
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -4
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts +63 -15
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +75 -8
- package/dist/continue-branch.js.map +1 -1
- package/dist/crashed-run-save.d.ts +73 -0
- package/dist/crashed-run-save.d.ts.map +1 -0
- package/dist/crashed-run-save.js +86 -0
- package/dist/crashed-run-save.js.map +1 -0
- package/dist/cwd-section.d.ts.map +1 -1
- package/dist/cwd-section.js +10 -2
- package/dist/cwd-section.js.map +1 -1
- package/dist/decision-engine.d.ts +10 -0
- package/dist/decision-engine.d.ts.map +1 -1
- package/dist/decision-engine.js.map +1 -1
- package/dist/do-config.d.ts +2 -24
- package/dist/do-config.d.ts.map +1 -1
- package/dist/do-config.js +1 -33
- package/dist/do-config.js.map +1 -1
- package/dist/do.d.ts +149 -1
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +115 -25
- package/dist/do.js.map +1 -1
- package/dist/dorfl-item-template.d.ts +98 -0
- package/dist/dorfl-item-template.d.ts.map +1 -0
- package/dist/dorfl-item-template.js +504 -0
- package/dist/dorfl-item-template.js.map +1 -0
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +7 -5
- package/dist/env-config.js.map +1 -1
- package/dist/frontmatter.d.ts +52 -0
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +136 -2
- package/dist/frontmatter.js.map +1 -1
- package/dist/gc.d.ts +13 -0
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +16 -2
- package/dist/gc.js.map +1 -1
- package/dist/git.d.ts +18 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +70 -5
- package/dist/git.js.map +1 -1
- package/dist/harness.d.ts +44 -5
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +24 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/install-ci-action-pins.d.ts +123 -0
- package/dist/install-ci-action-pins.d.ts.map +1 -0
- package/dist/install-ci-action-pins.js +190 -0
- package/dist/install-ci-action-pins.js.map +1 -0
- package/dist/install-ci-actions-pr-creation.d.ts +70 -0
- package/dist/install-ci-actions-pr-creation.d.ts.map +1 -0
- package/dist/install-ci-actions-pr-creation.js +106 -0
- package/dist/install-ci-actions-pr-creation.js.map +1 -0
- package/dist/install-ci-capabilities/dorfl-item.d.ts +12 -0
- package/dist/install-ci-capabilities/dorfl-item.d.ts.map +1 -0
- package/dist/install-ci-capabilities/dorfl-item.js +26 -0
- package/dist/install-ci-capabilities/dorfl-item.js.map +1 -0
- package/dist/install-ci-core.d.ts +155 -37
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +304 -200
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +47 -2
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +113 -1
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-ci.d.ts +25 -6
- package/dist/install-ci.d.ts.map +1 -1
- package/dist/install-ci.js +72 -36
- package/dist/install-ci.js.map +1 -1
- package/dist/intake-marker.d.ts +6 -1
- package/dist/intake-marker.d.ts.map +1 -1
- package/dist/intake-marker.js.map +1 -1
- package/dist/intake-triage.d.ts.map +1 -1
- package/dist/intake-triage.js +6 -2
- package/dist/intake-triage.js.map +1 -1
- package/dist/intake-trigger-template.d.ts +31 -25
- package/dist/intake-trigger-template.d.ts.map +1 -1
- package/dist/intake-trigger-template.js +107 -292
- package/dist/intake-trigger-template.js.map +1 -1
- package/dist/intake.d.ts +186 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +533 -416
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +154 -1
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +759 -486
- package/dist/integration-core.js.map +1 -1
- package/dist/integrator.d.ts +12 -0
- package/dist/integrator.d.ts.map +1 -1
- package/dist/integrator.js +21 -0
- package/dist/integrator.js.map +1 -1
- package/dist/isolation.d.ts +12 -2
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +19 -8
- package/dist/isolation.js.map +1 -1
- package/dist/item-lock.d.ts +93 -11
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +233 -65
- package/dist/item-lock.js.map +1 -1
- package/dist/land-question-residue.d.ts +58 -0
- package/dist/land-question-residue.d.ts.map +1 -0
- package/dist/land-question-residue.js +95 -0
- package/dist/land-question-residue.js.map +1 -0
- package/dist/lifecycle-gather.d.ts +16 -0
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +54 -6
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/merge-answer.d.ts +41 -0
- package/dist/merge-answer.d.ts.map +1 -0
- package/dist/merge-answer.js +60 -0
- package/dist/merge-answer.js.map +1 -0
- package/dist/merge-question-surfacer.d.ts +105 -30
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +202 -91
- package/dist/merge-question-surfacer.js.map +1 -1
- package/dist/merge-question-tick.d.ts +64 -0
- package/dist/merge-question-tick.d.ts.map +1 -0
- package/dist/merge-question-tick.js +96 -0
- package/dist/merge-question-tick.js.map +1 -0
- package/dist/mint-adr.d.ts.map +1 -1
- package/dist/mint-adr.js +20 -2
- package/dist/mint-adr.js.map +1 -1
- package/dist/needs-attention.d.ts +72 -2
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +275 -38
- package/dist/needs-attention.js.map +1 -1
- package/dist/phase-recorder.d.ts +168 -0
- package/dist/phase-recorder.d.ts.map +1 -0
- package/dist/phase-recorder.js +218 -0
- package/dist/phase-recorder.js.map +1 -0
- package/dist/phase.d.ts +94 -0
- package/dist/phase.d.ts.map +1 -0
- package/dist/phase.js +148 -0
- package/dist/phase.js.map +1 -0
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +19 -6
- package/dist/pi-harness.js.map +1 -1
- package/dist/prepare.d.ts.map +1 -1
- package/dist/prepare.js +4 -0
- package/dist/prepare.js.map +1 -1
- package/dist/prompt.d.ts +4 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +48 -1
- package/dist/prompt.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +3 -1
- package/dist/protocol/WORK-CONTRACT.md +1 -1
- package/dist/reap-branches.d.ts +48 -0
- package/dist/reap-branches.d.ts.map +1 -1
- package/dist/reap-branches.js +64 -0
- package/dist/reap-branches.js.map +1 -1
- package/dist/ref-write.d.ts +198 -0
- package/dist/ref-write.d.ts.map +1 -0
- package/dist/ref-write.js +79 -0
- package/dist/ref-write.js.map +1 -0
- package/dist/repo-config.js +8 -8
- package/dist/repo-config.js.map +1 -1
- package/dist/repo-mirror.d.ts +36 -0
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +91 -1
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-verdict.d.ts +1 -1
- package/dist/review-verdict.js +1 -1
- package/dist/run.d.ts +7 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +28 -4
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +19 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +21 -8
- package/dist/scan.js.map +1 -1
- package/dist/sidecar.d.ts +14 -0
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +21 -0
- package/dist/sidecar.js.map +1 -1
- package/dist/single-job-warning.d.ts +63 -0
- package/dist/single-job-warning.d.ts.map +1 -0
- package/dist/single-job-warning.js +110 -0
- package/dist/single-job-warning.js.map +1 -0
- package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +3 -1
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +1 -1
- package/dist/spec-complete.d.ts +13 -6
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +23 -11
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +47 -14
- package/dist/start.js.map +1 -1
- package/dist/status.d.ts +12 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +11 -3
- package/dist/status.js.map +1 -1
- package/dist/tasking.d.ts +246 -1
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +452 -358
- package/dist/tasking.js.map +1 -1
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +21 -2
- package/dist/triage-persist.js.map +1 -1
- package/dist/verify-workflow-template.d.ts.map +1 -1
- package/dist/verify-workflow-template.js +8 -4
- package/dist/verify-workflow-template.js.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +4 -0
- package/dist/verify.js.map +1 -1
- package/dist/watch-session.d.ts +42 -13
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +52 -23
- package/dist/watch-session.js.map +1 -1
- package/dist/work-layout.d.ts +10 -4
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +11 -3
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +65 -4
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +159 -11
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
- package/src/advance-ci-template.ts +80 -81
- package/src/advance-drivers.ts +45 -6
- package/src/advance-isolated.ts +3 -5
- package/src/advance-lifecycle-template.ts +375 -538
- package/src/advance-loop-driver.ts +3 -5
- package/src/advance-treeless-publish.ts +61 -0
- package/src/advance.ts +145 -15
- package/src/advancing-lock.ts +36 -1
- package/src/agent-env.ts +174 -0
- package/src/agent-stop.ts +81 -0
- package/src/apply-merge-action.ts +401 -154
- package/src/apply-stuck-action.ts +2 -2
- package/src/ci-agent-result.ts +421 -0
- package/src/ci-handoff-apply.ts +737 -0
- package/src/ci-handoff-format.ts +926 -0
- package/src/ci-handoff-lfs.ts +519 -0
- package/src/ci-handoff.ts +392 -0
- package/src/ci-lock-outputs.ts +273 -0
- package/src/ci-phase-build.ts +1328 -0
- package/src/ci-phase-driver.ts +548 -0
- package/src/ci-phase-intake.ts +861 -0
- package/src/ci-phase-tasking.ts +1255 -0
- package/src/ci-phase-treeless.ts +2028 -0
- package/src/ci-read-token.ts +90 -0
- package/src/cli.ts +591 -29
- package/src/close-job-template.ts +52 -15
- package/src/close-job.ts +77 -12
- package/src/complete.ts +19 -1
- package/src/config.ts +43 -48
- package/src/continue-branch.ts +121 -16
- package/src/crashed-run-save.ts +162 -0
- package/src/cwd-section.ts +10 -0
- package/src/decision-engine.ts +10 -0
- package/src/do-config.ts +2 -57
- package/src/do.ts +170 -28
- package/src/dorfl-item-template.ts +556 -0
- package/src/env-config.ts +7 -5
- package/src/frontmatter.ts +155 -2
- package/src/gc.ts +27 -2
- package/src/git.ts +83 -5
- package/src/harness.ts +58 -6
- package/src/index.ts +50 -1
- package/src/install-ci-action-pins.ts +209 -0
- package/src/install-ci-actions-pr-creation.ts +186 -0
- package/src/install-ci-capabilities/dorfl-item.ts +33 -0
- package/src/install-ci-core.ts +388 -233
- package/src/install-ci-github.ts +149 -1
- package/src/install-ci.ts +83 -37
- package/src/intake-marker.ts +6 -1
- package/src/intake-triage.ts +7 -3
- package/src/intake-trigger-template.ts +126 -327
- package/src/intake.ts +725 -544
- package/src/integration-core.ts +975 -523
- package/src/integrator.ts +28 -0
- package/src/isolation.ts +29 -10
- package/src/item-lock.ts +308 -92
- package/src/land-question-residue.ts +122 -0
- package/src/lifecycle-gather.ts +82 -10
- package/src/merge-answer.ts +82 -0
- package/src/merge-question-surfacer.ts +320 -112
- package/src/merge-question-tick.ts +182 -0
- package/src/mint-adr.ts +28 -2
- package/src/needs-attention.ts +350 -42
- package/src/phase-recorder.ts +350 -0
- package/src/phase.ts +177 -0
- package/src/pi-harness.ts +25 -5
- package/src/prepare.ts +4 -0
- package/src/prompt.ts +68 -1
- package/src/reap-branches.ts +107 -0
- package/src/ref-write.ts +324 -0
- package/src/repo-config.ts +8 -8
- package/src/repo-mirror.ts +107 -1
- package/src/review-verdict.ts +1 -1
- package/src/run.ts +47 -6
- package/src/scan.ts +37 -7
- package/src/sidecar.ts +36 -0
- package/src/single-job-warning.ts +135 -0
- package/src/spec-complete.ts +36 -14
- package/src/start.ts +55 -16
- package/src/status.ts +29 -3
- package/src/tasking.ts +593 -378
- package/src/triage-persist.ts +24 -2
- package/src/verify-workflow-template.ts +8 -4
- package/src/verify.ts +4 -0
- package/src/watch-session.ts +86 -28
- package/src/work-layout.ts +11 -3
- package/src/workspace.ts +210 -14
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **The shared CI phase driver** (spec `ci-agent-job-without-write-token`, ADR
|
|
3
|
+
* `ci-agent-job-holds-no-write-token`, task `ci-split-build-path`).
|
|
4
|
+
*
|
|
5
|
+
* Every CI item runs as three jobs (`phase.ts`): lock, agent, apply. Each path
|
|
6
|
+
* split (`ci-phase-build.ts` first; intake, tasking and the tree-less rungs
|
|
7
|
+
* later) needs the same few steps around its own pipeline halves, and they live
|
|
8
|
+
* here so every split does them the same way:
|
|
9
|
+
*
|
|
10
|
+
* - the lock phase reads `<arbiter>/main` as the base (never the run's own
|
|
11
|
+
* commit), classifies the item in a throwaway worktree of that base, and
|
|
12
|
+
* publishes its trusted facts to `$GITHUB_OUTPUT` (`ci-lock-outputs.ts`);
|
|
13
|
+
* - the agent and apply phases read those facts back from
|
|
14
|
+
* {@link LOCK_OUTPUTS_ENV} and check them;
|
|
15
|
+
* - the lock-ownership rule (spec §8): the agent phase checks, read-only, that
|
|
16
|
+
* the item's lock ref still equals `lockSha` before launching anything, and
|
|
17
|
+
* the apply phase checks it before its FIRST write and writes nothing when
|
|
18
|
+
* it does not; every later lock operation is leased on the sha last observed
|
|
19
|
+
* ({@link releaseLockLeased});
|
|
20
|
+
* - the agent phase bounds the free text it hands over to the handoff limits;
|
|
21
|
+
* - the lock phase computes the agent job's timeout from the config at
|
|
22
|
+
* `baseSha` ({@link agentTimeoutMinutesAt}), and the apply phase decides what
|
|
23
|
+
* to do with the agent job's result before it reads any artifact
|
|
24
|
+
* ({@link resolveAgentResult}, `ci-agent-result.ts`): only `success` reads
|
|
25
|
+
* it (task `ci-split-agent-result-and-reruns`).
|
|
26
|
+
*
|
|
27
|
+
* **A handled outcome is green** (task `a-handled-surface-exits-green-in-ci`):
|
|
28
|
+
* the job's exit code is the only signal a CI run's colour carries, so every
|
|
29
|
+
* phase (build, tasking, tree-less, intake) exits 0 for an outcome it HANDLED
|
|
30
|
+
* and non-zero only for one it did not:
|
|
31
|
+
*
|
|
32
|
+
* - exit 0: the item was surfaced to needs-attention (including a rejected
|
|
33
|
+
* handoff whose surface landed: outcome `rejected` / `surfaced`); a lock
|
|
34
|
+
* phase that backed off because another run holds the lock (`lost`,
|
|
35
|
+
* `backed-off`); an agent or apply phase whose lock is no longer this run's
|
|
36
|
+
* (`stale-lock`, e.g. a "Re-run failed jobs" of a finished run), which
|
|
37
|
+
* writes nothing;
|
|
38
|
+
* - non-zero: a surface that did not land (`surface-unmoved`), a refused or
|
|
39
|
+
* failed publish or release (`stale-lease`, `publish-refused`,
|
|
40
|
+
* `publish-failed`, `release-refused`, `land-failed`), an agent that failed
|
|
41
|
+
* without a handoff (`agent-failed`), a usage or environment problem, and
|
|
42
|
+
* any unexpected error.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import {mkdtempSync, rmSync, appendFileSync} from 'node:fs';
|
|
46
|
+
import {tmpdir} from 'node:os';
|
|
47
|
+
import {join} from 'node:path';
|
|
48
|
+
import {
|
|
49
|
+
LOCK_OUTPUTS_ENV,
|
|
50
|
+
readLockOutputsFromEnv,
|
|
51
|
+
serializeLockOutputs,
|
|
52
|
+
type LockOutputs,
|
|
53
|
+
} from './ci-lock-outputs.js';
|
|
54
|
+
import {HandoffRejected, type HandoffRung} from './ci-handoff-format.js';
|
|
55
|
+
import {
|
|
56
|
+
actionsRunRefFromEnv,
|
|
57
|
+
decideAgentResult,
|
|
58
|
+
detectAgentTimeout,
|
|
59
|
+
githubTokenApiGet,
|
|
60
|
+
type AgentJobResult,
|
|
61
|
+
type AgentResultDecision,
|
|
62
|
+
type GithubApiGet,
|
|
63
|
+
} from './ci-agent-result.js';
|
|
64
|
+
import {DEFAULT_CONFIG, type Config} from './config.js';
|
|
65
|
+
import {git, run, runAsync} from './git.js';
|
|
66
|
+
import {pushLfsObjects} from './ci-handoff-lfs.js';
|
|
67
|
+
import type {HandoffLfsObject} from './ci-handoff.js';
|
|
68
|
+
import type {IntegrationCoreInput} from './integration-core.js';
|
|
69
|
+
import {workItemRel} from './work-layout.js';
|
|
70
|
+
import {
|
|
71
|
+
REPO_CONFIG_FILENAME,
|
|
72
|
+
REPO_CONFIG_FILENAME_LEGACY,
|
|
73
|
+
loadRepoConfigFromContent,
|
|
74
|
+
} from './repo-config.js';
|
|
75
|
+
import {itemLockRef, lockEntryFor} from './item-lock.js';
|
|
76
|
+
import {refWrite} from './ref-write.js';
|
|
77
|
+
|
|
78
|
+
export {LOCK_OUTPUTS_ENV};
|
|
79
|
+
|
|
80
|
+
/** A phase run that cannot go on: bad trusted inputs or a missing precondition. */
|
|
81
|
+
export class PhaseDriverError extends Error {
|
|
82
|
+
override readonly name = 'PhaseDriverError';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The instruction a phase prints when it refuses to act for a lock it no longer
|
|
87
|
+
* owns (spec §8, "Re-runs"): "Re-run failed jobs" replays old lock outputs, and
|
|
88
|
+
* the way to retry an item is a new run, which takes a fresh lock.
|
|
89
|
+
*/
|
|
90
|
+
export const STALE_LOCK_RETRY_HINT =
|
|
91
|
+
'The lock was released, reaped or re-taken since the lock job ran (for ' +
|
|
92
|
+
'example a "Re-run failed jobs" of a finished run). Nothing was written. To ' +
|
|
93
|
+
'retry the item, start a NEW run (the next tick, or a dispatch), which takes ' +
|
|
94
|
+
'a fresh lock; do not re-run the old jobs.';
|
|
95
|
+
|
|
96
|
+
/** Git environment for any checkout of hostile or unverified content: never smudge LFS. */
|
|
97
|
+
export function noSmudgeEnv(
|
|
98
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
99
|
+
): NodeJS.ProcessEnv {
|
|
100
|
+
return {...(env ?? process.env), GIT_LFS_SKIP_SMUDGE: '1'};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Fetch the arbiter's `main` into `<arbiter>/main` and return its tip: the
|
|
105
|
+
* phase's `baseSha`. The lock phase classifies against THIS tip, never the
|
|
106
|
+
* run's `github.sha` (a queued run may start hours after its dispatch).
|
|
107
|
+
*/
|
|
108
|
+
export async function fetchArbiterMain(params: {
|
|
109
|
+
cwd: string;
|
|
110
|
+
arbiter: string;
|
|
111
|
+
env?: NodeJS.ProcessEnv;
|
|
112
|
+
}): Promise<string> {
|
|
113
|
+
const {cwd, arbiter, env} = params;
|
|
114
|
+
await gitAsyncHard(
|
|
115
|
+
[
|
|
116
|
+
'fetch',
|
|
117
|
+
'--quiet',
|
|
118
|
+
'--no-tags',
|
|
119
|
+
arbiter,
|
|
120
|
+
`+refs/heads/main:refs/remotes/${arbiter}/main`,
|
|
121
|
+
],
|
|
122
|
+
cwd,
|
|
123
|
+
env,
|
|
124
|
+
);
|
|
125
|
+
return (
|
|
126
|
+
await gitAsyncHard(
|
|
127
|
+
['rev-parse', '--verify', `refs/remotes/${arbiter}/main^{commit}`],
|
|
128
|
+
cwd,
|
|
129
|
+
env,
|
|
130
|
+
)
|
|
131
|
+
).trim();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Run `fn` with a throwaway, detached worktree of `baseSha` (LFS smudging off),
|
|
136
|
+
* removed afterwards. The lock phase reads the ledger (the item's folder, its
|
|
137
|
+
* frontmatter, its sidecar) from here, so a checkout behind the arbiter cannot
|
|
138
|
+
* make it act on stale state. Only files are read: no repository code runs.
|
|
139
|
+
*/
|
|
140
|
+
export async function withBaseWorktree<T>(
|
|
141
|
+
params: {cwd: string; baseSha: string; env?: NodeJS.ProcessEnv},
|
|
142
|
+
fn: (dir: string) => Promise<T>,
|
|
143
|
+
): Promise<T> {
|
|
144
|
+
const {cwd, baseSha} = params;
|
|
145
|
+
const env = noSmudgeEnv(params.env);
|
|
146
|
+
const parent = mkdtempSync(join(tmpdir(), 'dorfl-base-'));
|
|
147
|
+
const dir = join(parent, 'tree');
|
|
148
|
+
await gitAsyncHard(
|
|
149
|
+
['worktree', 'add', '--quiet', '--detach', dir, baseSha],
|
|
150
|
+
cwd,
|
|
151
|
+
env,
|
|
152
|
+
);
|
|
153
|
+
try {
|
|
154
|
+
return await fn(dir);
|
|
155
|
+
} finally {
|
|
156
|
+
await runAsync('git', ['worktree', 'remove', '--force', dir], cwd, {env});
|
|
157
|
+
rmSync(parent, {recursive: true, force: true});
|
|
158
|
+
await runAsync('git', ['worktree', 'prune'], cwd, {env});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The sha of `ref` on the arbiter (`git ls-remote`, read-only), or `undefined`. */
|
|
163
|
+
export async function arbiterRefSha(params: {
|
|
164
|
+
cwd: string;
|
|
165
|
+
arbiter: string;
|
|
166
|
+
ref: string;
|
|
167
|
+
env?: NodeJS.ProcessEnv;
|
|
168
|
+
}): Promise<string | undefined> {
|
|
169
|
+
const out = await gitAsyncHard(
|
|
170
|
+
['ls-remote', params.arbiter, params.ref],
|
|
171
|
+
params.cwd,
|
|
172
|
+
params.env,
|
|
173
|
+
);
|
|
174
|
+
for (const line of out.split('\n')) {
|
|
175
|
+
const [sha, name] = line.split('\t');
|
|
176
|
+
if (name === params.ref && sha !== undefined && sha !== '') return sha;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The item's lock ref sha on the arbiter (read-only), or `undefined` when released. */
|
|
182
|
+
export function arbiterLockSha(params: {
|
|
183
|
+
cwd: string;
|
|
184
|
+
arbiter: string;
|
|
185
|
+
item: string;
|
|
186
|
+
env?: NodeJS.ProcessEnv;
|
|
187
|
+
}): Promise<string | undefined> {
|
|
188
|
+
return arbiterRefSha({
|
|
189
|
+
...params,
|
|
190
|
+
ref: itemLockRef(lockEntryFor(params.item)),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** The result of {@link checkLockOwnership}. */
|
|
195
|
+
export type LockOwnership =
|
|
196
|
+
| {owned: true}
|
|
197
|
+
| {owned: false; current: string | undefined; message: string};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The lock-ownership rule (spec §8): does the item's lock ref on the arbiter
|
|
201
|
+
* still equal `lockSha`, the sha the lock job produced? Read-only. The lock
|
|
202
|
+
* `holder` is the same bot for every CI run, so the sha is what tells runs
|
|
203
|
+
* apart. When it does not match, the message carries the retry instruction.
|
|
204
|
+
*/
|
|
205
|
+
export async function checkLockOwnership(params: {
|
|
206
|
+
cwd: string;
|
|
207
|
+
arbiter: string;
|
|
208
|
+
item: string;
|
|
209
|
+
lockSha: string;
|
|
210
|
+
env?: NodeJS.ProcessEnv;
|
|
211
|
+
}): Promise<LockOwnership> {
|
|
212
|
+
const current = await arbiterLockSha(params);
|
|
213
|
+
if (current === params.lockSha) return {owned: true};
|
|
214
|
+
const now =
|
|
215
|
+
current === undefined ? 'is released' : `now points at ${current}`;
|
|
216
|
+
return {
|
|
217
|
+
owned: false,
|
|
218
|
+
current,
|
|
219
|
+
message:
|
|
220
|
+
`the lock of ${params.item} ${now}, not the lock job's ` +
|
|
221
|
+
`${params.lockSha}. ${STALE_LOCK_RETRY_HINT}`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Release the item's lock with a LEASE on `expectedSha`, the sha this phase last
|
|
227
|
+
* observed, so a phase never releases a lock another run holds now. Returns
|
|
228
|
+
* whether the ref was deleted (a lost lease leaves it untouched).
|
|
229
|
+
*/
|
|
230
|
+
export async function releaseLockLeased(params: {
|
|
231
|
+
cwd: string;
|
|
232
|
+
arbiter: string;
|
|
233
|
+
item: string;
|
|
234
|
+
expectedSha: string;
|
|
235
|
+
env?: NodeJS.ProcessEnv;
|
|
236
|
+
}): Promise<{released: boolean; message: string}> {
|
|
237
|
+
const ref = itemLockRef(lockEntryFor(params.item));
|
|
238
|
+
const del = await refWrite.deleteLockRef({
|
|
239
|
+
arbiter: params.arbiter,
|
|
240
|
+
ref,
|
|
241
|
+
expectedSha: params.expectedSha,
|
|
242
|
+
cwd: params.cwd,
|
|
243
|
+
env: params.env,
|
|
244
|
+
});
|
|
245
|
+
if (del.status === 0) {
|
|
246
|
+
await runAsync('git', ['update-ref', '-d', ref], params.cwd, {
|
|
247
|
+
env: params.env,
|
|
248
|
+
});
|
|
249
|
+
return {released: true, message: `released ${ref}`};
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
released: false,
|
|
253
|
+
message: `the leased release of ${ref} was rejected: ${del.stderr.trim()}`,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Publish the lock phase's facts: append them to the `$GITHUB_OUTPUT` file when
|
|
259
|
+
* one is given, else print them (a local replay). The serializer refuses any
|
|
260
|
+
* value that is not a sha, an enum, a boolean, a bounded integer or a
|
|
261
|
+
* dorfl-derived name.
|
|
262
|
+
*/
|
|
263
|
+
export function emitLockOutputs(
|
|
264
|
+
facts: LockOutputs,
|
|
265
|
+
params: {githubOutput?: string; note: (message: string) => void},
|
|
266
|
+
): void {
|
|
267
|
+
const lines = serializeLockOutputs(facts);
|
|
268
|
+
if (params.githubOutput !== undefined && params.githubOutput !== '') {
|
|
269
|
+
appendFileSync(params.githubOutput, lines);
|
|
270
|
+
} else {
|
|
271
|
+
params.note(`lock outputs:\n${lines}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** The lock outputs an agent or apply phase acts on, checked for its rung. */
|
|
276
|
+
export interface HeldLock {
|
|
277
|
+
baseSha: string;
|
|
278
|
+
lockSha: string;
|
|
279
|
+
rung: HandoffRung;
|
|
280
|
+
continueTip?: string;
|
|
281
|
+
handoffName?: string;
|
|
282
|
+
needsAgent?: boolean;
|
|
283
|
+
agentTimeoutMinutes?: number;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Read the lock outputs (given, or from {@link LOCK_OUTPUTS_ENV}) and require
|
|
288
|
+
* what an agent or apply phase needs: the lock was acquired, on `rung`, with a
|
|
289
|
+
* base and a lock sha. Throws {@link PhaseDriverError} otherwise.
|
|
290
|
+
*/
|
|
291
|
+
export function requireHeldLock(params: {
|
|
292
|
+
lockOutputs?: LockOutputs;
|
|
293
|
+
env: NodeJS.ProcessEnv;
|
|
294
|
+
rung: HandoffRung;
|
|
295
|
+
}): HeldLock {
|
|
296
|
+
const facts = params.lockOutputs ?? readLockOutputsFromEnv(params.env);
|
|
297
|
+
if (facts.acquired !== true) {
|
|
298
|
+
throw new PhaseDriverError(
|
|
299
|
+
'the lock job did not acquire the item (acquired != true); nothing to do',
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
if (facts.rung !== params.rung) {
|
|
303
|
+
throw new PhaseDriverError(
|
|
304
|
+
`the lock job classified the rung ${String(facts.rung)}, not ${params.rung}`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
if (facts.baseSha === undefined || facts.lockSha === undefined) {
|
|
308
|
+
throw new PhaseDriverError(
|
|
309
|
+
'the lock outputs carry no baseSha or no lockSha',
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
baseSha: facts.baseSha,
|
|
314
|
+
lockSha: facts.lockSha,
|
|
315
|
+
rung: facts.rung,
|
|
316
|
+
continueTip: facts.continueTip,
|
|
317
|
+
handoffName: facts.handoffName,
|
|
318
|
+
needsAgent: facts.needsAgent,
|
|
319
|
+
agentTimeoutMinutes: facts.agentTimeoutMinutes,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Control characters the handoff record refuses (all but tab, LF, CR). */
|
|
324
|
+
// eslint-disable-next-line no-control-regex
|
|
325
|
+
const HANDOFF_TEXT_CONTROL_RE =
|
|
326
|
+
/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Bound one free-text product for the handoff record: drop the control
|
|
330
|
+
* characters the record refuses (an agent's output may carry terminal escapes)
|
|
331
|
+
* and cut it to `maxChars`, marking the cut. The apply phase re-validates it;
|
|
332
|
+
* this only keeps a long but honest summary from failing the whole handoff.
|
|
333
|
+
*/
|
|
334
|
+
export function boundHandoffText(text: string, maxChars: number): string {
|
|
335
|
+
const clean = text.replace(HANDOFF_TEXT_CONTROL_RE, '');
|
|
336
|
+
if (clean.length <= maxChars) return clean;
|
|
337
|
+
const marker = '\n\n[truncated by dorfl for the CI handoff]';
|
|
338
|
+
return clean.slice(0, Math.max(0, maxChars - marker.length)) + marker;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* The `$RUNNER_TEMP` the apply phase reads the artifact under (the handoff
|
|
343
|
+
* reader refuses a directory outside it).
|
|
344
|
+
*/
|
|
345
|
+
export function runnerTempFrom(
|
|
346
|
+
given: string | undefined,
|
|
347
|
+
env: NodeJS.ProcessEnv,
|
|
348
|
+
): string {
|
|
349
|
+
const dir = given ?? env.RUNNER_TEMP;
|
|
350
|
+
if (dir === undefined || dir === '') {
|
|
351
|
+
throw new PhaseDriverError(
|
|
352
|
+
'RUNNER_TEMP is not set (the apply phase reads the handoff only from under it)',
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
return dir;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* The repository config committed at `baseSha` (`dorfl.json`, else the legacy
|
|
360
|
+
* dotfile): the trusted config a phase acts on, never the checkout's or a
|
|
361
|
+
* bundle's. `undefined` when neither file exists or the first one found does
|
|
362
|
+
* not parse.
|
|
363
|
+
*/
|
|
364
|
+
export function repoConfigAt(
|
|
365
|
+
cwd: string,
|
|
366
|
+
baseSha: string,
|
|
367
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
368
|
+
): Partial<Config> | undefined {
|
|
369
|
+
for (const name of [REPO_CONFIG_FILENAME, REPO_CONFIG_FILENAME_LEGACY]) {
|
|
370
|
+
const r = run('git', ['cat-file', 'blob', `${baseSha}:${name}`], cwd, {
|
|
371
|
+
env,
|
|
372
|
+
});
|
|
373
|
+
if (r.status !== 0) continue;
|
|
374
|
+
try {
|
|
375
|
+
return loadRepoConfigFromContent(r.stdout, `${baseSha}:${name}`).config;
|
|
376
|
+
} catch {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* The agent job's timeout in minutes, as the lock phase publishes it
|
|
385
|
+
* (`agentTimeoutMinutes`): today's `agentDeadlineMinutes +
|
|
386
|
+
* checkpointHeadroomMinutes`, each read from the config at `baseSha` and falling
|
|
387
|
+
* back to its default (60 and 30) when absent or outside its valid range
|
|
388
|
+
* ([1, 240] and [10, 60]).
|
|
389
|
+
*/
|
|
390
|
+
export function agentTimeoutMinutesAt(
|
|
391
|
+
cwd: string,
|
|
392
|
+
baseSha: string,
|
|
393
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
394
|
+
): number {
|
|
395
|
+
const config = repoConfigAt(cwd, baseSha, env) ?? {};
|
|
396
|
+
const pick = (v: unknown, min: number, max: number, fallback: number) =>
|
|
397
|
+
typeof v === 'number' && Number.isInteger(v) && v >= min && v <= max
|
|
398
|
+
? v
|
|
399
|
+
: fallback;
|
|
400
|
+
return (
|
|
401
|
+
pick(
|
|
402
|
+
config.agentDeadlineMinutes,
|
|
403
|
+
1,
|
|
404
|
+
240,
|
|
405
|
+
DEFAULT_CONFIG.agentDeadlineMinutes,
|
|
406
|
+
) +
|
|
407
|
+
pick(
|
|
408
|
+
config.checkpointHeadroomMinutes,
|
|
409
|
+
10,
|
|
410
|
+
60,
|
|
411
|
+
DEFAULT_CONFIG.checkpointHeadroomMinutes,
|
|
412
|
+
)
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Decide what the apply phase does with the agent job's result (decision 5),
|
|
418
|
+
* BEFORE any artifact is read. For `cancelled` it reads the agent job from the
|
|
419
|
+
* Actions API with `GITHUB_TOKEN` (or the injected `api`) to tell a timeout from
|
|
420
|
+
* a real cancel. `agentTimeoutMinutes` is the trusted lock output
|
|
421
|
+
* (`--agent-timeout-minutes`, else the lock outputs), never the artifact's.
|
|
422
|
+
* Nothing it reads from the API is ever printed.
|
|
423
|
+
*/
|
|
424
|
+
export function resolveAgentResult(params: {
|
|
425
|
+
result: AgentJobResult;
|
|
426
|
+
held: HeldLock;
|
|
427
|
+
agentTimeoutMinutes?: number;
|
|
428
|
+
api?: GithubApiGet;
|
|
429
|
+
env: NodeJS.ProcessEnv;
|
|
430
|
+
}): Promise<AgentResultDecision> {
|
|
431
|
+
return decideAgentResult({
|
|
432
|
+
result: params.result,
|
|
433
|
+
needsAgent: params.held.needsAgent,
|
|
434
|
+
detectTimeout: () =>
|
|
435
|
+
detectAgentTimeout({
|
|
436
|
+
get: params.api ?? githubTokenApiGet(params.env),
|
|
437
|
+
run: actionsRunRefFromEnv(params.env),
|
|
438
|
+
agentTimeoutMinutes:
|
|
439
|
+
params.agentTimeoutMinutes ?? params.held.agentTimeoutMinutes,
|
|
440
|
+
}),
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** The result of {@link pushHandoffLfs}. */
|
|
445
|
+
export type HandoffLfsPush =
|
|
446
|
+
| {ok: true}
|
|
447
|
+
| {
|
|
448
|
+
ok: false;
|
|
449
|
+
/** True when an object broke a handoff rule (the handoff is rejected). */
|
|
450
|
+
rejected: boolean;
|
|
451
|
+
reason: string;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Run a shared lock acquire whose refusal is BOTH noted and returned as its
|
|
456
|
+
* `message`, which the phase returns as its result line and the CLI prints:
|
|
457
|
+
* its notes are held back until it returns, then every one is passed on except
|
|
458
|
+
* a refusal's own message, so that line appears once in the job log (task
|
|
459
|
+
* `ci-phase-logs-each-line-once`). An acquire is one short arbiter round trip,
|
|
460
|
+
* so holding its notes back does not delay the log noticeably.
|
|
461
|
+
*/
|
|
462
|
+
export async function acquireNotingOnce<
|
|
463
|
+
R extends {exitCode: number; message: string},
|
|
464
|
+
>(
|
|
465
|
+
note: (message: string) => void,
|
|
466
|
+
acquire: (note: (message: string) => void) => Promise<R>,
|
|
467
|
+
): Promise<R> {
|
|
468
|
+
const held: string[] = [];
|
|
469
|
+
const result = await acquire((m) => held.push(m));
|
|
470
|
+
for (const m of held) {
|
|
471
|
+
if (result.exitCode === 0 || m !== result.message) note(m);
|
|
472
|
+
}
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Push the validated handoff's Git LFS objects to the arbiter (decision 6),
|
|
478
|
+
* the apply phase's FIRST write: before any ref of any intent (a continue push,
|
|
479
|
+
* the land's pushes, a WIP branch save), so a ref never lands pointing at a
|
|
480
|
+
* missing object. Pushed from the apply checkout of the trusted base, so
|
|
481
|
+
* `.lfsconfig` and the endpoint come from main. The caller surfaces the item on
|
|
482
|
+
* a failure (`rejected` when an object broke a handoff rule).
|
|
483
|
+
*/
|
|
484
|
+
export async function pushHandoffLfs(params: {
|
|
485
|
+
cwd: string;
|
|
486
|
+
arbiter: string;
|
|
487
|
+
objects: readonly HandoffLfsObject[];
|
|
488
|
+
env: NodeJS.ProcessEnv;
|
|
489
|
+
note: (message: string) => void;
|
|
490
|
+
}): Promise<HandoffLfsPush> {
|
|
491
|
+
const {cwd, arbiter, objects, env, note} = params;
|
|
492
|
+
if (objects.length === 0) return {ok: true};
|
|
493
|
+
let pushed: {status: number; stderr: string};
|
|
494
|
+
try {
|
|
495
|
+
pushed = await pushLfsObjects({cwd, arbiter, objects, env});
|
|
496
|
+
} catch (err) {
|
|
497
|
+
if (!(err instanceof HandoffRejected)) throw err;
|
|
498
|
+
note(err.message);
|
|
499
|
+
return {ok: false, rejected: true, reason: err.message};
|
|
500
|
+
}
|
|
501
|
+
if (pushed.status !== 0) {
|
|
502
|
+
const reason =
|
|
503
|
+
`the ${objects.length} Git LFS object(s) of the handoff ` +
|
|
504
|
+
`could not be pushed to ${arbiter} (${pushed.stderr.trim().slice(0, 500)}), ` +
|
|
505
|
+
'so no branch of the work was pushed';
|
|
506
|
+
note(reason);
|
|
507
|
+
return {ok: false, rejected: false, reason};
|
|
508
|
+
}
|
|
509
|
+
note(`pushed ${objects.length} Git LFS object(s) to ${arbiter}`);
|
|
510
|
+
return {ok: true};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* The done-move source folder of the task AT `baseSha` (trusted), which picks
|
|
515
|
+
* the land half's ledger reconcile arms exactly as the agent's `complete` did.
|
|
516
|
+
*/
|
|
517
|
+
export function taskSourceAtBase(
|
|
518
|
+
cwd: string,
|
|
519
|
+
baseSha: string,
|
|
520
|
+
slug: string,
|
|
521
|
+
env: NodeJS.ProcessEnv,
|
|
522
|
+
): IntegrationCoreInput['source'] {
|
|
523
|
+
const folders = ['tasks-ready', 'tasks-backlog', 'done'] as const;
|
|
524
|
+
for (const folder of folders) {
|
|
525
|
+
const spec = `${baseSha}:${workItemRel(folder, `${slug}.md`)}`;
|
|
526
|
+
try {
|
|
527
|
+
git(['cat-file', '-e', spec], cwd, {env});
|
|
528
|
+
return folder;
|
|
529
|
+
} catch {
|
|
530
|
+
// not in this folder
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return 'tasks-ready';
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async function gitAsyncHard(
|
|
537
|
+
args: string[],
|
|
538
|
+
cwd: string,
|
|
539
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
540
|
+
): Promise<string> {
|
|
541
|
+
const r = await runAsync('git', args, cwd, {env});
|
|
542
|
+
if (r.status !== 0) {
|
|
543
|
+
throw new Error(
|
|
544
|
+
`git ${args.join(' ')} failed (exit ${r.status}): ${r.stderr.trim()}`,
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
return r.stdout;
|
|
548
|
+
}
|