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
|
@@ -14,6 +14,15 @@
|
|
|
14
14
|
* a normal (non-fork-restricted) `GITHUB_TOKEN` that can actually close issues
|
|
15
15
|
* — whereas a `pull_request` event from a FORK gets a read-only token and
|
|
16
16
|
* cannot close. (See the `## Decisions` block in the task.)
|
|
17
|
+
* - PLUS an hourly `schedule:` (the same `'0 * * * *'` cron the generated
|
|
18
|
+
* `advance-lifecycle` uses) and `workflow_dispatch:` (task
|
|
19
|
+
* `close-job-also-runs-on-a-schedule`): GitHub starts NO workflow for a push
|
|
20
|
+
* made with a job's `GITHUB_TOKEN`, so a land the CI apply job pushes itself
|
|
21
|
+
* (merge mode, an answered `merge` question, a tree-less publish) never fires
|
|
22
|
+
* the `push` trigger. `close-merged-issues` keeps no state (every run
|
|
23
|
+
* re-derives from `main` what is complete) and skips an issue that is already
|
|
24
|
+
* closed, so the timed run is safe and quiet; it catches those lands within
|
|
25
|
+
* the hour. The `push` trigger stays so a human-merged PR closes at once.
|
|
17
26
|
* - The job INVOKES the close machinery via `dorfl close-merged-issues`,
|
|
18
27
|
* which CONSUMES the UNCHANGED engine pieces: the resolution
|
|
19
28
|
* (`resolveClosingIssue`), the "spec complete?" query (`prd-complete-query`,
|
|
@@ -33,15 +42,19 @@
|
|
|
33
42
|
* the test generates this artifact under `--fake` and asserts every invariant.
|
|
34
43
|
*/
|
|
35
44
|
|
|
45
|
+
import {ACTION_PINS, pinnedUses} from './install-ci-action-pins.js';
|
|
36
46
|
import type {ResolvedCIConfig} from './install-ci-core.js';
|
|
37
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
WRITER_SETUP_ACTION_USES,
|
|
49
|
+
shouldDropCheckoutCredentials,
|
|
50
|
+
} from './install-ci-core.js';
|
|
38
51
|
|
|
39
52
|
/** The capability id (the registry key + the emitted workflow file stem). */
|
|
40
53
|
export const CLOSE_JOB_CAPABILITY_ID = 'close-job';
|
|
41
54
|
|
|
42
55
|
/** The wizard-facing label for the close-job capability. */
|
|
43
56
|
export const CLOSE_JOB_CAPABILITY_LABEL =
|
|
44
|
-
'Close issues when their work lands (the close-job: on a merge to main)';
|
|
57
|
+
'Close issues when their work lands (the close-job: on a merge to main, and hourly)';
|
|
45
58
|
|
|
46
59
|
/** The repo-relative path (under the output base) of the emitted workflow. */
|
|
47
60
|
export const CLOSE_JOB_WORKFLOW_PATH = 'workflows/close-job.yml';
|
|
@@ -62,8 +75,7 @@ export function generateCloseJobWorkflow(config: ResolvedCIConfig): string {
|
|
|
62
75
|
? `
|
|
63
76
|
# Public repository: every git read works without a token, and this
|
|
64
77
|
# job cannot push, so do not leave the token in .git/config for later
|
|
65
|
-
# steps (the setup action
|
|
66
|
-
# close-merged-issues) to read.
|
|
78
|
+
# steps (the setup action and close-merged-issues) to read.
|
|
67
79
|
persist-credentials: false`
|
|
68
80
|
: '';
|
|
69
81
|
return `\
|
|
@@ -78,6 +90,13 @@ export function generateCloseJobWorkflow(config: ResolvedCIConfig): string {
|
|
|
78
90
|
# actually CLOSE issues — a \`pull_request\` event from a FORK gets a read-only
|
|
79
91
|
# token and could not close (a real limitation we deliberately avoid).
|
|
80
92
|
#
|
|
93
|
+
# PLUS an HOURLY schedule (and workflow_dispatch for a manual catch-up). GitHub
|
|
94
|
+
# starts NO workflow for a push made with a job's GITHUB_TOKEN, so a land the CI
|
|
95
|
+
# pushes itself (merge mode, an answered \`merge\` question, a tree-less publish)
|
|
96
|
+
# never fires the push trigger above. The schedule closes those issues within the
|
|
97
|
+
# hour. It is safe to repeat: \`close-merged-issues\` keeps no state (every run
|
|
98
|
+
# re-derives from main what is complete) and skips an issue already closed.
|
|
99
|
+
#
|
|
81
100
|
# WHAT IT DOES — \`dorfl close-merged-issues\` resolves which source issue(s)
|
|
82
101
|
# the landed work closes and closes them. CI owns ONLY this job + the trigger; the
|
|
83
102
|
# command CONSUMES the engine's UNCHANGED pieces and re-implements none of them:
|
|
@@ -106,6 +125,12 @@ on:
|
|
|
106
125
|
push:
|
|
107
126
|
branches:
|
|
108
127
|
- main
|
|
128
|
+
# Hourly (the same cadence as advance-lifecycle): a land the CI pushes with its
|
|
129
|
+
# GITHUB_TOKEN starts no workflow, so this catches the issues those lands close.
|
|
130
|
+
schedule:
|
|
131
|
+
- cron: '0 * * * *'
|
|
132
|
+
# Manual catch-up.
|
|
133
|
+
workflow_dispatch:
|
|
109
134
|
|
|
110
135
|
# Serialise overlapping close ticks on main; the close-job mutates only the ISSUE
|
|
111
136
|
# (not the main-CAS), so this just avoids redundant concurrent passes.
|
|
@@ -113,20 +138,25 @@ concurrency:
|
|
|
113
138
|
group: close-job-\${{ github.ref }}
|
|
114
139
|
cancel-in-progress: false
|
|
115
140
|
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
issues: write
|
|
141
|
+
# Nothing at workflow level: the job grants its own scopes. NO \`workflows\`
|
|
142
|
+
# permission: the running job can NEVER edit the workflows tree under .github
|
|
143
|
+
# (US #9).
|
|
144
|
+
permissions: {}
|
|
121
145
|
|
|
122
146
|
jobs:
|
|
123
147
|
close-merged-issues:
|
|
124
148
|
runs-on: ubuntu-latest
|
|
149
|
+
# It needs only to READ the work/ tree and CLOSE issues.
|
|
150
|
+
permissions:
|
|
151
|
+
contents: read
|
|
152
|
+
issues: write
|
|
125
153
|
steps:
|
|
126
|
-
- uses:
|
|
154
|
+
- uses: ${pinnedUses(ACTION_PINS.checkout)}
|
|
127
155
|
with:
|
|
128
156
|
fetch-depth: 0${checkoutCredentials}
|
|
129
|
-
|
|
157
|
+
# The writer role (Node and dorfl only): this job runs no agent, so it
|
|
158
|
+
# installs no harness, no provider key and no project-setup hook.
|
|
159
|
+
- uses: ${WRITER_SETUP_ACTION_USES}
|
|
130
160
|
- name: close issues whose work has landed on main
|
|
131
161
|
# In-place in this checkout (no --isolated/--remote): the CI container IS
|
|
132
162
|
# the isolation. Resolves the closing issue(s) from the work/ tree, runs
|
|
@@ -188,11 +218,18 @@ export function validateCloseJobWorkflow(text: string): CloseJobValidation {
|
|
|
188
218
|
operative,
|
|
189
219
|
), 'must NOT use the `pull_request` trigger (a fork PR gets a read-only token ' +
|
|
190
220
|
'that cannot close; `push: [main]` is the chosen trigger).');
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
221
|
+
// AND an hourly schedule + workflow_dispatch: GitHub starts no workflow for a
|
|
222
|
+
// push made with a job's GITHUB_TOKEN, so a land the CI pushes itself never
|
|
223
|
+
// fires the push trigger; the timed run closes those issues. Operative lines
|
|
224
|
+
// only: the header comment mentions the schedule.
|
|
225
|
+
require('trigger-cron', /\bschedule:\s*[\s\S]*?-\s*cron:/.test(
|
|
226
|
+
operative,
|
|
227
|
+
), 'must ALSO trigger on a cron schedule (`on.schedule[].cron`): a land the ' +
|
|
228
|
+
'CI pushes with its GITHUB_TOKEN starts no workflow, so only the timed run ' +
|
|
229
|
+
'closes its issue.');
|
|
230
|
+
require('trigger-workflow-dispatch', /\bworkflow_dispatch:/.test(
|
|
194
231
|
operative,
|
|
195
|
-
), '
|
|
232
|
+
), 'must trigger on `workflow_dispatch` (manual catch-up).');
|
|
196
233
|
require('no-answer-loop-push-trigger', !/work\/questions\//.test(
|
|
197
234
|
text,
|
|
198
235
|
), 'the close-job must NOT carry the `push work/questions/**` (answer-loop) ' +
|
package/src/close-job.ts
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* fanned task reaches the number via `task.spec: → work/specs/<spec>.md spec
|
|
11
11
|
* issue:`, and `spec:` WINS on a (hand-edited) conflict;
|
|
12
12
|
* - the QUERY — {@link isSpecComplete} (`spec-complete.ts`, task
|
|
13
|
-
* `prd-complete-query`, done): a spec is COMPLETE iff ≥1 `spec:<slug>` task
|
|
14
|
-
*
|
|
13
|
+
* `prd-complete-query`, done): a spec is COMPLETE iff ≥1 `spec:<slug>` task is
|
|
14
|
+
* in `work/tasks/done/` AND every other one is in `work/tasks/cancelled/` (a
|
|
15
|
+
* task staged in backlog, ready or in progress keeps it open);
|
|
15
16
|
* - the CLOSE — {@link IssueProvider.closeIssue} (`issue-provider.ts`, the atomic
|
|
16
17
|
* comment+close seam intake already uses): NO direct `gh` in this core; any
|
|
17
18
|
* informational comment rides the SAME close call (never the PR seam
|
|
@@ -25,13 +26,27 @@
|
|
|
25
26
|
* `spec:<slug>` tasks are in `work/done/` (reason `completed`).
|
|
26
27
|
*
|
|
27
28
|
* A spec whose query is NOT yet complete is left OPEN (the final fanned task's
|
|
28
|
-
* merge tick closes it). A lone task still outside `work/done/` is skipped.
|
|
29
|
+
* merge tick closes it). A lone task still outside `work/done/` is skipped.
|
|
30
|
+
*
|
|
31
|
+
* CANCELLED work never closes an issue (task
|
|
32
|
+
* `spec-complete-counts-staged-and-cancelled-tasks`): a spec whose tasks are ALL
|
|
33
|
+
* cancelled, and a lone task that was cancelled, are left OPEN with decision
|
|
34
|
+
* `cancelled`, not closed as `completed` (nothing was delivered) and not closed
|
|
35
|
+
* as `not planned` either: abandoning an issue is a human's call (drop the spec,
|
|
36
|
+
* close the issue by hand), not something a merge-to-main job infers. The
|
|
29
37
|
* close DEGRADES (never throws) on a missing/unauthenticated provider, exactly
|
|
30
38
|
* like intake's bounce close — the run reports the real cause and stays exit-0.
|
|
31
39
|
*
|
|
40
|
+
* An issue the provider already reports CLOSED is skipped with decision
|
|
41
|
+
* `already-closed` (no close call, no comment, not counted in `closed`): the
|
|
42
|
+
* workflow also runs HOURLY, so without this every pass would re-close and
|
|
43
|
+
* re-report issues closed long ago.
|
|
44
|
+
*
|
|
32
45
|
* This is the JOB the close-job WORKFLOW (capability E) invokes via
|
|
33
46
|
* `dorfl close-merged-issues`; the workflow is triggered on a merge to
|
|
34
|
-
* `main` (`push: {branches: [main]}`)
|
|
47
|
+
* `main` (`push: {branches: [main]}`) AND on an hourly `schedule:`, because a
|
|
48
|
+
* land the CI pushes with its `GITHUB_TOKEN` starts no workflow. Every pass
|
|
49
|
+
* re-derives everything from the `work/` tree (no state), so repeating is safe. The driver itself is provider-pluggable
|
|
35
50
|
* (it takes an {@link IssueProvider}) and reads only the `work/` tree.
|
|
36
51
|
*/
|
|
37
52
|
|
|
@@ -64,6 +79,8 @@ export type CloseDecision =
|
|
|
64
79
|
| 'closed' // the issue was closed via the provider seam
|
|
65
80
|
| 'not-complete' // a spec whose query says it is not yet complete → left open
|
|
66
81
|
| 'not-landed' // a lone task not yet in work/done/ → left open
|
|
82
|
+
| 'cancelled' // a spec whose tasks are ALL cancelled, or a cancelled lone task → left open
|
|
83
|
+
| 'already-closed' // closure condition held but the issue is already closed → no close call
|
|
67
84
|
| 'close-failed'; // closure condition held but the provider close degraded
|
|
68
85
|
|
|
69
86
|
/** One candidate the close-job considered, with the decision it reached. */
|
|
@@ -199,11 +216,15 @@ function resolveCandidates(repoPath: string): {
|
|
|
199
216
|
return [...specCandidates, ...taskCandidates];
|
|
200
217
|
}
|
|
201
218
|
|
|
202
|
-
/** True iff the lone task with this slug resides in `work
|
|
203
|
-
function
|
|
204
|
-
|
|
219
|
+
/** True iff the lone task with this slug resides in `work/<folder>/`. */
|
|
220
|
+
function loneTaskIn(
|
|
221
|
+
repoPath: string,
|
|
222
|
+
taskSlug: string,
|
|
223
|
+
folder: 'done' | 'cancelled',
|
|
224
|
+
): boolean {
|
|
225
|
+
for (const file of listMarkdown(repoPath, folder)) {
|
|
205
226
|
const fm = parseFrontmatter(
|
|
206
|
-
readFileSync(join(repoPath, workItemRel(
|
|
227
|
+
readFileSync(join(repoPath, workItemRel(folder, file)), 'utf8'),
|
|
207
228
|
);
|
|
208
229
|
const slug = fm.slug ?? basename(file, '.md');
|
|
209
230
|
if (slug === taskSlug) {
|
|
@@ -220,6 +241,32 @@ function closeComment(via: 'issue' | 'spec', slug: string): string {
|
|
|
220
241
|
: `Closed by dorfl: the task \`${slug}\` has landed in \`work/done/\`.`;
|
|
221
242
|
}
|
|
222
243
|
|
|
244
|
+
/**
|
|
245
|
+
* True iff the provider reports the issue as already `closed` (via the existing
|
|
246
|
+
* {@link IssueProvider.getIssue} read, whose `state` is lower-cased). A read that
|
|
247
|
+
* FAILS (the GitHub adapter throws on a `gh` failure) or reports no state answers
|
|
248
|
+
* `false`, so the pass falls through to the close attempt exactly as before: the
|
|
249
|
+
* close then either succeeds or reports `close-failed` with the real cause. A
|
|
250
|
+
* state read must never be the reason an issue stays open.
|
|
251
|
+
*/
|
|
252
|
+
async function isAlreadyClosed(
|
|
253
|
+
issueProvider: IssueProvider,
|
|
254
|
+
repoPath: string,
|
|
255
|
+
issueNumber: number,
|
|
256
|
+
env: NodeJS.ProcessEnv | undefined,
|
|
257
|
+
): Promise<boolean> {
|
|
258
|
+
try {
|
|
259
|
+
const issue = await issueProvider.getIssue({
|
|
260
|
+
cwd: repoPath,
|
|
261
|
+
issueNumber,
|
|
262
|
+
env,
|
|
263
|
+
});
|
|
264
|
+
return issue.state === 'closed';
|
|
265
|
+
} catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
223
270
|
/**
|
|
224
271
|
* Run the close-job over a repo's `work/` tree: resolve the closure candidates,
|
|
225
272
|
* apply the per-kind closure condition (a landed lone task; a spec whose
|
|
@@ -243,11 +290,14 @@ export async function runCloseJob(
|
|
|
243
290
|
let shouldClose: boolean;
|
|
244
291
|
let notReady: CloseDecision;
|
|
245
292
|
if (cand.via === 'spec') {
|
|
246
|
-
|
|
247
|
-
|
|
293
|
+
const query = isSpecComplete({repoPath, slug: cand.slug});
|
|
294
|
+
shouldClose = query.complete;
|
|
295
|
+
notReady = query.allCancelled ? 'cancelled' : 'not-complete';
|
|
248
296
|
} else {
|
|
249
|
-
shouldClose =
|
|
250
|
-
notReady = '
|
|
297
|
+
shouldClose = loneTaskIn(repoPath, cand.slug, 'done');
|
|
298
|
+
notReady = loneTaskIn(repoPath, cand.slug, 'cancelled')
|
|
299
|
+
? 'cancelled'
|
|
300
|
+
: 'not-landed';
|
|
251
301
|
}
|
|
252
302
|
|
|
253
303
|
if (!shouldClose) {
|
|
@@ -260,6 +310,21 @@ export async function runCloseJob(
|
|
|
260
310
|
continue;
|
|
261
311
|
}
|
|
262
312
|
|
|
313
|
+
// ALREADY CLOSED? The close-job also runs hourly (task
|
|
314
|
+
// `close-job-also-runs-on-a-schedule`), so most passes meet issues an
|
|
315
|
+
// earlier pass (or a human) already closed. Read the state through the
|
|
316
|
+
// provider seam (NO direct `gh`) and skip those: no second close, no second
|
|
317
|
+
// comment, not counted in `closed`.
|
|
318
|
+
if (await isAlreadyClosed(issueProvider, repoPath, cand.issueNumber, env)) {
|
|
319
|
+
candidates.push({
|
|
320
|
+
issueNumber: cand.issueNumber,
|
|
321
|
+
via: cand.via,
|
|
322
|
+
slug: cand.slug,
|
|
323
|
+
decision: 'already-closed',
|
|
324
|
+
});
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
263
328
|
// CLOSE via the provider seam (NO direct `gh`): comment + reason + close in
|
|
264
329
|
// ONE atomic call, exactly the seam intake's bounce uses.
|
|
265
330
|
const result = await issueProvider.closeIssue({
|
package/src/complete.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
PR_TITLE_MAX,
|
|
12
12
|
} from './integration-core.js';
|
|
13
13
|
import {ledgerWrite} from './ledger-write.js';
|
|
14
|
-
import {releaseItemLock} from './item-lock.js';
|
|
14
|
+
import {markLockKeptForProposePr, releaseItemLock} from './item-lock.js';
|
|
15
15
|
import {workBranchRef, parseWorkBranchRef} from './slug-namespace.js';
|
|
16
16
|
import {isProvablyMergedForReap} from './gc.js';
|
|
17
17
|
import type {SidecarType} from './sidecar.js';
|
|
@@ -1272,6 +1272,24 @@ async function releaseClaimLockAfterDurableMove(
|
|
|
1272
1272
|
'the lock is released by the next claim (or now, with `dorfl status ' +
|
|
1273
1273
|
'--reconcile-locks`).',
|
|
1274
1274
|
);
|
|
1275
|
+
// Say so ON the lock (task `wire-merge-questions-into-the-advance-tick`,
|
|
1276
|
+
// decision 4): the kept lock no longer covers a live build, so the
|
|
1277
|
+
// merge-question surfacer may ask about the branch (when no PR is open) and
|
|
1278
|
+
// an answered `merge` may take the lock over. Best-effort: an unmarked kept
|
|
1279
|
+
// lock only means the branch is treated as a live build (never surfaced).
|
|
1280
|
+
try {
|
|
1281
|
+
const marked = await markLockKeptForProposePr({
|
|
1282
|
+
item: `task:${slug}`,
|
|
1283
|
+
cwd,
|
|
1284
|
+
arbiter,
|
|
1285
|
+
env,
|
|
1286
|
+
});
|
|
1287
|
+
if (marked.outcome === 'lost' || marked.outcome === 'error') {
|
|
1288
|
+
note(marked.message);
|
|
1289
|
+
}
|
|
1290
|
+
} catch {
|
|
1291
|
+
// Never fail a completed propose on the marker.
|
|
1292
|
+
}
|
|
1275
1293
|
return;
|
|
1276
1294
|
}
|
|
1277
1295
|
try {
|
package/src/config.ts
CHANGED
|
@@ -81,31 +81,28 @@ export type SpecsLandIn = 'proposed' | 'ready';
|
|
|
81
81
|
export type ObservationTriage = 'off' | 'ask' | 'auto';
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
|
-
* **The `mergeQuestions` gate axis
|
|
84
|
+
* **The `mergeQuestions` gate axis**: the 2-state member of the question-
|
|
85
85
|
* surfacing gate family that governs the MERGE-QUESTION surfacer (spec
|
|
86
86
|
* `land-time-reverify-and-parallel-merge-ceiling` Story 17 / task
|
|
87
|
-
* `merge-questions-gate-axis`).
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* lands). The states:
|
|
87
|
+
* `merge-questions-gate-axis`). A SEPARATE axis from `observationTriage`:
|
|
88
|
+
* "should this built work merge?" is materially more consequential than "is
|
|
89
|
+
* this observation worth promoting?", so a merge-question must NEVER ride
|
|
90
|
+
* `observationTriage` and must NOT default `off` (a silently-dropped
|
|
91
|
+
* merge-question means finished, pushed work never lands). The states:
|
|
93
92
|
* - `off` ⇒ the merge-question surfacer is not invoked (only correct for a
|
|
94
93
|
* repo that lands by some other means);
|
|
95
|
-
* - `ask` (DEFAULT) ⇒ the surfacer enumerates unmerged `work/*` branches
|
|
96
|
-
* emits a merge-question sidecar; a human's plain
|
|
97
|
-
* answer is required before the land. The
|
|
98
|
-
* propose semantics and never silently
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* answered in this task's Applied answers 2026-06-26 (q1/q2/q3) + SPEC sidecar
|
|
106
|
-
* Q3.
|
|
94
|
+
* - `ask` (DEFAULT) ⇒ the surfacer enumerates unmerged `work/*` branches with
|
|
95
|
+
* no open PR + emits a merge-question sidecar; a human's plain
|
|
96
|
+
* `merge | hold | drop` answer is required before the land. The
|
|
97
|
+
* conservative default that honours propose semantics and never silently
|
|
98
|
+
* drops pushed work.
|
|
99
|
+
* There is NO `auto` (task `wire-merge-questions-into-the-advance-tick`,
|
|
100
|
+
* decision 3): unattended landing is what merge mode (`integration: merge`) is
|
|
101
|
+
* for. Resolved through the precedence chain env > per-repo > global > default
|
|
102
|
+
* `ask` (there is no CLI flag). The surfacer runs in the laptop bare `advance`
|
|
103
|
+
* tick and in the CI `surface-merge-questions` job (`merge-question-tick.ts`).
|
|
107
104
|
*/
|
|
108
|
-
export type MergeQuestions = 'off' | 'ask'
|
|
105
|
+
export type MergeQuestions = 'off' | 'ask';
|
|
109
106
|
|
|
110
107
|
/**
|
|
111
108
|
* Which harness adapter (ADR §5) launches a job's agent and reports its
|
|
@@ -229,23 +226,20 @@ export interface Config {
|
|
|
229
226
|
*/
|
|
230
227
|
observationTriage: ObservationTriage;
|
|
231
228
|
/**
|
|
232
|
-
* Per-repo policy governing the MERGE-QUESTION SURFACER
|
|
229
|
+
* Per-repo policy governing the MERGE-QUESTION SURFACER, the 2-state member
|
|
233
230
|
* of the question-surfacing gate family (spec
|
|
234
231
|
* `land-time-reverify-and-parallel-merge-ceiling` Story 17 / task
|
|
235
|
-
* `merge-questions-gate-axis`).
|
|
236
|
-
*
|
|
237
|
-
* `
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* (`--merge-questions`) > `DORFL_MERGE_QUESTIONS` env > per-repo > global >
|
|
247
|
-
* default `ask`. Gates the SURFACE phase of the merge-question loop only;
|
|
248
|
-
* APPLY (consume a committed merge-answer) stays ALWAYS allowed (the
|
|
232
|
+
* `merge-questions-gate-axis`). A SEPARATE axis from `observationTriage` with
|
|
233
|
+
* a DIFFERENT default: `observationTriage` defaults `off` (a dropped
|
|
234
|
+
* observation is safely ignorable), `mergeQuestions` defaults `ask` (a
|
|
235
|
+
* dropped merge-question means pushed work never lands). `off` ⇒ the
|
|
236
|
+
* surfacer is NOT invoked (only for a repo that lands by some other means);
|
|
237
|
+
* `ask` (default) ⇒ the surfacer enumerates unmerged `work/*` branches with no
|
|
238
|
+
* open PR and emits a merge-question sidecar a human answers. No `auto` (task
|
|
239
|
+
* `wire-merge-questions-into-the-advance-tick`, decision 3: unattended landing
|
|
240
|
+
* is merge mode's job). Resolved `DORFL_MERGE_QUESTIONS` env > per-repo >
|
|
241
|
+
* global > default `ask`. Gates the SURFACE phase of the merge-question loop
|
|
242
|
+
* only; APPLY (consume a committed merge-answer) stays ALWAYS allowed (the
|
|
249
243
|
* create-vs-consume invariant the gate family obeys, ADR
|
|
250
244
|
* `ci-config-policy-and-gate-family` §4).
|
|
251
245
|
*/
|
|
@@ -746,8 +740,10 @@ export interface Config {
|
|
|
746
740
|
* INTEGRATES in ONE process; across separate CI jobs the CAS loop IS the queue,
|
|
747
741
|
* and this cap is what determines how wide a matrix burst converges before any
|
|
748
742
|
* spurious bounce. A wide-matrix CI raises it; the default stays modest. Race-1
|
|
749
|
-
* safety is unchanged (a lost CAS costs only a re-rebase
|
|
750
|
-
* `--force`, never a both-land-broken
|
|
743
|
+
* safety is unchanged (a lost CAS costs only a re-rebase and a retried push, never
|
|
744
|
+
* a `--force`, never a both-land-broken; it does NOT re-run the gate, and the CI
|
|
745
|
+
* apply phase reports a land whose tree differs from the gated tree, ADR
|
|
746
|
+
* `ci-agent-job-holds-no-write-token` decision 2) — scaling the cap only changes WHEN a
|
|
751
747
|
* genuinely-stuck loser gives up. Resolved per-repo like `freshWorktreeGate`:
|
|
752
748
|
* flag (`--merge-retries`) > env (`DORFL_MERGE_RETRIES`) > per-repo > global >
|
|
753
749
|
* default. The default matches `integration-core.ts`'s built-in fallback (1000 —
|
|
@@ -762,13 +758,14 @@ export interface Config {
|
|
|
762
758
|
* **Per-repo OPT-IN strictness layered on the OQ6 stale-approval default**
|
|
763
759
|
* (spec `land-time-reverify-and-parallel-merge-ceiling`, sidecar OQ6 / task
|
|
764
760
|
* `strict-merge-approval-gate`). Controls how the apply rung treats a prior
|
|
765
|
-
* merge-answer when
|
|
766
|
-
* the
|
|
767
|
-
* when the rebased tip re-verifies GREEN
|
|
768
|
-
* sufficient
|
|
769
|
-
*
|
|
770
|
-
* `main
|
|
771
|
-
* instead of auto-landing
|
|
761
|
+
* merge-answer when `main`'s code (outside `work/`) moved since the question
|
|
762
|
+
* was asked (the `askedAtMain` the question records). The default (`false`)
|
|
763
|
+
* honours the prior answer and lands when the rebased tip re-verifies GREEN
|
|
764
|
+
* (a green re-verify is trusted as sufficient: the cheap fast-path). `true`
|
|
765
|
+
* re-surfaces the merge-question (appends a follow-up asked against the
|
|
766
|
+
* current `main`, under the `advancing` lock, before any checkout or
|
|
767
|
+
* rebase: no branch-side mutation) instead of auto-landing when `main`
|
|
768
|
+
* moved, even on a green re-verify
|
|
772
769
|
* — the host-agnostic analogue of GitHub's "dismiss stale approvals when
|
|
773
770
|
* the base changes". Story #16's RED-re-verify refusal is UNCHANGED and
|
|
774
771
|
* independent of this flag.
|
|
@@ -905,10 +902,8 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
905
902
|
// a silently-dropped merge-question means finished, pushed work never lands —
|
|
906
903
|
// strictly more consequential than a dropped observation-promote prompt, so
|
|
907
904
|
// this gate axis is DELIBERATELY HIGHER than `observationTriage`'s `off`. A
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
// lands by some other means opts into `off`. Resolved per-repo through the
|
|
911
|
-
// SAME precedence chain (flag > env > per-repo > global > default).
|
|
905
|
+
// repo that lands by some other means opts into `off`. Resolved per-repo
|
|
906
|
+
// through the SAME precedence chain (env > per-repo > global > default).
|
|
912
907
|
mergeQuestions: 'ask',
|
|
913
908
|
// DECLARED blocked work is calm by default (`false`): the `needsAnswers`-blocked
|
|
914
909
|
// pool is dropped from the auto-pick selection, so `advance` does NOT proactively
|
package/src/continue-branch.ts
CHANGED
|
@@ -165,11 +165,91 @@ export function branchAheadOfArbiter(options: {
|
|
|
165
165
|
return branchAheadOf(cwd, branchRef, mainRef, env);
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Why a continue rebase did not apply (task
|
|
170
|
+
* `a-failed-rebase-reports-its-real-cause`). Either way the rebase was
|
|
171
|
+
* `--abort`ed (NEVER auto-resolved) and the kept branch is back on its
|
|
172
|
+
* un-rebased tip; the two kinds differ only in what the report says:
|
|
173
|
+
*
|
|
174
|
+
* - `conflict`: git stopped on conflicting paths (`paths`, the unmerged index
|
|
175
|
+
* entries; empty only when git printed a `CONFLICT (...)` line but left no
|
|
176
|
+
* unmerged entry). A genuine content conflict for a human to resolve.
|
|
177
|
+
* - `failed`: the rebase exited non-zero WITHOUT a conflict (a missing
|
|
178
|
+
* committer identity, an unresolvable `main`, a dirty tree, a hook, ...).
|
|
179
|
+
* Calling it "conflicted" hid the real cause (observation
|
|
180
|
+
* `ci-answered-merge-reports-a-conflict-a-plain-rebase-does-not-have`).
|
|
181
|
+
*
|
|
182
|
+
* `onto` is the sha `mainRef` resolved to when the rebase ran (`''` when it did
|
|
183
|
+
* not resolve), and `output` is git's own report (stderr, then stdout),
|
|
184
|
+
* condensed to one line by {@link condenseGitOutput}.
|
|
185
|
+
*/
|
|
186
|
+
export type ContinueRebaseFailure =
|
|
187
|
+
| {kind: 'conflict'; onto: string; paths: string[]; output: string}
|
|
188
|
+
| {kind: 'failed'; onto: string; output: string};
|
|
189
|
+
|
|
168
190
|
/** The result of rebasing a continued branch onto the freshly-fetched main. */
|
|
169
|
-
export
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
export type ContinueRebaseResult = {kind: 'clean'} | ContinueRebaseFailure;
|
|
192
|
+
|
|
193
|
+
/** The longest git report a {@link ContinueRebaseFailure} carries. */
|
|
194
|
+
const MAX_GIT_OUTPUT = 600;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Condense git's multi-line report into one line for a reason / note: split
|
|
198
|
+
* on newlines and on the carriage returns of git's progress lines, drop blank
|
|
199
|
+
* and `hint:` lines, join the rest with ` / `, and cap the length.
|
|
200
|
+
*/
|
|
201
|
+
export function condenseGitOutput(text: string): string {
|
|
202
|
+
const lines = text
|
|
203
|
+
.split(/[\r\n]+/)
|
|
204
|
+
.map((l) => l.trim())
|
|
205
|
+
.filter((l) => l !== '' && !l.startsWith('hint:'));
|
|
206
|
+
const joined = lines.join(' / ');
|
|
207
|
+
return joined.length > MAX_GIT_OUTPUT
|
|
208
|
+
? `${joined.slice(0, MAX_GIT_OUTPUT)}...`
|
|
209
|
+
: joined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* What happened to a continue rebase, as the tail of a sentence that starts
|
|
214
|
+
* "rebasing `<branch>` onto current main ...". A conflict names its paths and
|
|
215
|
+
* the `main` sha; any other failure says so and quotes git. Both say the rebase
|
|
216
|
+
* was aborted, so the kept work is intact.
|
|
217
|
+
*/
|
|
218
|
+
export function describeContinueRebaseFailure(
|
|
219
|
+
failure: ContinueRebaseFailure,
|
|
220
|
+
): string {
|
|
221
|
+
const onto = failure.onto === '' ? 'an unresolved main' : failure.onto;
|
|
222
|
+
if (failure.kind === 'conflict') {
|
|
223
|
+
const on =
|
|
224
|
+
failure.paths.length > 0 ? ` on ${failure.paths.join(', ')}` : '';
|
|
225
|
+
return (
|
|
226
|
+
`(${onto}) conflicted${on} (aborted, never auto-resolved)` +
|
|
227
|
+
(failure.paths.length > 0 || failure.output === ''
|
|
228
|
+
? ''
|
|
229
|
+
: `; git: ${failure.output}`)
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
return (
|
|
233
|
+
`(${onto}) failed without a conflict (aborted); git: ` +
|
|
234
|
+
(failure.output === '' ? '(no output)' : failure.output)
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The whole sentence for a continue rebase that did not apply: "rebasing
|
|
240
|
+
* `<branch>` onto current main (<sha>) conflicted on <paths> ..." or "... failed
|
|
241
|
+
* without a conflict (aborted); git: <report>". `failure` is optional only so a
|
|
242
|
+
* caller holding just the legacy boolean still gets a sentence.
|
|
243
|
+
*/
|
|
244
|
+
export function continueRebaseSentence(
|
|
245
|
+
branch: string,
|
|
246
|
+
failure: ContinueRebaseFailure | undefined,
|
|
247
|
+
): string {
|
|
248
|
+
const tail =
|
|
249
|
+
failure === undefined
|
|
250
|
+
? 'did not apply (aborted, never auto-resolved)'
|
|
251
|
+
: describeContinueRebaseFailure(failure);
|
|
252
|
+
return `rebasing \`${branch}\` onto current main ${tail}`;
|
|
173
253
|
}
|
|
174
254
|
|
|
175
255
|
/**
|
|
@@ -188,10 +268,13 @@ export interface ContinueRebaseResult {
|
|
|
188
268
|
* rename/rename ledger conflict that inheritance caused) is deleted. The ONLY
|
|
189
269
|
* commits replayed are the agent's own wip / `→done` commits.
|
|
190
270
|
*
|
|
191
|
-
* A CLEAN rebase returns `{kind: 'clean'}
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
271
|
+
* A CLEAN rebase returns `{kind: 'clean'}`. Any other outcome is `--abort`ed
|
|
272
|
+
* (NEVER auto-resolved) and returns a {@link ContinueRebaseFailure} carrying the
|
|
273
|
+
* `main` sha rebased onto and git's report: `conflict` (with the conflicting
|
|
274
|
+
* paths) when git stopped on a GENUINE conflict, `failed` for every other
|
|
275
|
+
* non-zero exit. The caller routes either to needs-attention via the §10 path,
|
|
276
|
+
* naming the real cause ({@link describeContinueRebaseFailure}). Must be called
|
|
277
|
+
* while HEAD is the continued branch.
|
|
195
278
|
*
|
|
196
279
|
* RENAME-DETECTION-OFF (task `disable-rename-detection-on-continue-rebase`): the
|
|
197
280
|
* rebase is invoked with `-c merge.directoryRenames=false` SCOPED to the
|
|
@@ -210,6 +293,11 @@ export function rebaseContinuedBranchOntoMain(
|
|
|
210
293
|
mainRef: string,
|
|
211
294
|
env: NodeJS.ProcessEnv | undefined,
|
|
212
295
|
): ContinueRebaseResult {
|
|
296
|
+
const onto = gitSoft(
|
|
297
|
+
['rev-parse', '--verify', '--quiet', `${mainRef}^{commit}`],
|
|
298
|
+
cwd,
|
|
299
|
+
env,
|
|
300
|
+
).stdout.trim();
|
|
213
301
|
const rebase = gitSoft(
|
|
214
302
|
['-c', 'merge.directoryRenames=false', 'rebase', mainRef],
|
|
215
303
|
cwd,
|
|
@@ -218,9 +306,20 @@ export function rebaseContinuedBranchOntoMain(
|
|
|
218
306
|
if (rebase.status === 0) {
|
|
219
307
|
return {kind: 'clean'};
|
|
220
308
|
}
|
|
221
|
-
//
|
|
309
|
+
// Read the conflicting paths (the unmerged index entries) BEFORE the abort
|
|
310
|
+
// clears them. git prints its `CONFLICT (...)` lines on stdout.
|
|
311
|
+
const paths = gitSoft(['diff', '--name-only', '--diff-filter=U'], cwd, env)
|
|
312
|
+
.stdout.split('\n')
|
|
313
|
+
.map((p) => p.trim())
|
|
314
|
+
.filter((p) => p !== '');
|
|
315
|
+
const output = condenseGitOutput([rebase.stderr, rebase.stdout].join('\n'));
|
|
316
|
+
// NEVER auto-resolve: abort back to a clean continued-branch tip. A failure
|
|
317
|
+
// that never started a rebase leaves nothing to abort; that exit is harmless.
|
|
222
318
|
gitSoft(['rebase', '--abort'], cwd, env);
|
|
223
|
-
|
|
319
|
+
if (paths.length > 0 || /^CONFLICT \(/m.test(rebase.stdout)) {
|
|
320
|
+
return {kind: 'conflict', onto, paths, output};
|
|
321
|
+
}
|
|
322
|
+
return {kind: 'failed', onto, output};
|
|
224
323
|
}
|
|
225
324
|
|
|
226
325
|
/**
|
|
@@ -376,6 +475,9 @@ export function pushProposeBranchWithStaleLeaseRetry(options: {
|
|
|
376
475
|
// shape is the same create-only `--force-with-lease=<ref>:` form
|
|
377
476
|
// `item-lock.ts` uses for the CAS create.
|
|
378
477
|
const lease = observed === '' ? `${branch}:` : `${branch}:${observed}`;
|
|
478
|
+
// Write-seam implementation: reached only from the integrator, behind
|
|
479
|
+
// `ledgerWrite.applyCompleteTransition` (guard:
|
|
480
|
+
// `write-sites-through-seams.test.ts`).
|
|
379
481
|
const push = gitSoft(
|
|
380
482
|
['push', arbiter, `${branch}:${branch}`, `--force-with-lease=${lease}`],
|
|
381
483
|
cwd,
|
|
@@ -423,11 +525,12 @@ export type ContinuedPushResult =
|
|
|
423
525
|
/** The work branch landed on the arbiter (first try or after a clean re-rebase). */
|
|
424
526
|
| {kind: 'pushed'}
|
|
425
527
|
/**
|
|
426
|
-
* A re-rebase onto the freshly-fetched main
|
|
427
|
-
*
|
|
428
|
-
*
|
|
528
|
+
* A re-rebase onto the freshly-fetched main did not apply on a retry (a
|
|
529
|
+
* `conflict`, or a non-conflict `failed`); it was `--abort`ed (NEVER
|
|
530
|
+
* auto-resolved). The caller routes the item to needs-attention via the §10
|
|
531
|
+
* path, naming the cause; the green work stays intact on the branch.
|
|
429
532
|
*/
|
|
430
|
-
|
|
|
533
|
+
| ContinueRebaseFailure;
|
|
431
534
|
|
|
432
535
|
/**
|
|
433
536
|
* Push the CONTINUED `work/<slug>` branch to the arbiter with
|
|
@@ -493,6 +596,8 @@ export function pushContinuedBranchWithStaleLeaseRetry(options: {
|
|
|
493
596
|
let attempt = 0;
|
|
494
597
|
// eslint-disable-next-line no-constant-condition
|
|
495
598
|
while (true) {
|
|
599
|
+
// Write-seam implementation: callers reach this through
|
|
600
|
+
// `refWrite.pushContinuedBranch` (guard: `write-sites-through-seams.test.ts`).
|
|
496
601
|
const push = gitSoft(
|
|
497
602
|
[
|
|
498
603
|
'push',
|
|
@@ -556,8 +661,8 @@ export function pushContinuedBranchWithStaleLeaseRetry(options: {
|
|
|
556
661
|
// merge). A CONFLICT is the EXISTING abort → needs-attention path — the retry
|
|
557
662
|
// handles ONLY the clean-rebase stale-lease case (never auto-resolves).
|
|
558
663
|
const rebase = rebaseContinuedBranchOntoMain(cwd, mainRef, env);
|
|
559
|
-
if (rebase.kind
|
|
560
|
-
return
|
|
664
|
+
if (rebase.kind !== 'clean') {
|
|
665
|
+
return rebase;
|
|
561
666
|
}
|
|
562
667
|
// Loop: retry the push, now re-leased against the freshly-fetched remote tip.
|
|
563
668
|
}
|