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
package/src/cli.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {Command, Option} from 'commander';
|
|
2
|
+
import {Command, InvalidArgumentError, Option} from 'commander';
|
|
3
3
|
import type {Command as Commander} from 'commander';
|
|
4
4
|
import {createInterface} from 'node:readline';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
@@ -66,7 +66,11 @@ import {
|
|
|
66
66
|
type IsolatedAdvanceContext,
|
|
67
67
|
} from './advance-isolated.js';
|
|
68
68
|
import {advanceRegistrySetRunTick} from './advance-loop-driver.js';
|
|
69
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
performIntake,
|
|
71
|
+
resolveIntakeIntegrationModes,
|
|
72
|
+
type PerformIntakeOptions,
|
|
73
|
+
} from './intake.js';
|
|
70
74
|
import {workFolderPrefix} from './work-layout.js';
|
|
71
75
|
import {
|
|
72
76
|
performDoAuto,
|
|
@@ -90,6 +94,7 @@ import {
|
|
|
90
94
|
encodeRepoKey,
|
|
91
95
|
} from './repo-mirror.js';
|
|
92
96
|
import {identityEnv, type Identity} from './identity.js';
|
|
97
|
+
import {runMergeQuestionTick} from './merge-question-tick.js';
|
|
93
98
|
import {
|
|
94
99
|
harnessFlagOverrides,
|
|
95
100
|
doFlagOverrides,
|
|
@@ -121,7 +126,10 @@ import {
|
|
|
121
126
|
type DataLeak,
|
|
122
127
|
} from './prd-to-spec.js';
|
|
123
128
|
import {resyncProtocol, type ResyncResult} from './resync-protocol.js';
|
|
124
|
-
import {
|
|
129
|
+
import {
|
|
130
|
+
sweepRemoteMergedBranches,
|
|
131
|
+
sweepTerminalItemLocks,
|
|
132
|
+
} from './reap-branches.js';
|
|
125
133
|
import {sweepOrphanSidecars} from './orphan-sidecar.js';
|
|
126
134
|
import {sweepLedgerDuplicates, formatLedgerSweep} from './ledger-lint.js';
|
|
127
135
|
import {status, formatStatus} from './status.js';
|
|
@@ -161,6 +169,19 @@ import {GitHubCIContext} from './install-ci-github.js';
|
|
|
161
169
|
import {loadCapabilityRegistry} from './install-ci-core.js';
|
|
162
170
|
import {performCloseMergedIssues} from './close-job.js';
|
|
163
171
|
import {installSkills, type InstallSkillsResult} from './install-skills.js';
|
|
172
|
+
import {parsePhase, PhaseUsageError, type Phase} from './phase.js';
|
|
173
|
+
import {activateProcessPhase} from './phase-recorder.js';
|
|
174
|
+
import {performBuildPhase, type BuildPhaseVerb} from './ci-phase-build.js';
|
|
175
|
+
import {performIntakePhase} from './ci-phase-intake.js';
|
|
176
|
+
import {performTaskingPhase} from './ci-phase-tasking.js';
|
|
177
|
+
import {advancePhasePath, performTreelessPhase} from './ci-phase-treeless.js';
|
|
178
|
+
import {
|
|
179
|
+
AgentResultUsageError,
|
|
180
|
+
parseAgentJobResult,
|
|
181
|
+
parseAgentTimeoutMinutes,
|
|
182
|
+
type AgentJobResult,
|
|
183
|
+
} from './ci-agent-result.js';
|
|
184
|
+
import {singleJobWarning} from './single-job-warning.js';
|
|
164
185
|
|
|
165
186
|
interface ScanFlags {
|
|
166
187
|
config?: string;
|
|
@@ -168,10 +189,61 @@ interface ScanFlags {
|
|
|
168
189
|
json?: boolean;
|
|
169
190
|
here?: boolean;
|
|
170
191
|
reconcileLocks?: boolean;
|
|
192
|
+
allArbiters?: boolean;
|
|
171
193
|
arbiterRemote?: string;
|
|
172
194
|
arbiter?: string;
|
|
173
195
|
}
|
|
174
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Resolve the ARBITER SCOPE of a `--reconcile-locks` write for `status` or `scan`
|
|
199
|
+
* (tasks `reconcile-locks-stays-within-the-current-arbiter` and
|
|
200
|
+
* `status-no-arbiter-is-honoured`). Like `gc`, the drain is arbiter-scoped by
|
|
201
|
+
* DEFAULT: the hub key of the arbiter resolved from the cwd (its `--arbiter`
|
|
202
|
+
* remote, else the configured `defaultArbiter`), so a drain run in repo A never
|
|
203
|
+
* releases repo B's locks. `--all-arbiters` is the explicit, loud global opt-in
|
|
204
|
+
* and returns `undefined` (= every mirror). With neither a resolvable cwd arbiter
|
|
205
|
+
* nor the flag this REFUSES (exit 1) rather than silently widening the write.
|
|
206
|
+
* Returns `undefined` too when `--reconcile-locks` is absent (the key is then
|
|
207
|
+
* ignored by the report builders).
|
|
208
|
+
*/
|
|
209
|
+
function resolveReconcileArbiterKey(
|
|
210
|
+
verb: 'status' | 'scan',
|
|
211
|
+
flags: {
|
|
212
|
+
reconcileLocks?: boolean;
|
|
213
|
+
allArbiters?: boolean;
|
|
214
|
+
arbiter?: string | false;
|
|
215
|
+
},
|
|
216
|
+
defaultArbiter: string,
|
|
217
|
+
): string | undefined {
|
|
218
|
+
if (flags.reconcileLocks !== true) {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
if (flags.allArbiters === true) {
|
|
222
|
+
console.error(
|
|
223
|
+
'>> --all-arbiters: releasing stale per-item locks GLOBALLY across ' +
|
|
224
|
+
'ALL registered arbiters (NOT scoped to the current repo).',
|
|
225
|
+
);
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
// `typeof` guard: on `status`, `--no-arbiter` is commander's negation of
|
|
229
|
+
// `--arbiter`, so it arrives as `arbiter: false`, not a remote name.
|
|
230
|
+
const remote =
|
|
231
|
+
typeof flags.arbiter === 'string' ? flags.arbiter : defaultArbiter;
|
|
232
|
+
const key = resolveArbiterKeyFromCwd(process.cwd(), remote, process.env);
|
|
233
|
+
if (key === undefined) {
|
|
234
|
+
console.error(
|
|
235
|
+
`refusing: \`${verb} --reconcile-locks\` is ARBITER-SCOPED and could ` +
|
|
236
|
+
`not resolve an arbiter from the current directory (no '${remote}' ` +
|
|
237
|
+
'git remote, or not inside a repo). It will NOT fall back to ' +
|
|
238
|
+
'releasing locks on every arbiter. Run it from a repo with an ' +
|
|
239
|
+
'arbiter remote, pass --arbiter <remote>, or --all-arbiters to ' +
|
|
240
|
+
'reconcile every registered arbiter.',
|
|
241
|
+
);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
return key;
|
|
245
|
+
}
|
|
246
|
+
|
|
175
247
|
/**
|
|
176
248
|
* Whether `--auto-build` / `--no-auto-build` was explicitly passed on the command
|
|
177
249
|
* line. Commander gives a negatable boolean option a default of `true`, so we
|
|
@@ -750,6 +822,16 @@ function explicitSpecsLandInFromFlag(
|
|
|
750
822
|
|
|
751
823
|
interface DoFlags {
|
|
752
824
|
config?: string;
|
|
825
|
+
/** Hidden, CI-only `--phase lock|agent|apply` (see {@link phaseOption}). */
|
|
826
|
+
phase?: Phase;
|
|
827
|
+
/** Hidden, CI-only: the directory the agent phase writes the handoff to. */
|
|
828
|
+
handoffOut?: string;
|
|
829
|
+
/** Hidden, CI-only: the directory the apply phase reads the handoff from. */
|
|
830
|
+
handoffIn?: string;
|
|
831
|
+
/** Hidden, CI-only: `needs.agent.result`, for the apply phase. */
|
|
832
|
+
agentResult?: AgentJobResult;
|
|
833
|
+
/** Hidden, CI-only: the lock job's `agentTimeoutMinutes`, for the apply phase. */
|
|
834
|
+
agentTimeoutMinutes?: number;
|
|
753
835
|
arbiter?: string;
|
|
754
836
|
remote?: string;
|
|
755
837
|
/** `--isolated`: build in a job worktree off THIS repo's arbiter (no checkout takeover). Since `make-isolated-default-build-mode` this is the DEFAULT; the flag remains accepted as a redundant explicit opt-IN alias (D3). */
|
|
@@ -764,8 +846,6 @@ interface DoFlags {
|
|
|
764
846
|
observationTriage?: string;
|
|
765
847
|
/** `--surface-blockers` / `--no-surface-blockers`: the declared-blocked-work gate (`advance`). */
|
|
766
848
|
surfaceBlockers?: boolean;
|
|
767
|
-
/** `--merge-questions <off|ask|auto>`: the merge-question SURFACER gate (`advance`). SEPARATE from `--observation-triage` with a HIGHER default. */
|
|
768
|
-
mergeQuestions?: string;
|
|
769
849
|
/** `--strict-merge-approval` / `--no-strict-merge-approval`: the OPT-IN strictness layered on the OQ6 stale-approval default (`advance`). Default OFF — ON re-surfaces the merge-question on a merge-base change instead of auto-landing on a green re-verify. */
|
|
770
850
|
strictMergeApproval?: boolean;
|
|
771
851
|
merge?: boolean;
|
|
@@ -804,6 +884,16 @@ interface DoFlags {
|
|
|
804
884
|
|
|
805
885
|
interface IntakeFlags {
|
|
806
886
|
config?: string;
|
|
887
|
+
/** Hidden, CI-only `--phase lock|agent|apply` (see {@link phaseOption}). */
|
|
888
|
+
phase?: Phase;
|
|
889
|
+
/** Hidden, CI-only: the directory the agent phase writes the handoff to. */
|
|
890
|
+
handoffOut?: string;
|
|
891
|
+
/** Hidden, CI-only: the directory the apply phase reads the handoff from. */
|
|
892
|
+
handoffIn?: string;
|
|
893
|
+
/** Hidden, CI-only: `needs.agent.result`, for the apply phase. */
|
|
894
|
+
agentResult?: AgentJobResult;
|
|
895
|
+
/** Hidden, CI-only: accepted for parity with `do`/`advance`; intake does not use it. */
|
|
896
|
+
agentTimeoutMinutes?: number;
|
|
807
897
|
arbiter?: string;
|
|
808
898
|
merge?: boolean;
|
|
809
899
|
propose?: boolean;
|
|
@@ -1027,10 +1117,15 @@ interface StatusFlags {
|
|
|
1027
1117
|
config?: string;
|
|
1028
1118
|
workspace?: string;
|
|
1029
1119
|
arbiterRemote?: string;
|
|
1030
|
-
|
|
1031
|
-
|
|
1120
|
+
/**
|
|
1121
|
+
* `--arbiter <remote>` gives the remote name; `--no-arbiter` is commander's
|
|
1122
|
+
* NEGATION of the same option and arrives as `false` (there is no separate
|
|
1123
|
+
* `noArbiter` key). `undefined` when neither is passed.
|
|
1124
|
+
*/
|
|
1125
|
+
arbiter?: string | false;
|
|
1032
1126
|
here?: boolean;
|
|
1033
1127
|
reconcileLocks?: boolean;
|
|
1128
|
+
allArbiters?: boolean;
|
|
1034
1129
|
json?: boolean;
|
|
1035
1130
|
}
|
|
1036
1131
|
|
|
@@ -1307,6 +1402,254 @@ function resolvePackageVersion(): string {
|
|
|
1307
1402
|
}
|
|
1308
1403
|
}
|
|
1309
1404
|
|
|
1405
|
+
/**
|
|
1406
|
+
* The hidden, CI-only `--phase lock|agent|apply` option shared by `intake`,
|
|
1407
|
+
* `advance` and `do` (spec `ci-agent-job-without-write-token`, see `phase.ts`).
|
|
1408
|
+
* Validated at PARSE time ({@link parsePhase}): an unknown value, or any value
|
|
1409
|
+
* outside GitHub Actions, is a commander usage error before the action runs.
|
|
1410
|
+
* Absent ⇒ today's single process.
|
|
1411
|
+
*/
|
|
1412
|
+
function phaseOption(): Option {
|
|
1413
|
+
return new Option(
|
|
1414
|
+
'--phase <phase>',
|
|
1415
|
+
'(CI-only) run as one job of the split CI item workflow: lock, agent or apply. Requires GITHUB_ACTIONS=true.',
|
|
1416
|
+
)
|
|
1417
|
+
.hideHelp()
|
|
1418
|
+
.argParser((raw: string): Phase => {
|
|
1419
|
+
try {
|
|
1420
|
+
return parsePhase(raw);
|
|
1421
|
+
} catch (err) {
|
|
1422
|
+
if (err instanceof PhaseUsageError) {
|
|
1423
|
+
throw new InvalidArgumentError(err.message);
|
|
1424
|
+
}
|
|
1425
|
+
throw err;
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* Enter the `--phase` for this one-shot process (a no-op without one): the phase
|
|
1432
|
+
* guards fire from here on, and the agent phase records its write seams.
|
|
1433
|
+
*/
|
|
1434
|
+
function enterCliPhase(phase: Phase | undefined): void {
|
|
1435
|
+
if (phase !== undefined) {
|
|
1436
|
+
activateProcessPhase(phase);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* The hidden, CI-only handoff-directory options of the phase mode: the agent
|
|
1442
|
+
* phase writes the handoff to `--handoff-out`, the apply phase reads it from
|
|
1443
|
+
* `--handoff-in` (task `ci-split-build-path`).
|
|
1444
|
+
*/
|
|
1445
|
+
function handoffDirOptions(): Option[] {
|
|
1446
|
+
return [
|
|
1447
|
+
new Option(
|
|
1448
|
+
'--handoff-out <dir>',
|
|
1449
|
+
'(CI-only, --phase agent) write the handoff artifact to this directory',
|
|
1450
|
+
).hideHelp(),
|
|
1451
|
+
new Option(
|
|
1452
|
+
'--handoff-in <dir>',
|
|
1453
|
+
'(CI-only, --phase apply) read the handoff artifact from this directory (under $RUNNER_TEMP)',
|
|
1454
|
+
).hideHelp(),
|
|
1455
|
+
];
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* The hidden, CI-only agent-result options of the apply phase (task
|
|
1460
|
+
* `ci-split-agent-result-and-reruns`, decision 5): `--agent-result` is
|
|
1461
|
+
* `needs.agent.result` (only `success` reads the handoff), and
|
|
1462
|
+
* `--agent-timeout-minutes` is the lock job's trusted `agentTimeoutMinutes`,
|
|
1463
|
+
* which tells a timed-out agent job from a cancelled one.
|
|
1464
|
+
*/
|
|
1465
|
+
function agentResultOptions(): Option[] {
|
|
1466
|
+
const parse =
|
|
1467
|
+
<T>(fn: (raw: string) => T) =>
|
|
1468
|
+
(raw: string): T => {
|
|
1469
|
+
try {
|
|
1470
|
+
return fn(raw);
|
|
1471
|
+
} catch (err) {
|
|
1472
|
+
if (err instanceof AgentResultUsageError) {
|
|
1473
|
+
throw new InvalidArgumentError(err.message);
|
|
1474
|
+
}
|
|
1475
|
+
throw err;
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
return [
|
|
1479
|
+
new Option(
|
|
1480
|
+
'--agent-result <result>',
|
|
1481
|
+
'(CI-only, --phase apply) the agent job result (needs.agent.result): success, failure, cancelled or skipped',
|
|
1482
|
+
)
|
|
1483
|
+
.hideHelp()
|
|
1484
|
+
.argParser(parse(parseAgentJobResult)),
|
|
1485
|
+
new Option(
|
|
1486
|
+
'--agent-timeout-minutes <n>',
|
|
1487
|
+
"(CI-only, --phase apply) the agent job's timeout, the lock job's agentTimeoutMinutes output",
|
|
1488
|
+
)
|
|
1489
|
+
.hideHelp()
|
|
1490
|
+
.argParser(parse(parseAgentTimeoutMinutes)),
|
|
1491
|
+
];
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
/**
|
|
1495
|
+
* Run ONE phase of the split build path (`do <slug>` / `advance task:<slug>`)
|
|
1496
|
+
* or tasking path (`do spec:<slug>` / `advance spec:<slug>`, task
|
|
1497
|
+
* `ci-split-tasking`) for a single named item with `--phase`, and exit with its
|
|
1498
|
+
* code. The lock phase
|
|
1499
|
+
* reads its trusted facts back from nowhere (it produces them); the agent and
|
|
1500
|
+
* apply phases read them from `DORFL_LOCK_OUTPUTS` (see `ci-lock-outputs.ts`).
|
|
1501
|
+
*/
|
|
1502
|
+
async function runBuildPhaseAndExit(
|
|
1503
|
+
verb: BuildPhaseVerb,
|
|
1504
|
+
args: string[],
|
|
1505
|
+
flags: DoFlags,
|
|
1506
|
+
options: Omit<DoOptions, 'arg'>,
|
|
1507
|
+
): Promise<never> {
|
|
1508
|
+
const phase = flags.phase!;
|
|
1509
|
+
if (args.length !== 1) {
|
|
1510
|
+
console.error(
|
|
1511
|
+
`error: --phase runs ONE CI item; name exactly one item (got ${args.length}).`,
|
|
1512
|
+
);
|
|
1513
|
+
process.exit(1);
|
|
1514
|
+
}
|
|
1515
|
+
const handoffDir = phase === 'agent' ? flags.handoffOut : flags.handoffIn;
|
|
1516
|
+
if (phase !== 'lock' && handoffDir === undefined) {
|
|
1517
|
+
console.error(
|
|
1518
|
+
`error: --phase ${phase} needs ${phase === 'agent' ? '--handoff-out' : '--handoff-in'} <dir>.`,
|
|
1519
|
+
);
|
|
1520
|
+
process.exit(1);
|
|
1521
|
+
}
|
|
1522
|
+
if (phase === 'apply' && flags.agentResult === undefined) {
|
|
1523
|
+
console.error(
|
|
1524
|
+
'error: --phase apply needs --agent-result <result> (needs.agent.result).',
|
|
1525
|
+
);
|
|
1526
|
+
process.exit(1);
|
|
1527
|
+
}
|
|
1528
|
+
// A `spec:<slug>` item is the TASKING path (task `ci-split-tasking`); every
|
|
1529
|
+
// other item is the build path, which refuses a non-task itself.
|
|
1530
|
+
const result =
|
|
1531
|
+
parseSlugArg(args[0]).explicit === 'spec'
|
|
1532
|
+
? await performTaskingPhase({
|
|
1533
|
+
...options,
|
|
1534
|
+
arg: args[0],
|
|
1535
|
+
phase,
|
|
1536
|
+
verb,
|
|
1537
|
+
handoffDir,
|
|
1538
|
+
agentResult: flags.agentResult,
|
|
1539
|
+
agentTimeoutMinutes: flags.agentTimeoutMinutes,
|
|
1540
|
+
})
|
|
1541
|
+
: await performBuildPhase({
|
|
1542
|
+
...options,
|
|
1543
|
+
arg: args[0],
|
|
1544
|
+
phase,
|
|
1545
|
+
verb,
|
|
1546
|
+
handoffDir,
|
|
1547
|
+
allowBacklog: flags.allowBacklog === true,
|
|
1548
|
+
agentResult: flags.agentResult,
|
|
1549
|
+
agentTimeoutMinutes: flags.agentTimeoutMinutes,
|
|
1550
|
+
});
|
|
1551
|
+
if (result.exitCode !== 0) {
|
|
1552
|
+
console.error(`error: ${result.message}`);
|
|
1553
|
+
} else {
|
|
1554
|
+
console.error(`>> ${result.message}`);
|
|
1555
|
+
}
|
|
1556
|
+
process.exit(result.exitCode);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
/**
|
|
1560
|
+
* Run ONE phase of `advance <item> --phase` and exit with its code. The item's
|
|
1561
|
+
* rung picks the split path ({@link advancePhasePath}): the build and tasking
|
|
1562
|
+
* paths as before ({@link runBuildPhaseAndExit}), and the tree-less rungs
|
|
1563
|
+
* (surface, triage, apply; task `ci-split-treeless-rungs`) through
|
|
1564
|
+
* {@link performTreelessPhase} with the same advance context the laptop tick
|
|
1565
|
+
* gets.
|
|
1566
|
+
*/
|
|
1567
|
+
async function runAdvancePhaseAndExit(
|
|
1568
|
+
args: string[],
|
|
1569
|
+
flags: DoFlags,
|
|
1570
|
+
doOptions: Omit<DoOptions, 'arg'>,
|
|
1571
|
+
advanceContext: AdvanceContext,
|
|
1572
|
+
): Promise<never> {
|
|
1573
|
+
const phase = flags.phase!;
|
|
1574
|
+
if (args.length === 1) {
|
|
1575
|
+
const path = await advancePhasePath({
|
|
1576
|
+
phase,
|
|
1577
|
+
arg: args[0],
|
|
1578
|
+
cwd: advanceContext.cwd,
|
|
1579
|
+
arbiter: advanceContext.arbiter,
|
|
1580
|
+
env: identityEnv(doOptions.identity, process.env),
|
|
1581
|
+
});
|
|
1582
|
+
if (path === 'treeless') {
|
|
1583
|
+
const handoffDir = phase === 'agent' ? flags.handoffOut : flags.handoffIn;
|
|
1584
|
+
if (phase !== 'lock' && handoffDir === undefined) {
|
|
1585
|
+
console.error(
|
|
1586
|
+
`error: --phase ${phase} needs ${phase === 'agent' ? '--handoff-out' : '--handoff-in'} <dir>.`,
|
|
1587
|
+
);
|
|
1588
|
+
process.exit(1);
|
|
1589
|
+
}
|
|
1590
|
+
if (phase === 'apply' && flags.agentResult === undefined) {
|
|
1591
|
+
console.error(
|
|
1592
|
+
'error: --phase apply needs --agent-result <result> (needs.agent.result).',
|
|
1593
|
+
);
|
|
1594
|
+
process.exit(1);
|
|
1595
|
+
}
|
|
1596
|
+
const result = await performTreelessPhase({
|
|
1597
|
+
...advanceContext,
|
|
1598
|
+
arg: args[0],
|
|
1599
|
+
phase,
|
|
1600
|
+
identity: doOptions.identity,
|
|
1601
|
+
handoffDir,
|
|
1602
|
+
agentResult: flags.agentResult,
|
|
1603
|
+
agentTimeoutMinutes: flags.agentTimeoutMinutes,
|
|
1604
|
+
});
|
|
1605
|
+
if (result.exitCode !== 0) {
|
|
1606
|
+
console.error(`error: ${result.message}`);
|
|
1607
|
+
} else {
|
|
1608
|
+
console.error(`>> ${result.message}`);
|
|
1609
|
+
}
|
|
1610
|
+
process.exit(result.exitCode);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return runBuildPhaseAndExit('advance', args, flags, doOptions);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/**
|
|
1617
|
+
* Run ONE phase of the split intake path (`intake <N> --phase`) and exit with
|
|
1618
|
+
* its code (task `ci-split-intake`). The agent and apply phases read the lock
|
|
1619
|
+
* job's facts from `DORFL_LOCK_OUTPUTS`.
|
|
1620
|
+
*/
|
|
1621
|
+
async function runIntakePhaseAndExit(
|
|
1622
|
+
flags: IntakeFlags,
|
|
1623
|
+
options: PerformIntakeOptions,
|
|
1624
|
+
): Promise<never> {
|
|
1625
|
+
const phase = flags.phase!;
|
|
1626
|
+
const handoffDir = phase === 'agent' ? flags.handoffOut : flags.handoffIn;
|
|
1627
|
+
if (phase !== 'lock' && handoffDir === undefined) {
|
|
1628
|
+
console.error(
|
|
1629
|
+
`error: --phase ${phase} needs ${phase === 'agent' ? '--handoff-out' : '--handoff-in'} <dir>.`,
|
|
1630
|
+
);
|
|
1631
|
+
process.exit(1);
|
|
1632
|
+
}
|
|
1633
|
+
if (phase === 'apply' && flags.agentResult === undefined) {
|
|
1634
|
+
console.error(
|
|
1635
|
+
'error: --phase apply needs --agent-result <result> (needs.agent.result).',
|
|
1636
|
+
);
|
|
1637
|
+
process.exit(1);
|
|
1638
|
+
}
|
|
1639
|
+
const result = await performIntakePhase({
|
|
1640
|
+
...options,
|
|
1641
|
+
phase,
|
|
1642
|
+
handoffDir,
|
|
1643
|
+
agentResult: flags.agentResult,
|
|
1644
|
+
});
|
|
1645
|
+
if (result.exitCode !== 0) {
|
|
1646
|
+
console.error(`error: ${result.message}`);
|
|
1647
|
+
} else {
|
|
1648
|
+
console.error(`>> ${result.message}`);
|
|
1649
|
+
}
|
|
1650
|
+
process.exit(result.exitCode);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1310
1653
|
export function buildProgram(): Command {
|
|
1311
1654
|
const program = new Command();
|
|
1312
1655
|
|
|
@@ -1389,7 +1732,11 @@ export function buildProgram(): Command {
|
|
|
1389
1732
|
)
|
|
1390
1733
|
.option(
|
|
1391
1734
|
'--reconcile-locks',
|
|
1392
|
-
'WRITE: release the per-item locks whose item is already at rest in a terminal folder on the arbiter’s main (e.g. a merged propose PR left its lock behind). Without this flag `scan` is strictly read-only. Not normally needed, the claim path releases them automatically on every unit of work. An item that is NOT terminal on main (a live build, an open PR, a stuck item) is NEVER released, with or without the flag.',
|
|
1735
|
+
'WRITE: release the per-item locks whose item is already at rest in a terminal folder on the arbiter’s main (e.g. a merged propose PR left its lock behind). Without this flag `scan` is strictly read-only. SCOPE: by DEFAULT this is ARBITER-SCOPED, like gc and `status --reconcile-locks`: it releases locks ONLY on the arbiter resolved from the current directory (its --arbiter remote, default: the configured defaultArbiter), so a drain run in repo A never touches repo B’s locks (other repos’ stale locks are still classified). Pass --all-arbiters to release across EVERY registered arbiter. Refuses when no arbiter resolves from the cwd and --all-arbiters is not given. Not normally needed, the claim path releases them automatically on every unit of work. An item that is NOT terminal on main (a live build, an open PR, a stuck item) is NEVER released, with or without the flag.',
|
|
1736
|
+
)
|
|
1737
|
+
.option(
|
|
1738
|
+
'--all-arbiters',
|
|
1739
|
+
'with --reconcile-locks: release stale locks across EVERY registered arbiter (loud + global), not just the current repo’s. No effect on the read-only report, which always covers every registered repo.',
|
|
1393
1740
|
)
|
|
1394
1741
|
.option('--json', 'output the raw report as JSON')
|
|
1395
1742
|
.action(async (flags: ScanFlags, command: Commander) => {
|
|
@@ -1432,10 +1779,19 @@ export function buildProgram(): Command {
|
|
|
1432
1779
|
}
|
|
1433
1780
|
return;
|
|
1434
1781
|
}
|
|
1782
|
+
// SCOPE of the registry-loop `--reconcile-locks` WRITE: arbiter-scoped by
|
|
1783
|
+
// default, `--all-arbiters` for the global drain, refusal when neither
|
|
1784
|
+
// resolves (the SAME rule `status --reconcile-locks` applies).
|
|
1785
|
+
const reconcileArbiterKey = resolveReconcileArbiterKey(
|
|
1786
|
+
'scan',
|
|
1787
|
+
flags,
|
|
1788
|
+
config.defaultArbiter,
|
|
1789
|
+
);
|
|
1435
1790
|
const report = await scan(config, {
|
|
1436
1791
|
warn,
|
|
1437
1792
|
override,
|
|
1438
1793
|
reconcileLocks: flags.reconcileLocks === true,
|
|
1794
|
+
reconcileArbiterKey,
|
|
1439
1795
|
});
|
|
1440
1796
|
// The cwd-local section: resolve it ONLY when a participating cwd is NOT
|
|
1441
1797
|
// already covered by a registered mirror. A FETCH-FREE pre-check
|
|
@@ -2425,7 +2781,13 @@ export function buildProgram(): Command {
|
|
|
2425
2781
|
'--merge-retries <n>',
|
|
2426
2782
|
'cross-job merge-serialiser CAS-retry cap (see `run --help`); resolved flag > env > per-repo > global > default 1000.',
|
|
2427
2783
|
)
|
|
2784
|
+
.addOption(phaseOption())
|
|
2785
|
+
.addOption(handoffDirOptions()[0])
|
|
2786
|
+
.addOption(handoffDirOptions()[1])
|
|
2787
|
+
.addOption(agentResultOptions()[0])
|
|
2788
|
+
.addOption(agentResultOptions()[1])
|
|
2428
2789
|
.action(async (rawSlugs: string[], flags: DoFlags) => {
|
|
2790
|
+
enterCliPhase(flags.phase);
|
|
2429
2791
|
// Variadic grammar (`do-autopick`): zero args = AUTO-PICK; one = the single
|
|
2430
2792
|
// named item; many = those, IN SEQUENCE. `-n <x>` is the auto-pick count.
|
|
2431
2793
|
const args = rawSlugs ?? [];
|
|
@@ -2798,6 +3160,7 @@ export function buildProgram(): Command {
|
|
|
2798
3160
|
// sequential `performDo` (do-autopick runs the EXISTING pipeline per item).
|
|
2799
3161
|
const baseDoOptions: Omit<DoOptions, 'arg'> = {
|
|
2800
3162
|
cwd,
|
|
3163
|
+
phase: flags.phase,
|
|
2801
3164
|
arbiter: flags.arbiter ?? config.defaultArbiter,
|
|
2802
3165
|
// The host-only runner IDENTITY (a bot): scopes the runner's git/provider
|
|
2803
3166
|
// ops (claim, push, integrate, `gh`) — NEVER the agent launch. Absent ⇒
|
|
@@ -2910,6 +3273,12 @@ export function buildProgram(): Command {
|
|
|
2910
3273
|
process.exit(1);
|
|
2911
3274
|
}
|
|
2912
3275
|
|
|
3276
|
+
// CI PHASE MODE (task `ci-split-build-path`): `--phase` runs ONE job of the
|
|
3277
|
+
// split item workflow for a single named task or spec, on the SAME pipeline.
|
|
3278
|
+
if (flags.phase !== undefined) {
|
|
3279
|
+
await runBuildPhaseAndExit('do', args, flags, baseDoOptions);
|
|
3280
|
+
}
|
|
3281
|
+
|
|
2913
3282
|
// DISPATCH the variadic grammar (in-place forms):
|
|
2914
3283
|
// zero args -> AUTO-PICK `count` (default 1) across the two pools
|
|
2915
3284
|
// (ordered by selectionOrder; default drain = tasks-first)
|
|
@@ -2964,7 +3333,7 @@ export function buildProgram(): Command {
|
|
|
2964
3333
|
.command('advance')
|
|
2965
3334
|
.helpGroup(HEADLINE_GROUP)
|
|
2966
3335
|
.description(
|
|
2967
|
-
'Advance work/ item(s) one lifecycle rung toward ready/built (SPEC advance-loop), the SEQUENTIAL one-shot driver over the advance tick. advance <slug> (bare = the task) | advance spec:<slug> (the spec tasking rung) | advance obs:<slug> (triage an observation) | advance (auto-pick one eligible) | advance <a> <b> (those, in sequence) | advance -n <x> (x eligible, in sequence). Each item: classify (read-only, no model, no lock) → take the `advancing` CAS lock → dispatch winner-only — build/task rungs ORCHESTRATE `do`/`do spec:`, surface/apply always run, triage respects observationTriage (off|ask|auto). The bare/`-n` selection respects the per-action gates (build→autoBuild, task→autoTask, triage→observationTriage); `-n` is ALWAYS sequential (parallelism is `run` / the CI matrix).',
|
|
3336
|
+
'Advance work/ item(s) one lifecycle rung toward ready/built (SPEC advance-loop), the SEQUENTIAL one-shot driver over the advance tick. advance <slug> (bare = the task) | advance spec:<slug> (the spec tasking rung) | advance obs:<slug> (triage an observation) | advance (auto-pick one eligible) | advance <a> <b> (those, in sequence) | advance -n <x> (x eligible, in sequence). Each item: classify (read-only, no model, no lock) → take the `advancing` CAS lock → dispatch winner-only — build/task rungs ORCHESTRATE `do`/`do spec:`, surface/apply always run, triage respects observationTriage (off|ask|auto). The bare/`-n` selection respects the per-action gates (build→autoBuild, task→autoTask, triage→observationTriage); `-n` is ALWAYS sequential (parallelism is `run` / the CI matrix). Before it selects, the bare/`-n` form also asks a merge question for each unmerged work/task-<slug> branch with no open PR and no live build (the `surface-merge-questions` pass; gated by mergeQuestions off|ask).',
|
|
2968
3337
|
)
|
|
2969
3338
|
.argument(
|
|
2970
3339
|
'[slugs...]',
|
|
@@ -3001,15 +3370,11 @@ export function buildProgram(): Command {
|
|
|
3001
3370
|
)
|
|
3002
3371
|
.option(
|
|
3003
3372
|
'--strict-merge-approval',
|
|
3004
|
-
'opt in to the host-agnostic "dismiss stale approvals on base change" discipline (spec `land-time-reverify-and-parallel-merge-ceiling` sidecar OQ6): when
|
|
3373
|
+
'opt in to the host-agnostic "dismiss stale approvals on base change" discipline (spec `land-time-reverify-and-parallel-merge-ceiling` sidecar OQ6): when `main`’s code (anything outside `work/`) moved since the merge-question was asked (the `askedAtMain` it records), RE-SURFACE the merge-question (append a follow-up asked against the current `main`, under the advancing lock) instead of auto-landing on a green re-verify. A question written before `askedAtMain` existed never re-stales. Default OFF (a green re-verify is trusted as sufficient; honour the prior answer). Story #16’s RED-re-verify refusal is UNCHANGED and independent of this flag. Resolved flag > env > per-repo > global > default off.',
|
|
3005
3374
|
)
|
|
3006
3375
|
.option(
|
|
3007
3376
|
'--no-strict-merge-approval',
|
|
3008
|
-
'honour the prior merge-answer and land when the rebased tip re-verifies GREEN even if the
|
|
3009
|
-
)
|
|
3010
|
-
.option(
|
|
3011
|
-
'--merge-questions <mode>',
|
|
3012
|
-
'the merge-question SURFACER gate (off|ask|auto): off drops the surfacer (only for a repo that lands by some other means); ask (default) enumerates unmerged `work/*` branches and surfaces a merge-question sidecar a human answers; auto self-supplies the `merge` answer and lands via the SAME deterministic apply-time re-verify (the merge-mode-like fast path). SEPARATE axis from --observation-triage with a HIGHER default (a dropped merge-question means pushed work never lands). Resolved flag > env > per-repo > global > default ask.',
|
|
3377
|
+
'honour the prior merge-answer and land when the rebased tip re-verifies GREEN even if `main` moved since the question was asked (default; the cheap green-re-verify-is-enough path)',
|
|
3013
3378
|
)
|
|
3014
3379
|
.option(
|
|
3015
3380
|
'--merge',
|
|
@@ -3027,7 +3392,13 @@ export function buildProgram(): Command {
|
|
|
3027
3392
|
'--watch',
|
|
3028
3393
|
"stream the build agent's high-signal events live by tailing the pi session log (requires harness: pi; READ-ONLY observer — does not change outcome/gate/git). The same view `do --watch` gives, threaded through the build rung; CI uses it so the job log shows the agent working instead of freezing.",
|
|
3029
3394
|
)
|
|
3395
|
+
.addOption(phaseOption())
|
|
3396
|
+
.addOption(handoffDirOptions()[0])
|
|
3397
|
+
.addOption(handoffDirOptions()[1])
|
|
3398
|
+
.addOption(agentResultOptions()[0])
|
|
3399
|
+
.addOption(agentResultOptions()[1])
|
|
3030
3400
|
.action(async (rawSlugs: string[], flags: DoFlags) => {
|
|
3401
|
+
enterCliPhase(flags.phase);
|
|
3031
3402
|
// Variadic grammar (mirrors `do`): zero args = AUTO-PICK; one = the single
|
|
3032
3403
|
// named item; many = those, IN SEQUENCE. `-n <x>` is the auto-pick count
|
|
3033
3404
|
// (ALWAYS sequential, US #25).
|
|
@@ -3289,6 +3660,7 @@ export function buildProgram(): Command {
|
|
|
3289
3660
|
// hands the resolved arg to `performDo`, never re-implementing it.
|
|
3290
3661
|
const doOptions: Omit<DoOptions, 'arg'> = {
|
|
3291
3662
|
cwd,
|
|
3663
|
+
phase: flags.phase,
|
|
3292
3664
|
// `--watch`: stream the build agent's session live (pi harness only;
|
|
3293
3665
|
// validated in `performDo`). Threaded through the orchestrated build rung
|
|
3294
3666
|
// so `advance --watch` (and CI) shows the agent working, not a frozen log.
|
|
@@ -3351,6 +3723,7 @@ export function buildProgram(): Command {
|
|
|
3351
3723
|
// Surface + apply stay ALWAYS allowed regardless of the gate family.
|
|
3352
3724
|
const advanceContext: AdvanceContext = {
|
|
3353
3725
|
cwd,
|
|
3726
|
+
phase: flags.phase,
|
|
3354
3727
|
arbiter: flags.arbiter ?? config.defaultArbiter,
|
|
3355
3728
|
doOptions,
|
|
3356
3729
|
surfaceGate: harnessSurfaceGate({harness, agentCmd: config.agentCmd}),
|
|
@@ -3360,6 +3733,14 @@ export function buildProgram(): Command {
|
|
|
3360
3733
|
observationTriage: config.observationTriage,
|
|
3361
3734
|
triageGate: harnessTriageGate({harness, agentCmd: config.agentCmd}),
|
|
3362
3735
|
triageModel: config.triageModel,
|
|
3736
|
+
// The answered-merge land (the apply rung's `kind: merge` action) cuts
|
|
3737
|
+
// its job worktree under `workspacesDir` and gates the rebased tip with
|
|
3738
|
+
// `prepare` / `verify`; without them an answered `merge` is refused
|
|
3739
|
+
// as a usage error (task `wire-merge-questions-into-the-advance-tick`).
|
|
3740
|
+
workspacesDir: config.workspacesDir,
|
|
3741
|
+
prepare: config.prepare,
|
|
3742
|
+
verify: config.verify,
|
|
3743
|
+
strictMergeApproval: config.strictMergeApproval,
|
|
3363
3744
|
note: (message) => console.error(`>> ${message}`),
|
|
3364
3745
|
};
|
|
3365
3746
|
|
|
@@ -3368,6 +3749,24 @@ export function buildProgram(): Command {
|
|
|
3368
3749
|
// ticks in sequence and would tail several logs; reject rather than silently
|
|
3369
3750
|
// stream only one. The CI propose matrix names a single item per leg, so it
|
|
3370
3751
|
// satisfies this; the `-n` merge job must NOT pass `--watch`.
|
|
3752
|
+
// CI PHASE MODE (task `ci-split-build-path`): `--phase` runs ONE job of the
|
|
3753
|
+
// split item workflow: the build, tasking and tree-less (surface, triage,
|
|
3754
|
+
// apply) paths are split (task `ci-split-treeless-rungs`), and the apply
|
|
3755
|
+
// rung's answered merge action with them (task
|
|
3756
|
+
// `ci-split-answered-merge-action`), whose agent job checks the branch
|
|
3757
|
+
// out through `createJob` under `workspacesDir` and gates the rebased tip
|
|
3758
|
+
// with the repo's `prepare` / `verify`. Threaded for the phase mode ONLY:
|
|
3759
|
+
// the in-place laptop tick below keeps today's context.
|
|
3760
|
+
if (flags.phase !== undefined) {
|
|
3761
|
+
await runAdvancePhaseAndExit(args, flags, doOptions, {
|
|
3762
|
+
...advanceContext,
|
|
3763
|
+
workspacesDir: config.workspacesDir,
|
|
3764
|
+
prepare: config.prepare,
|
|
3765
|
+
verify: config.verify,
|
|
3766
|
+
strictMergeApproval: config.strictMergeApproval,
|
|
3767
|
+
});
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3371
3770
|
const advanceMulti =
|
|
3372
3771
|
args.length === 0 || count !== undefined || args.length > 1;
|
|
3373
3772
|
if (advanceMulti && flags.watch === true) {
|
|
@@ -3461,7 +3860,7 @@ export function buildProgram(): Command {
|
|
|
3461
3860
|
)
|
|
3462
3861
|
.option(
|
|
3463
3862
|
'--remote-branches',
|
|
3464
|
-
'SWEEP the arbiter’s remote work/* BRANCHES instead of job worktrees: delete (via git push --delete, NEVER --force) exactly those PROVABLY MERGED into <arbiter>/main (git merge-base --is-ancestor, the SAME predicate the worktree reaper uses), and RETAIN the rest with a reason. An in-flight/un-merged branch (the recovery point) is NEVER touched. Provider-agnostic plain git — works on a --bare arbiter. The merged-only complement of `requeue --reset`.',
|
|
3863
|
+
'SWEEP the arbiter’s remote work/* BRANCHES instead of job worktrees: delete (via git push --delete, NEVER --force) exactly those PROVABLY MERGED into <arbiter>/main (git merge-base --is-ancestor, the SAME predicate the worktree reaper uses), and RETAIN the rest with a reason. An in-flight/un-merged branch (the recovery point) is NEVER touched. Provider-agnostic plain git — works on a --bare arbiter. The merged-only complement of `requeue --reset`. The same run ALSO releases every per-item lock whose item is TERMINAL on <arbiter>/main (the `status --reconcile-locks` reconcile: leased on the sha it read, never forced; a lock whose item is not terminal is never touched), so the locks merged propose PRs leave behind do not accumulate.',
|
|
3465
3864
|
)
|
|
3466
3865
|
.option(
|
|
3467
3866
|
'--arbiter <remote-or-url>',
|
|
@@ -3477,7 +3876,7 @@ export function buildProgram(): Command {
|
|
|
3477
3876
|
)
|
|
3478
3877
|
.option(
|
|
3479
3878
|
'--dry-run',
|
|
3480
|
-
'(with --remote-branches) REPORT which merged branches WOULD be reaped without deleting anything (a read-only preview)',
|
|
3879
|
+
'(with --remote-branches) REPORT which merged branches WOULD be reaped (and which terminal-item locks WOULD be released) without deleting anything (a read-only preview)',
|
|
3481
3880
|
)
|
|
3482
3881
|
.option('--json', 'output the raw result as JSON')
|
|
3483
3882
|
.action(async (flags: GcFlags) => {
|
|
@@ -3631,9 +4030,27 @@ export function buildProgram(): Command {
|
|
|
3631
4030
|
dryRun: flags.dryRun === true,
|
|
3632
4031
|
note: (message) => console.error(`>> ${message}`),
|
|
3633
4032
|
});
|
|
4033
|
+
// The TERMINAL-LOCK release (task `ci-releases-locks-of-terminal-items`)
|
|
4034
|
+
// rides this SAME invocation for the same reason the orphan sweep does: the
|
|
4035
|
+
// scheduled `reap-merged-branches` CI job runs exactly this command, and a
|
|
4036
|
+
// merged propose item is never claimed again, so without it the lock a
|
|
4037
|
+
// merged PR leaves behind accumulates on the arbiter for ever. It is the
|
|
4038
|
+
// `status --reconcile-locks` reconcile (terminal-on-main only, leased on
|
|
4039
|
+
// the sha it read, never forced), scoped to the one `--arbiter` swept
|
|
4040
|
+
// here. Honours `--dry-run` (report-only preview).
|
|
4041
|
+
const locks = await sweepTerminalItemLocks({
|
|
4042
|
+
cwd: sweepCwd,
|
|
4043
|
+
arbiter: flags.arbiter ?? 'origin',
|
|
4044
|
+
dryRun: flags.dryRun === true,
|
|
4045
|
+
note: (message) => console.error(`>> ${message}`),
|
|
4046
|
+
});
|
|
3634
4047
|
if (flags.json) {
|
|
3635
4048
|
console.log(
|
|
3636
|
-
JSON.stringify(
|
|
4049
|
+
JSON.stringify(
|
|
4050
|
+
{...sweep, orphanSidecars: orphans, terminalLocks: locks},
|
|
4051
|
+
null,
|
|
4052
|
+
2,
|
|
4053
|
+
),
|
|
3637
4054
|
);
|
|
3638
4055
|
return;
|
|
3639
4056
|
}
|
|
@@ -3644,6 +4061,11 @@ export function buildProgram(): Command {
|
|
|
3644
4061
|
for (const w of orphans.wouldReap) {
|
|
3645
4062
|
console.log(` [would-reap] ${w.path} \u2014 orphan sidecar`);
|
|
3646
4063
|
}
|
|
4064
|
+
for (const entry of locks.wouldRelease) {
|
|
4065
|
+
console.log(
|
|
4066
|
+
` [would-release] lock ${entry} \u2014 item terminal on main`,
|
|
4067
|
+
);
|
|
4068
|
+
}
|
|
3647
4069
|
} else {
|
|
3648
4070
|
for (const r of sweep.reaped) {
|
|
3649
4071
|
console.log(` [reaped] ${r.branch} \u2014 merged`);
|
|
@@ -3651,6 +4073,14 @@ export function buildProgram(): Command {
|
|
|
3651
4073
|
for (const r of orphans.reaped) {
|
|
3652
4074
|
console.log(` [reaped] ${r.path} \u2014 orphan sidecar`);
|
|
3653
4075
|
}
|
|
4076
|
+
for (const entry of locks.released) {
|
|
4077
|
+
console.log(
|
|
4078
|
+
` [released] lock ${entry} \u2014 item terminal on main`,
|
|
4079
|
+
);
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
for (const e of locks.errors) {
|
|
4083
|
+
console.log(` [kept] lock ${e.entry} \u2014 ${e.message}`);
|
|
3654
4084
|
}
|
|
3655
4085
|
for (const ret of sweep.retained) {
|
|
3656
4086
|
console.log(` [retained] ${ret.branch} \u2014 ${ret.reasonText}`);
|
|
@@ -3660,8 +4090,19 @@ export function buildProgram(): Command {
|
|
|
3660
4090
|
? sweep.wouldReap.length + orphans.wouldReap.length
|
|
3661
4091
|
: sweep.reaped.length + orphans.reaped.length;
|
|
3662
4092
|
const verb = flags.dryRun === true ? 'would reap' : 'reaped';
|
|
4093
|
+
const releasedLocks =
|
|
4094
|
+
flags.dryRun === true
|
|
4095
|
+
? locks.wouldRelease.length
|
|
4096
|
+
: locks.released.length;
|
|
4097
|
+
const releaseVerb =
|
|
4098
|
+
flags.dryRun === true ? 'would release' : 'released';
|
|
3663
4099
|
console.log(
|
|
3664
|
-
`Summary: ${reapedCount} ${verb}, ${sweep.retained.length} retained
|
|
4100
|
+
`Summary: ${reapedCount} ${verb}, ${sweep.retained.length} retained; ` +
|
|
4101
|
+
`${releasedLocks} terminal-item lock(s) ${releaseVerb}` +
|
|
4102
|
+
(locks.errors.length > 0
|
|
4103
|
+
? `, ${locks.errors.length} kept on error`
|
|
4104
|
+
: '') +
|
|
4105
|
+
'.',
|
|
3665
4106
|
);
|
|
3666
4107
|
return;
|
|
3667
4108
|
}
|
|
@@ -3773,7 +4214,8 @@ export function buildProgram(): Command {
|
|
|
3773
4214
|
}
|
|
3774
4215
|
for (const retained of result.retained) {
|
|
3775
4216
|
console.log(
|
|
3776
|
-
` [retained] ${retained.slug} \u2014 ${RETAIN_REASON_TEXT[retained.reason]}
|
|
4217
|
+
` [retained] ${retained.slug} \u2014 ${RETAIN_REASON_TEXT[retained.reason]}` +
|
|
4218
|
+
(retained.hint ? ` (${retained.hint})` : ''),
|
|
3777
4219
|
);
|
|
3778
4220
|
}
|
|
3779
4221
|
for (const orphan of result.sweptOrphans) {
|
|
@@ -3921,7 +4363,11 @@ export function buildProgram(): Command {
|
|
|
3921
4363
|
)
|
|
3922
4364
|
.option(
|
|
3923
4365
|
'--reconcile-locks',
|
|
3924
|
-
'WRITE: release the per-item locks reported as "Completed, lock not yet released" (item already at rest in a terminal folder on the arbiter’s main, e.g. a merged propose PR). Without this flag `status` is strictly read-only and only REPORTS them. Not normally needed, the claim path releases them automatically on every unit of work; this is the manual “drain them now” lever. An item that is NOT terminal on main (a live build, an open PR, a stuck item) is NEVER released, with or without the flag.',
|
|
4366
|
+
'WRITE: release the per-item locks reported as "Completed, lock not yet released" (item already at rest in a terminal folder on the arbiter’s main, e.g. a merged propose PR). Without this flag `status` is strictly read-only and only REPORTS them. SCOPE: by DEFAULT this is ARBITER-SCOPED, like gc: it releases locks ONLY on the arbiter resolved from the current directory (its --arbiter remote, default: the configured defaultArbiter), so a drain run in repo A never touches repo B’s locks (other repos’ stale locks are still REPORTED). Pass --all-arbiters to release across EVERY registered arbiter. Refuses when no arbiter resolves from the cwd and --all-arbiters is not given. Not normally needed, the claim path releases them automatically on every unit of work; this is the manual “drain them now” lever. An item that is NOT terminal on main (a live build, an open PR, a stuck item) is NEVER released, with or without the flag.',
|
|
4367
|
+
)
|
|
4368
|
+
.option(
|
|
4369
|
+
'--all-arbiters',
|
|
4370
|
+
'with --reconcile-locks: release stale locks across EVERY registered arbiter (loud + global), not just the current repo’s. No effect on the read-only report, which always covers every registered repo.',
|
|
3925
4371
|
)
|
|
3926
4372
|
.option('--json', 'output the raw report as JSON')
|
|
3927
4373
|
.action(async (flags: StatusFlags) => {
|
|
@@ -3937,7 +4383,10 @@ export function buildProgram(): Command {
|
|
|
3937
4383
|
config,
|
|
3938
4384
|
override,
|
|
3939
4385
|
arbiterRemote: flags.arbiterRemote ?? DEFAULT_ARBITER_REMOTE,
|
|
3940
|
-
|
|
4386
|
+
// `--no-arbiter` arrives as `arbiter: false` (commander negation):
|
|
4387
|
+
// never pass `false` on as a remote name.
|
|
4388
|
+
lockArbiterRemote:
|
|
4389
|
+
typeof flags.arbiter === 'string' ? flags.arbiter : 'origin',
|
|
3941
4390
|
reconcileLocks: flags.reconcileLocks === true,
|
|
3942
4391
|
warn,
|
|
3943
4392
|
});
|
|
@@ -3964,10 +4413,23 @@ export function buildProgram(): Command {
|
|
|
3964
4413
|
// REGISTERED HUB MIRROR (the registry), read from its bare `main` ref
|
|
3965
4414
|
// through the read seam (mirrors have no working tree).
|
|
3966
4415
|
const mirrorPaths = listMirrors({workspacesDir}).map((m) => m.path);
|
|
4416
|
+
// SCOPE of the `--reconcile-locks` WRITE (task
|
|
4417
|
+
// `reconcile-locks-stays-within-the-current-arbiter`): ARBITER-SCOPED by
|
|
4418
|
+
// default, resolved from the cwd exactly like `gc`, so a drain in repo A
|
|
4419
|
+
// never releases repo B's locks. `--all-arbiters` is the explicit, loud
|
|
4420
|
+
// global opt-in. With neither a resolvable cwd arbiter nor the flag we
|
|
4421
|
+
// REFUSE rather than silently widening the write to every arbiter.
|
|
4422
|
+
const reconcileArbiterKey = resolveReconcileArbiterKey(
|
|
4423
|
+
'status',
|
|
4424
|
+
flags,
|
|
4425
|
+
config.defaultArbiter,
|
|
4426
|
+
);
|
|
3967
4427
|
// Fold in the current repo's arbiter state (the old `arbiter status`, ADR
|
|
3968
4428
|
// §1/§7) unless --no-arbiter. Read-only; tolerates not being in a repo.
|
|
4429
|
+
// Commander treats `--no-arbiter` as the NEGATION of `--arbiter <remote>`
|
|
4430
|
+
// (one `arbiter` key): it sets `arbiter: false`, never a `noArbiter` key.
|
|
3969
4431
|
const arbiter =
|
|
3970
|
-
flags.
|
|
4432
|
+
flags.arbiter === false
|
|
3971
4433
|
? undefined
|
|
3972
4434
|
: arbiterStatus({
|
|
3973
4435
|
cwd: process.cwd(),
|
|
@@ -3993,6 +4455,7 @@ export function buildProgram(): Command {
|
|
|
3993
4455
|
arbiter,
|
|
3994
4456
|
cwd: cwdSection,
|
|
3995
4457
|
reconcileLocks: flags.reconcileLocks === true,
|
|
4458
|
+
reconcileArbiterKey,
|
|
3996
4459
|
warn,
|
|
3997
4460
|
});
|
|
3998
4461
|
if (flags.json) {
|
|
@@ -4006,7 +4469,7 @@ export function buildProgram(): Command {
|
|
|
4006
4469
|
.command('requeue <slug>')
|
|
4007
4470
|
.helpGroup(HEADLINE_GROUP)
|
|
4008
4471
|
.description(
|
|
4009
|
-
'Requeue a STUCK task to the backlog for re-claiming (ADR §12/§14). Recovers a task whose per-item lock is held — stuck (the resolved-recovery path: a previously-routed needs-attention item, now lock `state: stuck`) OR active (a claim that never surfaced — an un-surfaced abort, a killed run, or an in-place requeue note). The body rests in work/tasks/backlog/<slug>.md (claim never moves it under the per-item-lock model); requeue releases the lock so the item is claimable again. The release is published as a TREE-LESS compare-and-swap to the arbiter ref, EXACTLY like claim — it NEVER stages or commits in the cwd working tree, so a requeue in a shared checkout can never sweep up a concurrent writer’s uncommitted files. Escalation ladder (least to most destructive): (1) DEFAULT = keep + continue: leave the work/<slug> branch UNTOUCHED so the next claim CONTINUES from its tip (rebased onto fresh main at onboard-time); if the work/<slug> branch is not on the arbiter (never pushed, or a prior --reset already deleted it), default requeue succeeds anyway with a fresh-claim note. (2) --reconcile (alias --rebase) = NON-DESTRUCTIVE recovery: re-sync the mirror to the arbiter (prune-fetch) and RETRY the rebase of the kept branch onto latest arbiter/main in a scratch worktree; on a clean rebase, push the reconciled tip back (--force-with-lease, work branch only) and release the lock; on a genuine content conflict AFTER the clean re-sync, leave the item stuck and the branch UNTOUCHED (nothing deleted) so the human can retry when the churn settles. NEVER deletes the remote branch. (3) --reset = DESTRUCTIVE last resort: delete the remote work/<slug> branch FIRST (then release the lock) so the next claim starts fresh (guarded; never the default). -m/--message
|
|
4472
|
+
'Requeue a STUCK task to the backlog for re-claiming (ADR §12/§14). Recovers a task whose per-item lock is held — stuck (the resolved-recovery path: a previously-routed needs-attention item, now lock `state: stuck`) OR active (a claim that never surfaced — an un-surfaced abort, a killed run, or an in-place requeue note). The body rests in work/tasks/backlog/<slug>.md (claim never moves it under the per-item-lock model); requeue releases the lock so the item is claimable again. The release is published as a TREE-LESS compare-and-swap to the arbiter ref, EXACTLY like claim — it NEVER stages or commits in the cwd working tree, so a requeue in a shared checkout can never sweep up a concurrent writer’s uncommitted files. Escalation ladder (least to most destructive): (1) DEFAULT = keep + continue: leave the work/<slug> branch UNTOUCHED so the next claim CONTINUES from its tip (rebased onto fresh main at onboard-time); if the work/<slug> branch is not on the arbiter (never pushed, or a prior --reset already deleted it), default requeue succeeds anyway with a fresh-claim note. (2) --reconcile (alias --rebase) = NON-DESTRUCTIVE recovery: re-sync the mirror to the arbiter (prune-fetch) and RETRY the rebase of the kept branch onto latest arbiter/main in a scratch worktree; on a clean rebase, push the reconciled tip back (--force-with-lease, work branch only) and release the lock; on a genuine content conflict AFTER the clean re-sync, leave the item stuck and the branch UNTOUCHED (nothing deleted) so the human can retry when the churn settles. NEVER deletes the remote branch. (3) --reset = DESTRUCTIVE last resort: delete the remote work/<slug> branch FIRST (then release the lock) so the next claim starts fresh (guarded; never the default). RELEASED LOCK: a surfaced item (question sidecar on main) has its lock already released by the surface; if its body rests in the pool and its work/<slug> branch is on the arbiter, --reconcile and --reset still act on it, taking a short per-item lock for the operation and releasing it on every outcome; plain requeue on such an item is a no-op (the next claim already continues from the kept branch). -m/--message adds a dated handoff note to the item body, just before its `## Acceptance criteria` so it cannot collide with a kept branch’s done-move tail on the continue rebase (all modes; additive). CRASHED RUN: when the lock is held and a DEAD run left its retained job worktree (workspacesDir/work/*) with commits or uncommitted files the arbiter’s work/<slug> lacks, requeue first commits the residue as a wip commit and pushes the branch (plain push, never --force), so the next claim continues from it; if that save fails, the lock and the worktree are KEPT and requeue says why (not on --reset, which discards the work by design).',
|
|
4010
4473
|
)
|
|
4011
4474
|
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
4012
4475
|
.option(
|
|
@@ -4054,6 +4517,11 @@ export function buildProgram(): Command {
|
|
|
4054
4517
|
);
|
|
4055
4518
|
process.exit(1);
|
|
4056
4519
|
}
|
|
4520
|
+
// Where `do --isolated` retains job worktrees: a CRASHED run's retained
|
|
4521
|
+
// worktree may hold local-only commits, which requeue saves (pushes to the
|
|
4522
|
+
// work branch) before it releases the lock (task
|
|
4523
|
+
// `a-crashed-runs-local-work-is-saved-before-its-lock-is-released`).
|
|
4524
|
+
const {workspacesDir} = resolveGlobalConfig(loadConfig(flags.config), {});
|
|
4057
4525
|
const result = await ledgerWrite.applyReturnToBacklogTransition({
|
|
4058
4526
|
cwd,
|
|
4059
4527
|
slug,
|
|
@@ -4061,6 +4529,7 @@ export function buildProgram(): Command {
|
|
|
4061
4529
|
// arbiter to `origin` so the common case Just Works; `--arbiter` overrides.
|
|
4062
4530
|
// `--cwd` is purely the ORIGIN SOURCE the remote is resolved from.
|
|
4063
4531
|
arbiter: flags.arbiter ?? 'origin',
|
|
4532
|
+
workspacesDir,
|
|
4064
4533
|
reset: flags.reset,
|
|
4065
4534
|
reconcile,
|
|
4066
4535
|
message: flags.message,
|
|
@@ -4076,7 +4545,13 @@ export function buildProgram(): Command {
|
|
|
4076
4545
|
: result.reconciled
|
|
4077
4546
|
? ` (--reconcile: re-synced mirror + rebased ${workBranchRef('task', slug)} onto latest main and pushed the reconciled tip back; next claim continues from it)`
|
|
4078
4547
|
: ' (kept the work branch; next claim continues from its tip)';
|
|
4079
|
-
|
|
4548
|
+
const saved =
|
|
4549
|
+
result.crashedRunSave?.kind === 'saved'
|
|
4550
|
+
? ` Saved the crashed run's local work from ${result.crashedRunSave.dir} to ${result.crashedRunSave.branch} first.`
|
|
4551
|
+
: '';
|
|
4552
|
+
console.log(
|
|
4553
|
+
`Requeued '${slug}' to backlog for re-claiming.${how}${saved}`,
|
|
4554
|
+
);
|
|
4080
4555
|
});
|
|
4081
4556
|
|
|
4082
4557
|
// `promote [item]` (spec `staging-pool-position-gate-and-trust-model`, tasks
|
|
@@ -4450,7 +4925,13 @@ export function buildProgram(): Command {
|
|
|
4450
4925
|
'--sessions-dir <dir>',
|
|
4451
4926
|
'HOST-ONLY root folder under which the pi session file is generated',
|
|
4452
4927
|
)
|
|
4928
|
+
.addOption(phaseOption())
|
|
4929
|
+
.addOption(handoffDirOptions()[0])
|
|
4930
|
+
.addOption(handoffDirOptions()[1])
|
|
4931
|
+
.addOption(agentResultOptions()[0])
|
|
4932
|
+
.addOption(agentResultOptions()[1])
|
|
4453
4933
|
.action(async (rawNumber: string, flags: IntakeFlags) => {
|
|
4934
|
+
enterCliPhase(flags.phase);
|
|
4454
4935
|
const issueNumber = Number(rawNumber);
|
|
4455
4936
|
if (
|
|
4456
4937
|
rawNumber.trim() === '' ||
|
|
@@ -4555,9 +5036,10 @@ export function buildProgram(): Command {
|
|
|
4555
5036
|
);
|
|
4556
5037
|
process.exit(1);
|
|
4557
5038
|
}
|
|
4558
|
-
const
|
|
5039
|
+
const intakeOptions: PerformIntakeOptions = {
|
|
4559
5040
|
issueNumber,
|
|
4560
5041
|
cwd,
|
|
5042
|
+
phase: flags.phase,
|
|
4561
5043
|
arbiter: flags.arbiter ?? config.defaultArbiter,
|
|
4562
5044
|
integration: modes,
|
|
4563
5045
|
// The resolved cross-job CAS-retry cap (config `mergeRetries`). `intake`
|
|
@@ -4597,7 +5079,13 @@ export function buildProgram(): Command {
|
|
|
4597
5079
|
// decision/review AGENT launches); absent ⇒ ambient.
|
|
4598
5080
|
identity: config.identity,
|
|
4599
5081
|
note: (message) => console.error(`>> ${message}`),
|
|
4600
|
-
}
|
|
5082
|
+
};
|
|
5083
|
+
// CI PHASE MODE (task `ci-split-intake`): `--phase` runs ONE job of the
|
|
5084
|
+
// split item workflow for `issue:<N>`, on the SAME intake pipeline.
|
|
5085
|
+
if (flags.phase !== undefined) {
|
|
5086
|
+
await runIntakePhaseAndExit(flags, intakeOptions);
|
|
5087
|
+
}
|
|
5088
|
+
const result = await performIntake(intakeOptions);
|
|
4601
5089
|
if (result.exitCode !== 0) {
|
|
4602
5090
|
console.error(`error: ${result.message}`);
|
|
4603
5091
|
} else {
|
|
@@ -4617,7 +5105,7 @@ export function buildProgram(): Command {
|
|
|
4617
5105
|
.command('close-merged-issues')
|
|
4618
5106
|
.helpGroup(ADVANCED_GROUP)
|
|
4619
5107
|
.description(
|
|
4620
|
-
'Close source issues whose work has landed on main (CI capability E, spec runner-in-ci). Resolves each closing issue from the work/ tree (resolveClosingIssue: a lone task closes its own `issue:`; a fanned task reaches the number via `task.spec: → spec issue:`), runs the existing "spec complete?" query for the spec case (closes ONLY when ALL its spec:<slug> tasks are in work/done/), and closes via the IssueProvider seam (atomic comment+close; NO direct gh). Re-implements NONE of the resolution/query/close — it WIRES them. Invoked by the emitted close-job workflow on a merge to main; DEGRADES (never crashes) on a missing/unauthenticated gh.',
|
|
5108
|
+
'Close source issues whose work has landed on main (CI capability E, spec runner-in-ci). Resolves each closing issue from the work/ tree (resolveClosingIssue: a lone task closes its own `issue:`; a fanned task reaches the number via `task.spec: → spec issue:`), runs the existing "spec complete?" query for the spec case (closes ONLY when ALL its spec:<slug> tasks are in work/done/), and closes via the IssueProvider seam (atomic comment+close; NO direct gh). Re-implements NONE of the resolution/query/close — it WIRES them. Invoked by the emitted close-job workflow on a merge to main and hourly (a land the CI pushes itself triggers no workflow); an issue that is already closed is skipped (`already-closed`), not closed again. DEGRADES (never crashes) on a missing/unauthenticated gh.',
|
|
4621
5109
|
)
|
|
4622
5110
|
.option(
|
|
4623
5111
|
'--cwd <dir>',
|
|
@@ -4640,6 +5128,10 @@ export function buildProgram(): Command {
|
|
|
4640
5128
|
console.error(
|
|
4641
5129
|
`>> closed issue #${c.issueNumber} (${c.via} ${c.slug}).`,
|
|
4642
5130
|
);
|
|
5131
|
+
} else if (c.decision === 'already-closed') {
|
|
5132
|
+
console.error(
|
|
5133
|
+
`>> issue #${c.issueNumber} (${c.via} ${c.slug}) already closed.`,
|
|
5134
|
+
);
|
|
4643
5135
|
} else if (c.decision === 'close-failed') {
|
|
4644
5136
|
console.error(
|
|
4645
5137
|
`>> issue #${c.issueNumber} (${c.via} ${c.slug}) NOT closed: ${c.reason}`,
|
|
@@ -4659,6 +5151,56 @@ export function buildProgram(): Command {
|
|
|
4659
5151
|
process.exit(0);
|
|
4660
5152
|
});
|
|
4661
5153
|
|
|
5154
|
+
// `surface-merge-questions` (task `wire-merge-questions-into-the-advance-tick`,
|
|
5155
|
+
// decision 5): the no-agent entry point the generated `advance-lifecycle`
|
|
5156
|
+
// workflow's `surface-merge-questions` job runs. It asks, in the current
|
|
5157
|
+
// checkout of `main`, a merge question for every unmerged `work/task-<slug>`
|
|
5158
|
+
// branch with no open PR and no live build, and publishes the questions to
|
|
5159
|
+
// `<arbiter>/main`. Gated by `mergeQuestions` (`off` does nothing). The laptop
|
|
5160
|
+
// bare `advance` runs the SAME pass before it selects.
|
|
5161
|
+
program
|
|
5162
|
+
.command('surface-merge-questions')
|
|
5163
|
+
.helpGroup(ADVANCED_GROUP)
|
|
5164
|
+
.description(
|
|
5165
|
+
'Ask a merge question (a `kind: merge` sidecar entry, answered `merge | hold | drop`) for every unmerged `work/task-<slug>` branch on the arbiter that has no open PR (GitHub), whose task rests in tasks/ready/ or tasks/backlog/, whose lock is free or kept by its finished propose build, and whose tip carries the done-move; then publish the questions to <arbiter>/main (never --force). Fetches the arbiter first. Runs no agent. Gated by `mergeQuestions` (off|ask, default ask; DORFL_MERGE_QUESTIONS > dorfl.json > global): `off` surfaces nothing. The CI `advance-lifecycle` workflow runs it as its `surface-merge-questions` job; the bare laptop `advance` runs the same pass before it selects.',
|
|
5166
|
+
)
|
|
5167
|
+
.option(
|
|
5168
|
+
'--arbiter <name>',
|
|
5169
|
+
'the arbiter remote whose work branches to ask about (default: the configured defaultArbiter)',
|
|
5170
|
+
)
|
|
5171
|
+
.option('--gh-bin <bin>', 'the gh CLI binary (default: gh on PATH)')
|
|
5172
|
+
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
5173
|
+
.action(
|
|
5174
|
+
async (flags: {arbiter?: string; ghBin?: string; config: string}) => {
|
|
5175
|
+
const cwd = process.cwd();
|
|
5176
|
+
const {global, override} = loadGlobalAndOverride(flags.config);
|
|
5177
|
+
let config;
|
|
5178
|
+
try {
|
|
5179
|
+
config = resolveRepoConfig({repoPath: cwd, global, override}).config;
|
|
5180
|
+
} catch (err) {
|
|
5181
|
+
console.error(
|
|
5182
|
+
`error: ${err instanceof Error ? err.message : String(err)}`,
|
|
5183
|
+
);
|
|
5184
|
+
process.exit(1);
|
|
5185
|
+
}
|
|
5186
|
+
const pass = await runMergeQuestionTick({
|
|
5187
|
+
cwd,
|
|
5188
|
+
arbiter: flags.arbiter ?? config.defaultArbiter,
|
|
5189
|
+
mergeQuestions: config.mergeQuestions,
|
|
5190
|
+
ghBin: flags.ghBin,
|
|
5191
|
+
env: identityEnv(config.identity, process.env),
|
|
5192
|
+
note: (message) => console.error(`>> ${message}`),
|
|
5193
|
+
});
|
|
5194
|
+
// A failed pass (fetch, lock read, or the GitHub open-PR listing) is
|
|
5195
|
+
// an `error:` line and a non-zero exit, so a CI job cannot go green
|
|
5196
|
+
// while asking about no branch.
|
|
5197
|
+
console.error(
|
|
5198
|
+
pass.failed ? `error: ${pass.message}` : `>> ${pass.message}`,
|
|
5199
|
+
);
|
|
5200
|
+
process.exit(pass.failed ? 1 : 0);
|
|
5201
|
+
},
|
|
5202
|
+
);
|
|
5203
|
+
|
|
4662
5204
|
// The REGISTRY command group (ADR §1): the registered set of targets IS the
|
|
4663
5205
|
// hub-mirror set on disk. `remote add --local` absorbs the old `arbiter init`;
|
|
4664
5206
|
// `arbiter status` is folded into `status`. There is no standalone `arbiter`
|
|
@@ -4819,7 +5361,7 @@ export function buildProgram(): Command {
|
|
|
4819
5361
|
.command('install-ci')
|
|
4820
5362
|
.helpGroup(ADVANCED_GROUP)
|
|
4821
5363
|
.description(
|
|
4822
|
-
'Scaffold the CI auth/setup foundation (a one-time, human-run SCAFFOLDER): write the shared composite setup action (`dorfl-setup`) + provider auth (models.json
|
|
5364
|
+
'Scaffold the CI auth/setup foundation (a one-time, human-run SCAFFOLDER): write the shared composite setup action (`dorfl-setup`) + provider auth (a generated models.json; put rotating credentials such as OAuth behind a proxy provider `baseUrl`) and set the provider secrets via the GitHub seam. Interactive wizard, or `--config <file>` for a non-interactive reproduction; `--export-config` round-trips the config; `--fake` writes to `.fake/` (never `.github/`) and sets NO real secret (a snapshot dry-run).',
|
|
4823
5365
|
)
|
|
4824
5366
|
.option(
|
|
4825
5367
|
'--config <file>',
|
|
@@ -4938,6 +5480,26 @@ export function buildProgram(): Command {
|
|
|
4938
5480
|
console.log(formatSkillsAddReport(result, flags.local === true));
|
|
4939
5481
|
});
|
|
4940
5482
|
|
|
5483
|
+
// Decision 9 (task `ci-split-warn-single-job-workflows`): warn before an
|
|
5484
|
+
// agent-spawning verb runs in an old single-job CI workflow (GitHub Actions,
|
|
5485
|
+
// no `--phase`, a credential persisted in the checkout's git config).
|
|
5486
|
+
// Warn-only: the action still runs.
|
|
5487
|
+
program.hook('preAction', (_program, actionCommand) => {
|
|
5488
|
+
if (actionCommand.parent !== program) {
|
|
5489
|
+
return;
|
|
5490
|
+
}
|
|
5491
|
+
const phase = (actionCommand.opts() as {phase?: Phase}).phase;
|
|
5492
|
+
const warning = singleJobWarning({
|
|
5493
|
+
verb: actionCommand.name(),
|
|
5494
|
+
phase,
|
|
5495
|
+
env: process.env,
|
|
5496
|
+
cwd: process.cwd(),
|
|
5497
|
+
});
|
|
5498
|
+
if (warning !== undefined) {
|
|
5499
|
+
console.error(warning);
|
|
5500
|
+
}
|
|
5501
|
+
});
|
|
5502
|
+
|
|
4941
5503
|
return program;
|
|
4942
5504
|
}
|
|
4943
5505
|
|