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/dist/cli.js
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 { createInterface } from 'node:readline';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { realpathSync, mkdirSync, rmSync, readFileSync } from 'node:fs';
|
|
@@ -26,7 +26,7 @@ import { performAdvance } from './advance.js';
|
|
|
26
26
|
import { performAdvanceAuto, performAdvanceArgs, runAdvanceTickWithTreelessPublish, } from './advance-drivers.js';
|
|
27
27
|
import { performAdvanceIsolated, performAdvanceIsolatedAuto, performAdvanceIsolatedArgs, } from './advance-isolated.js';
|
|
28
28
|
import { advanceRegistrySetRunTick } from './advance-loop-driver.js';
|
|
29
|
-
import { performIntake, resolveIntakeIntegrationModes } from './intake.js';
|
|
29
|
+
import { performIntake, resolveIntakeIntegrationModes, } from './intake.js';
|
|
30
30
|
import { workFolderPrefix } from './work-layout.js';
|
|
31
31
|
import { performDoAuto, performDoArgs, } from './do-autopick.js';
|
|
32
32
|
import { createHarness } from './pi-harness.js';
|
|
@@ -35,6 +35,7 @@ import { shouldUseColor } from './output.js';
|
|
|
35
35
|
import { resolveRepoConfig, resolveRepoConfigFromLoaded, loadRepoConfigFromContent, REPO_CONFIG_FILENAME, } from './repo-config.js';
|
|
36
36
|
import { ensureMirrorMain, readRepoConfigFromMirrorMain, encodeRepoKey, } from './repo-mirror.js';
|
|
37
37
|
import { identityEnv } from './identity.js';
|
|
38
|
+
import { runMergeQuestionTick } from './merge-question-tick.js';
|
|
38
39
|
import { harnessFlagOverrides, doFlagOverrides, doNeedsAgentCmd, NO_AGENT_CMD_MESSAGE, reviewFlagOverrides, freshWorktreeGateFlagOverrides, mergeRetriesFlagOverrides, noPRFlagOverrides, triageModelFlagOverrides, } from './do-config.js';
|
|
39
40
|
import { harnessReviewGate, harnessTaskAcceptanceGate } from './review-gate.js';
|
|
40
41
|
import { harnessSurfaceGate } from './surface-gate.js';
|
|
@@ -47,7 +48,7 @@ import { resolvePromptGuidance, applyModelFallbacks } from './config.js';
|
|
|
47
48
|
import { gc, resolveArbiterKeyFromCwd, enumerateWorkspaceArbiters, RETAIN_REASON_TEXT, } from './gc.js';
|
|
48
49
|
import { runPrdToSpec, } from './prd-to-spec.js';
|
|
49
50
|
import { resyncProtocol } from './resync-protocol.js';
|
|
50
|
-
import { sweepRemoteMergedBranches } from './reap-branches.js';
|
|
51
|
+
import { sweepRemoteMergedBranches, sweepTerminalItemLocks, } from './reap-branches.js';
|
|
51
52
|
import { sweepOrphanSidecars } from './orphan-sidecar.js';
|
|
52
53
|
import { sweepLedgerDuplicates, formatLedgerSweep } from './ledger-lint.js';
|
|
53
54
|
import { status, formatStatus } from './status.js';
|
|
@@ -66,6 +67,50 @@ import { GitHubCIContext } from './install-ci-github.js';
|
|
|
66
67
|
import { loadCapabilityRegistry } from './install-ci-core.js';
|
|
67
68
|
import { performCloseMergedIssues } from './close-job.js';
|
|
68
69
|
import { installSkills } from './install-skills.js';
|
|
70
|
+
import { parsePhase, PhaseUsageError } from './phase.js';
|
|
71
|
+
import { activateProcessPhase } from './phase-recorder.js';
|
|
72
|
+
import { performBuildPhase } from './ci-phase-build.js';
|
|
73
|
+
import { performIntakePhase } from './ci-phase-intake.js';
|
|
74
|
+
import { performTaskingPhase } from './ci-phase-tasking.js';
|
|
75
|
+
import { advancePhasePath, performTreelessPhase } from './ci-phase-treeless.js';
|
|
76
|
+
import { AgentResultUsageError, parseAgentJobResult, parseAgentTimeoutMinutes, } from './ci-agent-result.js';
|
|
77
|
+
import { singleJobWarning } from './single-job-warning.js';
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the ARBITER SCOPE of a `--reconcile-locks` write for `status` or `scan`
|
|
80
|
+
* (tasks `reconcile-locks-stays-within-the-current-arbiter` and
|
|
81
|
+
* `status-no-arbiter-is-honoured`). Like `gc`, the drain is arbiter-scoped by
|
|
82
|
+
* DEFAULT: the hub key of the arbiter resolved from the cwd (its `--arbiter`
|
|
83
|
+
* remote, else the configured `defaultArbiter`), so a drain run in repo A never
|
|
84
|
+
* releases repo B's locks. `--all-arbiters` is the explicit, loud global opt-in
|
|
85
|
+
* and returns `undefined` (= every mirror). With neither a resolvable cwd arbiter
|
|
86
|
+
* nor the flag this REFUSES (exit 1) rather than silently widening the write.
|
|
87
|
+
* Returns `undefined` too when `--reconcile-locks` is absent (the key is then
|
|
88
|
+
* ignored by the report builders).
|
|
89
|
+
*/
|
|
90
|
+
function resolveReconcileArbiterKey(verb, flags, defaultArbiter) {
|
|
91
|
+
if (flags.reconcileLocks !== true) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
if (flags.allArbiters === true) {
|
|
95
|
+
console.error('>> --all-arbiters: releasing stale per-item locks GLOBALLY across ' +
|
|
96
|
+
'ALL registered arbiters (NOT scoped to the current repo).');
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
// `typeof` guard: on `status`, `--no-arbiter` is commander's negation of
|
|
100
|
+
// `--arbiter`, so it arrives as `arbiter: false`, not a remote name.
|
|
101
|
+
const remote = typeof flags.arbiter === 'string' ? flags.arbiter : defaultArbiter;
|
|
102
|
+
const key = resolveArbiterKeyFromCwd(process.cwd(), remote, process.env);
|
|
103
|
+
if (key === undefined) {
|
|
104
|
+
console.error(`refusing: \`${verb} --reconcile-locks\` is ARBITER-SCOPED and could ` +
|
|
105
|
+
`not resolve an arbiter from the current directory (no '${remote}' ` +
|
|
106
|
+
'git remote, or not inside a repo). It will NOT fall back to ' +
|
|
107
|
+
'releasing locks on every arbiter. Run it from a repo with an ' +
|
|
108
|
+
'arbiter remote, pass --arbiter <remote>, or --all-arbiters to ' +
|
|
109
|
+
'reconcile every registered arbiter.');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
return key;
|
|
113
|
+
}
|
|
69
114
|
/**
|
|
70
115
|
* Whether `--auto-build` / `--no-auto-build` was explicitly passed on the command
|
|
71
116
|
* line. Commander gives a negatable boolean option a default of `true`, so we
|
|
@@ -758,6 +803,207 @@ function resolvePackageVersion() {
|
|
|
758
803
|
return 'unknown';
|
|
759
804
|
}
|
|
760
805
|
}
|
|
806
|
+
/**
|
|
807
|
+
* The hidden, CI-only `--phase lock|agent|apply` option shared by `intake`,
|
|
808
|
+
* `advance` and `do` (spec `ci-agent-job-without-write-token`, see `phase.ts`).
|
|
809
|
+
* Validated at PARSE time ({@link parsePhase}): an unknown value, or any value
|
|
810
|
+
* outside GitHub Actions, is a commander usage error before the action runs.
|
|
811
|
+
* Absent ⇒ today's single process.
|
|
812
|
+
*/
|
|
813
|
+
function phaseOption() {
|
|
814
|
+
return new Option('--phase <phase>', '(CI-only) run as one job of the split CI item workflow: lock, agent or apply. Requires GITHUB_ACTIONS=true.')
|
|
815
|
+
.hideHelp()
|
|
816
|
+
.argParser((raw) => {
|
|
817
|
+
try {
|
|
818
|
+
return parsePhase(raw);
|
|
819
|
+
}
|
|
820
|
+
catch (err) {
|
|
821
|
+
if (err instanceof PhaseUsageError) {
|
|
822
|
+
throw new InvalidArgumentError(err.message);
|
|
823
|
+
}
|
|
824
|
+
throw err;
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Enter the `--phase` for this one-shot process (a no-op without one): the phase
|
|
830
|
+
* guards fire from here on, and the agent phase records its write seams.
|
|
831
|
+
*/
|
|
832
|
+
function enterCliPhase(phase) {
|
|
833
|
+
if (phase !== undefined) {
|
|
834
|
+
activateProcessPhase(phase);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* The hidden, CI-only handoff-directory options of the phase mode: the agent
|
|
839
|
+
* phase writes the handoff to `--handoff-out`, the apply phase reads it from
|
|
840
|
+
* `--handoff-in` (task `ci-split-build-path`).
|
|
841
|
+
*/
|
|
842
|
+
function handoffDirOptions() {
|
|
843
|
+
return [
|
|
844
|
+
new Option('--handoff-out <dir>', '(CI-only, --phase agent) write the handoff artifact to this directory').hideHelp(),
|
|
845
|
+
new Option('--handoff-in <dir>', '(CI-only, --phase apply) read the handoff artifact from this directory (under $RUNNER_TEMP)').hideHelp(),
|
|
846
|
+
];
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* The hidden, CI-only agent-result options of the apply phase (task
|
|
850
|
+
* `ci-split-agent-result-and-reruns`, decision 5): `--agent-result` is
|
|
851
|
+
* `needs.agent.result` (only `success` reads the handoff), and
|
|
852
|
+
* `--agent-timeout-minutes` is the lock job's trusted `agentTimeoutMinutes`,
|
|
853
|
+
* which tells a timed-out agent job from a cancelled one.
|
|
854
|
+
*/
|
|
855
|
+
function agentResultOptions() {
|
|
856
|
+
const parse = (fn) => (raw) => {
|
|
857
|
+
try {
|
|
858
|
+
return fn(raw);
|
|
859
|
+
}
|
|
860
|
+
catch (err) {
|
|
861
|
+
if (err instanceof AgentResultUsageError) {
|
|
862
|
+
throw new InvalidArgumentError(err.message);
|
|
863
|
+
}
|
|
864
|
+
throw err;
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
return [
|
|
868
|
+
new Option('--agent-result <result>', '(CI-only, --phase apply) the agent job result (needs.agent.result): success, failure, cancelled or skipped')
|
|
869
|
+
.hideHelp()
|
|
870
|
+
.argParser(parse(parseAgentJobResult)),
|
|
871
|
+
new Option('--agent-timeout-minutes <n>', "(CI-only, --phase apply) the agent job's timeout, the lock job's agentTimeoutMinutes output")
|
|
872
|
+
.hideHelp()
|
|
873
|
+
.argParser(parse(parseAgentTimeoutMinutes)),
|
|
874
|
+
];
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Run ONE phase of the split build path (`do <slug>` / `advance task:<slug>`)
|
|
878
|
+
* or tasking path (`do spec:<slug>` / `advance spec:<slug>`, task
|
|
879
|
+
* `ci-split-tasking`) for a single named item with `--phase`, and exit with its
|
|
880
|
+
* code. The lock phase
|
|
881
|
+
* reads its trusted facts back from nowhere (it produces them); the agent and
|
|
882
|
+
* apply phases read them from `DORFL_LOCK_OUTPUTS` (see `ci-lock-outputs.ts`).
|
|
883
|
+
*/
|
|
884
|
+
async function runBuildPhaseAndExit(verb, args, flags, options) {
|
|
885
|
+
const phase = flags.phase;
|
|
886
|
+
if (args.length !== 1) {
|
|
887
|
+
console.error(`error: --phase runs ONE CI item; name exactly one item (got ${args.length}).`);
|
|
888
|
+
process.exit(1);
|
|
889
|
+
}
|
|
890
|
+
const handoffDir = phase === 'agent' ? flags.handoffOut : flags.handoffIn;
|
|
891
|
+
if (phase !== 'lock' && handoffDir === undefined) {
|
|
892
|
+
console.error(`error: --phase ${phase} needs ${phase === 'agent' ? '--handoff-out' : '--handoff-in'} <dir>.`);
|
|
893
|
+
process.exit(1);
|
|
894
|
+
}
|
|
895
|
+
if (phase === 'apply' && flags.agentResult === undefined) {
|
|
896
|
+
console.error('error: --phase apply needs --agent-result <result> (needs.agent.result).');
|
|
897
|
+
process.exit(1);
|
|
898
|
+
}
|
|
899
|
+
// A `spec:<slug>` item is the TASKING path (task `ci-split-tasking`); every
|
|
900
|
+
// other item is the build path, which refuses a non-task itself.
|
|
901
|
+
const result = parseSlugArg(args[0]).explicit === 'spec'
|
|
902
|
+
? await performTaskingPhase({
|
|
903
|
+
...options,
|
|
904
|
+
arg: args[0],
|
|
905
|
+
phase,
|
|
906
|
+
verb,
|
|
907
|
+
handoffDir,
|
|
908
|
+
agentResult: flags.agentResult,
|
|
909
|
+
agentTimeoutMinutes: flags.agentTimeoutMinutes,
|
|
910
|
+
})
|
|
911
|
+
: await performBuildPhase({
|
|
912
|
+
...options,
|
|
913
|
+
arg: args[0],
|
|
914
|
+
phase,
|
|
915
|
+
verb,
|
|
916
|
+
handoffDir,
|
|
917
|
+
allowBacklog: flags.allowBacklog === true,
|
|
918
|
+
agentResult: flags.agentResult,
|
|
919
|
+
agentTimeoutMinutes: flags.agentTimeoutMinutes,
|
|
920
|
+
});
|
|
921
|
+
if (result.exitCode !== 0) {
|
|
922
|
+
console.error(`error: ${result.message}`);
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
console.error(`>> ${result.message}`);
|
|
926
|
+
}
|
|
927
|
+
process.exit(result.exitCode);
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Run ONE phase of `advance <item> --phase` and exit with its code. The item's
|
|
931
|
+
* rung picks the split path ({@link advancePhasePath}): the build and tasking
|
|
932
|
+
* paths as before ({@link runBuildPhaseAndExit}), and the tree-less rungs
|
|
933
|
+
* (surface, triage, apply; task `ci-split-treeless-rungs`) through
|
|
934
|
+
* {@link performTreelessPhase} with the same advance context the laptop tick
|
|
935
|
+
* gets.
|
|
936
|
+
*/
|
|
937
|
+
async function runAdvancePhaseAndExit(args, flags, doOptions, advanceContext) {
|
|
938
|
+
const phase = flags.phase;
|
|
939
|
+
if (args.length === 1) {
|
|
940
|
+
const path = await advancePhasePath({
|
|
941
|
+
phase,
|
|
942
|
+
arg: args[0],
|
|
943
|
+
cwd: advanceContext.cwd,
|
|
944
|
+
arbiter: advanceContext.arbiter,
|
|
945
|
+
env: identityEnv(doOptions.identity, process.env),
|
|
946
|
+
});
|
|
947
|
+
if (path === 'treeless') {
|
|
948
|
+
const handoffDir = phase === 'agent' ? flags.handoffOut : flags.handoffIn;
|
|
949
|
+
if (phase !== 'lock' && handoffDir === undefined) {
|
|
950
|
+
console.error(`error: --phase ${phase} needs ${phase === 'agent' ? '--handoff-out' : '--handoff-in'} <dir>.`);
|
|
951
|
+
process.exit(1);
|
|
952
|
+
}
|
|
953
|
+
if (phase === 'apply' && flags.agentResult === undefined) {
|
|
954
|
+
console.error('error: --phase apply needs --agent-result <result> (needs.agent.result).');
|
|
955
|
+
process.exit(1);
|
|
956
|
+
}
|
|
957
|
+
const result = await performTreelessPhase({
|
|
958
|
+
...advanceContext,
|
|
959
|
+
arg: args[0],
|
|
960
|
+
phase,
|
|
961
|
+
identity: doOptions.identity,
|
|
962
|
+
handoffDir,
|
|
963
|
+
agentResult: flags.agentResult,
|
|
964
|
+
agentTimeoutMinutes: flags.agentTimeoutMinutes,
|
|
965
|
+
});
|
|
966
|
+
if (result.exitCode !== 0) {
|
|
967
|
+
console.error(`error: ${result.message}`);
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
console.error(`>> ${result.message}`);
|
|
971
|
+
}
|
|
972
|
+
process.exit(result.exitCode);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return runBuildPhaseAndExit('advance', args, flags, doOptions);
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Run ONE phase of the split intake path (`intake <N> --phase`) and exit with
|
|
979
|
+
* its code (task `ci-split-intake`). The agent and apply phases read the lock
|
|
980
|
+
* job's facts from `DORFL_LOCK_OUTPUTS`.
|
|
981
|
+
*/
|
|
982
|
+
async function runIntakePhaseAndExit(flags, options) {
|
|
983
|
+
const phase = flags.phase;
|
|
984
|
+
const handoffDir = phase === 'agent' ? flags.handoffOut : flags.handoffIn;
|
|
985
|
+
if (phase !== 'lock' && handoffDir === undefined) {
|
|
986
|
+
console.error(`error: --phase ${phase} needs ${phase === 'agent' ? '--handoff-out' : '--handoff-in'} <dir>.`);
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
if (phase === 'apply' && flags.agentResult === undefined) {
|
|
990
|
+
console.error('error: --phase apply needs --agent-result <result> (needs.agent.result).');
|
|
991
|
+
process.exit(1);
|
|
992
|
+
}
|
|
993
|
+
const result = await performIntakePhase({
|
|
994
|
+
...options,
|
|
995
|
+
phase,
|
|
996
|
+
handoffDir,
|
|
997
|
+
agentResult: flags.agentResult,
|
|
998
|
+
});
|
|
999
|
+
if (result.exitCode !== 0) {
|
|
1000
|
+
console.error(`error: ${result.message}`);
|
|
1001
|
+
}
|
|
1002
|
+
else {
|
|
1003
|
+
console.error(`>> ${result.message}`);
|
|
1004
|
+
}
|
|
1005
|
+
process.exit(result.exitCode);
|
|
1006
|
+
}
|
|
761
1007
|
export function buildProgram() {
|
|
762
1008
|
const program = new Command();
|
|
763
1009
|
program
|
|
@@ -805,7 +1051,8 @@ export function buildProgram() {
|
|
|
805
1051
|
.option('--arbiter-remote <name>', `the current repo's arbiter remote to fetch + diff its local section against (default: ${DEFAULT_ARBITER_REMOTE})`)
|
|
806
1052
|
.option('--arbiter <remote>', 'the COORDINATION arbiter remote whose per-item lock refs (refs/dorfl/lock/*) gate the cwd selection pool (held in-flight items are subtracted); default: origin (the same remote claim/do use, NOT the --arbiter-remote divergence name)')
|
|
807
1053
|
.option('--here', 'report ONLY the current repo (the cwd working tree, fetch-first): skip the cross-repo registry loop entirely. The fast, focused path — no N-mirror fetches.')
|
|
808
|
-
.option('--reconcile-locks', '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.')
|
|
1054
|
+
.option('--reconcile-locks', '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.')
|
|
1055
|
+
.option('--all-arbiters', '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.')
|
|
809
1056
|
.option('--json', 'output the raw report as JSON')
|
|
810
1057
|
.action(async (flags, command) => {
|
|
811
1058
|
const fileConfig = loadConfig(flags.config);
|
|
@@ -836,10 +1083,15 @@ export function buildProgram() {
|
|
|
836
1083
|
}
|
|
837
1084
|
return;
|
|
838
1085
|
}
|
|
1086
|
+
// SCOPE of the registry-loop `--reconcile-locks` WRITE: arbiter-scoped by
|
|
1087
|
+
// default, `--all-arbiters` for the global drain, refusal when neither
|
|
1088
|
+
// resolves (the SAME rule `status --reconcile-locks` applies).
|
|
1089
|
+
const reconcileArbiterKey = resolveReconcileArbiterKey('scan', flags, config.defaultArbiter);
|
|
839
1090
|
const report = await scan(config, {
|
|
840
1091
|
warn,
|
|
841
1092
|
override,
|
|
842
1093
|
reconcileLocks: flags.reconcileLocks === true,
|
|
1094
|
+
reconcileArbiterKey,
|
|
843
1095
|
});
|
|
844
1096
|
// The cwd-local section: resolve it ONLY when a participating cwd is NOT
|
|
845
1097
|
// already covered by a registered mirror. A FETCH-FREE pre-check
|
|
@@ -1449,7 +1701,13 @@ export function buildProgram() {
|
|
|
1449
1701
|
.option('--fresh-worktree-gate', 'run the acceptance gate (prepare then verify) against the REBASED tip in a CLEAN throwaway worktree (the tree that actually integrates), so a green gate provably describes the merged artifact. ON by default. Resolved flag > env > per-repo > global > default on.')
|
|
1450
1702
|
.option('--no-fresh-worktree-gate', "run the acceptance gate in the agent's build worktree (the pre-rebase tree) as before — the opt-out for when the per-gate install cost is too high")
|
|
1451
1703
|
.option('--merge-retries <n>', 'cross-job merge-serialiser CAS-retry cap (see `run --help`); resolved flag > env > per-repo > global > default 1000.')
|
|
1704
|
+
.addOption(phaseOption())
|
|
1705
|
+
.addOption(handoffDirOptions()[0])
|
|
1706
|
+
.addOption(handoffDirOptions()[1])
|
|
1707
|
+
.addOption(agentResultOptions()[0])
|
|
1708
|
+
.addOption(agentResultOptions()[1])
|
|
1452
1709
|
.action(async (rawSlugs, flags) => {
|
|
1710
|
+
enterCliPhase(flags.phase);
|
|
1453
1711
|
// Variadic grammar (`do-autopick`): zero args = AUTO-PICK; one = the single
|
|
1454
1712
|
// named item; many = those, IN SEQUENCE. `-n <x>` is the auto-pick count.
|
|
1455
1713
|
const args = rawSlugs ?? [];
|
|
@@ -1791,6 +2049,7 @@ export function buildProgram() {
|
|
|
1791
2049
|
// sequential `performDo` (do-autopick runs the EXISTING pipeline per item).
|
|
1792
2050
|
const baseDoOptions = {
|
|
1793
2051
|
cwd,
|
|
2052
|
+
phase: flags.phase,
|
|
1794
2053
|
arbiter: flags.arbiter ?? config.defaultArbiter,
|
|
1795
2054
|
// The host-only runner IDENTITY (a bot): scopes the runner's git/provider
|
|
1796
2055
|
// ops (claim, push, integrate, `gh`) — NEVER the agent launch. Absent ⇒
|
|
@@ -1897,6 +2156,11 @@ export function buildProgram() {
|
|
|
1897
2156
|
`the pool). Name a single task: dorfl do task:<slug> --allow-backlog.`);
|
|
1898
2157
|
process.exit(1);
|
|
1899
2158
|
}
|
|
2159
|
+
// CI PHASE MODE (task `ci-split-build-path`): `--phase` runs ONE job of the
|
|
2160
|
+
// split item workflow for a single named task or spec, on the SAME pipeline.
|
|
2161
|
+
if (flags.phase !== undefined) {
|
|
2162
|
+
await runBuildPhaseAndExit('do', args, flags, baseDoOptions);
|
|
2163
|
+
}
|
|
1900
2164
|
// DISPATCH the variadic grammar (in-place forms):
|
|
1901
2165
|
// zero args -> AUTO-PICK `count` (default 1) across the two pools
|
|
1902
2166
|
// (ordered by selectionOrder; default drain = tasks-first)
|
|
@@ -1948,7 +2212,7 @@ export function buildProgram() {
|
|
|
1948
2212
|
program
|
|
1949
2213
|
.command('advance')
|
|
1950
2214
|
.helpGroup(HEADLINE_GROUP)
|
|
1951
|
-
.description('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).')
|
|
2215
|
+
.description('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).')
|
|
1952
2216
|
.argument('[slugs...]', 'the item(s) to advance: bare (= the task), task:<slug>, spec:<slug>, or obs:<slug> (an observation). Zero args = auto-pick one eligible; multiple = advance them in sequence.')
|
|
1953
2217
|
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
1954
2218
|
.option('--arbiter <remote>', 'name of the arbiter git remote (default: per-repo/global defaultArbiter)')
|
|
@@ -1958,14 +2222,19 @@ export function buildProgram() {
|
|
|
1958
2222
|
.option('--observation-triage <mode>', 'the observation-inbox gate (off|ask|auto): off (default) leaves observations untouched (the triage pool is dropped from auto-pick); ask surfaces a promote/keep/delete question for each untriaged observation; auto auto-disposes the no-question cases (duplicate/map) and asks about the rest. Resolved flag > env > per-repo > global > default. An explicit `advance obs:<slug>` bypasses the selection gate and runs in ask-mode (auto-disposes only under `auto`).')
|
|
1959
2223
|
.option('--surface-blockers', 'the declared-blocked-work gate (the orthogonal peer of --observation-triage): render a task/spec carrying needsAnswers:true into an answerable question sidecar (the needsAnswers-blocked pool is enumerated into auto-pick). Resolved flag > env > per-repo > global > default off. An explicit `advance <slug>`/`advance spec:<slug>` bypasses this selection gate and surfaces regardless. Does NOT gate apply (an answered sidecar still applies) or needs-attention (always on).')
|
|
1960
2224
|
.option('--no-surface-blockers', 'leave a needsAnswers:true task/spec silently blocked (default; the blocked pool is dropped from auto-pick)')
|
|
1961
|
-
.option('--strict-merge-approval', 'opt in to the host-agnostic "dismiss stale approvals on base change" discipline (spec `land-time-reverify-and-parallel-merge-ceiling` sidecar OQ6): when
|
|
1962
|
-
.option('--no-strict-merge-approval', 'honour the prior merge-answer and land when the rebased tip re-verifies GREEN even if the
|
|
1963
|
-
.option('--merge-questions <mode>', '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.')
|
|
2225
|
+
.option('--strict-merge-approval', '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.')
|
|
2226
|
+
.option('--no-strict-merge-approval', '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)')
|
|
1964
2227
|
.option('--merge', 'integrate the advanced item(s) in merge mode this invocation (mutually exclusive with --propose; overrides config). The CI merge shape is a SINGLE SEQUENTIAL job, so this rides the `-n`/named-sequence path, never the matrix.')
|
|
1965
2228
|
.option('--propose', 'integrate the advanced item(s) in propose mode this invocation (default; mutually exclusive with --merge; overrides config). The CI propose shape is the parallel matrix (one PR per item).')
|
|
1966
2229
|
.option('--tasks-land-in <where>', 'where `advance spec:<slug>` tasking output lands: `backlog` (staged) or `ready` (the agent POOL). The EXPLICIT operator override at the top of the placement precedence. Resolved flag > env (DORFL_TASKS_LAND_IN) > per-repo > global > built-in.')
|
|
1967
2230
|
.option('--watch', "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.")
|
|
2231
|
+
.addOption(phaseOption())
|
|
2232
|
+
.addOption(handoffDirOptions()[0])
|
|
2233
|
+
.addOption(handoffDirOptions()[1])
|
|
2234
|
+
.addOption(agentResultOptions()[0])
|
|
2235
|
+
.addOption(agentResultOptions()[1])
|
|
1968
2236
|
.action(async (rawSlugs, flags) => {
|
|
2237
|
+
enterCliPhase(flags.phase);
|
|
1969
2238
|
// Variadic grammar (mirrors `do`): zero args = AUTO-PICK; one = the single
|
|
1970
2239
|
// named item; many = those, IN SEQUENCE. `-n <x>` is the auto-pick count
|
|
1971
2240
|
// (ALWAYS sequential, US #25).
|
|
@@ -2207,6 +2476,7 @@ export function buildProgram() {
|
|
|
2207
2476
|
// hands the resolved arg to `performDo`, never re-implementing it.
|
|
2208
2477
|
const doOptions = {
|
|
2209
2478
|
cwd,
|
|
2479
|
+
phase: flags.phase,
|
|
2210
2480
|
// `--watch`: stream the build agent's session live (pi harness only;
|
|
2211
2481
|
// validated in `performDo`). Threaded through the orchestrated build rung
|
|
2212
2482
|
// so `advance --watch` (and CI) shows the agent working, not a frozen log.
|
|
@@ -2269,6 +2539,7 @@ export function buildProgram() {
|
|
|
2269
2539
|
// Surface + apply stay ALWAYS allowed regardless of the gate family.
|
|
2270
2540
|
const advanceContext = {
|
|
2271
2541
|
cwd,
|
|
2542
|
+
phase: flags.phase,
|
|
2272
2543
|
arbiter: flags.arbiter ?? config.defaultArbiter,
|
|
2273
2544
|
doOptions,
|
|
2274
2545
|
surfaceGate: harnessSurfaceGate({ harness, agentCmd: config.agentCmd }),
|
|
@@ -2278,6 +2549,14 @@ export function buildProgram() {
|
|
|
2278
2549
|
observationTriage: config.observationTriage,
|
|
2279
2550
|
triageGate: harnessTriageGate({ harness, agentCmd: config.agentCmd }),
|
|
2280
2551
|
triageModel: config.triageModel,
|
|
2552
|
+
// The answered-merge land (the apply rung's `kind: merge` action) cuts
|
|
2553
|
+
// its job worktree under `workspacesDir` and gates the rebased tip with
|
|
2554
|
+
// `prepare` / `verify`; without them an answered `merge` is refused
|
|
2555
|
+
// as a usage error (task `wire-merge-questions-into-the-advance-tick`).
|
|
2556
|
+
workspacesDir: config.workspacesDir,
|
|
2557
|
+
prepare: config.prepare,
|
|
2558
|
+
verify: config.verify,
|
|
2559
|
+
strictMergeApproval: config.strictMergeApproval,
|
|
2281
2560
|
note: (message) => console.error(`>> ${message}`),
|
|
2282
2561
|
};
|
|
2283
2562
|
// `--watch` tails ONE pi session, so it only fits the single-named-item form
|
|
@@ -2285,6 +2564,23 @@ export function buildProgram() {
|
|
|
2285
2564
|
// ticks in sequence and would tail several logs; reject rather than silently
|
|
2286
2565
|
// stream only one. The CI propose matrix names a single item per leg, so it
|
|
2287
2566
|
// satisfies this; the `-n` merge job must NOT pass `--watch`.
|
|
2567
|
+
// CI PHASE MODE (task `ci-split-build-path`): `--phase` runs ONE job of the
|
|
2568
|
+
// split item workflow: the build, tasking and tree-less (surface, triage,
|
|
2569
|
+
// apply) paths are split (task `ci-split-treeless-rungs`), and the apply
|
|
2570
|
+
// rung's answered merge action with them (task
|
|
2571
|
+
// `ci-split-answered-merge-action`), whose agent job checks the branch
|
|
2572
|
+
// out through `createJob` under `workspacesDir` and gates the rebased tip
|
|
2573
|
+
// with the repo's `prepare` / `verify`. Threaded for the phase mode ONLY:
|
|
2574
|
+
// the in-place laptop tick below keeps today's context.
|
|
2575
|
+
if (flags.phase !== undefined) {
|
|
2576
|
+
await runAdvancePhaseAndExit(args, flags, doOptions, {
|
|
2577
|
+
...advanceContext,
|
|
2578
|
+
workspacesDir: config.workspacesDir,
|
|
2579
|
+
prepare: config.prepare,
|
|
2580
|
+
verify: config.verify,
|
|
2581
|
+
strictMergeApproval: config.strictMergeApproval,
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2288
2584
|
const advanceMulti = args.length === 0 || count !== undefined || args.length > 1;
|
|
2289
2585
|
if (advanceMulti && flags.watch === true) {
|
|
2290
2586
|
console.error('error: --watch streams ONE session; it does not combine with the ' +
|
|
@@ -2353,11 +2649,11 @@ export function buildProgram() {
|
|
|
2353
2649
|
.option('--yes', 'confirm a destructive --force sweep non-interactively')
|
|
2354
2650
|
.option('--ledger [repoPath]', 'SWEEP the work/ lifecycle LEDGER instead of job worktrees: REPORT (never delete) every slug present in more than one work/ status folder (the one-slug-one-folder belt-and-suspenders), with its folders + candidate canonical folder, for a HUMAN to resolve. Defaults to the cwd repo.')
|
|
2355
2651
|
.option('--reap-stale-locks', '(with --ledger) OPT-IN: also CLEAR every ORPHAN lock the report finds via the SAME leased delete `release-lock` uses, so one command sweeps all orphaned locks instead of N hand-run release-locks. Reaps the `cleared-stale` class: a held `active` lock that is EITHER terminal on `<arbiter>/main` (stranded between the durable move and the release) OR non-terminal + SURFACED (`needsAnswers:true` + sidecar on `<arbiter>/main`) = the crash-window orphan the ordered bounce transition leaves when step 1 lands but step 2 never runs. A `kept-in-flight` (active + non-terminal + NOT surfaced — a healthy build) is NEVER reaped, even with this flag. A concurrent change to a lock ref makes its leased delete REJECT (reported), never --force. WITHOUT this flag `gc --ledger` stays report-only (fail-loud, deletes nothing).')
|
|
2356
|
-
.option('--remote-branches', '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`.')
|
|
2652
|
+
.option('--remote-branches', '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.')
|
|
2357
2653
|
.option('--arbiter <remote-or-url>', 'SCOPE the worktree sweep to a SPECIFIC arbiter (a remote NAME resolved from --cwd, or a direct arbiter URL) instead of the cwd\u2019s. (With --remote-branches / --ledger it names the arbiter git remote whose work/* branches / locks to sweep; default there: origin.)')
|
|
2358
2654
|
.option('--all-arbiters', 'GLOBAL, LOUD override: sweep worktrees across EVERY arbiter under workspacesDir/work/* (today\u2019s pre-scoping behaviour), not just the cwd\u2019s. Prints a banner naming every arbiter it will act on before doing anything; combining it with --force still requires --yes. This is the destructive cross-repo path \u2014 opt into it explicitly.')
|
|
2359
2655
|
.option('--cwd <dir>', '(with --remote-branches) the local repo/clone whose --arbiter remote points at the arbiter to sweep (default: cwd); only remote refs are read + deleted, never the working tree')
|
|
2360
|
-
.option('--dry-run', '(with --remote-branches) REPORT which merged branches WOULD be reaped without deleting anything (a read-only preview)')
|
|
2656
|
+
.option('--dry-run', '(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)')
|
|
2361
2657
|
.option('--json', 'output the raw result as JSON')
|
|
2362
2658
|
.action(async (flags) => {
|
|
2363
2659
|
const config = resolveGlobalConfig(loadConfig(flags.config), {});
|
|
@@ -2496,8 +2792,22 @@ export function buildProgram() {
|
|
|
2496
2792
|
dryRun: flags.dryRun === true,
|
|
2497
2793
|
note: (message) => console.error(`>> ${message}`),
|
|
2498
2794
|
});
|
|
2795
|
+
// The TERMINAL-LOCK release (task `ci-releases-locks-of-terminal-items`)
|
|
2796
|
+
// rides this SAME invocation for the same reason the orphan sweep does: the
|
|
2797
|
+
// scheduled `reap-merged-branches` CI job runs exactly this command, and a
|
|
2798
|
+
// merged propose item is never claimed again, so without it the lock a
|
|
2799
|
+
// merged PR leaves behind accumulates on the arbiter for ever. It is the
|
|
2800
|
+
// `status --reconcile-locks` reconcile (terminal-on-main only, leased on
|
|
2801
|
+
// the sha it read, never forced), scoped to the one `--arbiter` swept
|
|
2802
|
+
// here. Honours `--dry-run` (report-only preview).
|
|
2803
|
+
const locks = await sweepTerminalItemLocks({
|
|
2804
|
+
cwd: sweepCwd,
|
|
2805
|
+
arbiter: flags.arbiter ?? 'origin',
|
|
2806
|
+
dryRun: flags.dryRun === true,
|
|
2807
|
+
note: (message) => console.error(`>> ${message}`),
|
|
2808
|
+
});
|
|
2499
2809
|
if (flags.json) {
|
|
2500
|
-
console.log(JSON.stringify({ ...sweep, orphanSidecars: orphans }, null, 2));
|
|
2810
|
+
console.log(JSON.stringify({ ...sweep, orphanSidecars: orphans, terminalLocks: locks }, null, 2));
|
|
2501
2811
|
return;
|
|
2502
2812
|
}
|
|
2503
2813
|
if (flags.dryRun === true) {
|
|
@@ -2507,6 +2817,9 @@ export function buildProgram() {
|
|
|
2507
2817
|
for (const w of orphans.wouldReap) {
|
|
2508
2818
|
console.log(` [would-reap] ${w.path} \u2014 orphan sidecar`);
|
|
2509
2819
|
}
|
|
2820
|
+
for (const entry of locks.wouldRelease) {
|
|
2821
|
+
console.log(` [would-release] lock ${entry} \u2014 item terminal on main`);
|
|
2822
|
+
}
|
|
2510
2823
|
}
|
|
2511
2824
|
else {
|
|
2512
2825
|
for (const r of sweep.reaped) {
|
|
@@ -2515,6 +2828,12 @@ export function buildProgram() {
|
|
|
2515
2828
|
for (const r of orphans.reaped) {
|
|
2516
2829
|
console.log(` [reaped] ${r.path} \u2014 orphan sidecar`);
|
|
2517
2830
|
}
|
|
2831
|
+
for (const entry of locks.released) {
|
|
2832
|
+
console.log(` [released] lock ${entry} \u2014 item terminal on main`);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
for (const e of locks.errors) {
|
|
2836
|
+
console.log(` [kept] lock ${e.entry} \u2014 ${e.message}`);
|
|
2518
2837
|
}
|
|
2519
2838
|
for (const ret of sweep.retained) {
|
|
2520
2839
|
console.log(` [retained] ${ret.branch} \u2014 ${ret.reasonText}`);
|
|
@@ -2523,7 +2842,16 @@ export function buildProgram() {
|
|
|
2523
2842
|
? sweep.wouldReap.length + orphans.wouldReap.length
|
|
2524
2843
|
: sweep.reaped.length + orphans.reaped.length;
|
|
2525
2844
|
const verb = flags.dryRun === true ? 'would reap' : 'reaped';
|
|
2526
|
-
|
|
2845
|
+
const releasedLocks = flags.dryRun === true
|
|
2846
|
+
? locks.wouldRelease.length
|
|
2847
|
+
: locks.released.length;
|
|
2848
|
+
const releaseVerb = flags.dryRun === true ? 'would release' : 'released';
|
|
2849
|
+
console.log(`Summary: ${reapedCount} ${verb}, ${sweep.retained.length} retained; ` +
|
|
2850
|
+
`${releasedLocks} terminal-item lock(s) ${releaseVerb}` +
|
|
2851
|
+
(locks.errors.length > 0
|
|
2852
|
+
? `, ${locks.errors.length} kept on error`
|
|
2853
|
+
: '') +
|
|
2854
|
+
'.');
|
|
2527
2855
|
return;
|
|
2528
2856
|
}
|
|
2529
2857
|
// SCOPE RESOLUTION (this task): the worktree reaper is ARBITER-SCOPED by
|
|
@@ -2617,7 +2945,8 @@ export function buildProgram() {
|
|
|
2617
2945
|
console.log(` [reaped] ${reaped.slug} \u2014 ${how}`);
|
|
2618
2946
|
}
|
|
2619
2947
|
for (const retained of result.retained) {
|
|
2620
|
-
console.log(` [retained] ${retained.slug} \u2014 ${RETAIN_REASON_TEXT[retained.reason]}`
|
|
2948
|
+
console.log(` [retained] ${retained.slug} \u2014 ${RETAIN_REASON_TEXT[retained.reason]}` +
|
|
2949
|
+
(retained.hint ? ` (${retained.hint})` : ''));
|
|
2621
2950
|
}
|
|
2622
2951
|
for (const orphan of result.sweptOrphans) {
|
|
2623
2952
|
console.log(` [swept] ${orphan.dir} \u2014 ${orphan.kind}`);
|
|
@@ -2716,7 +3045,8 @@ export function buildProgram() {
|
|
|
2716
3045
|
.option('--arbiter <remote>', 'the COORDINATION arbiter remote whose per-item lock refs gate the cwd selection pool (held in-flight items are subtracted); default: origin')
|
|
2717
3046
|
.option('--no-arbiter', "skip the current repo's arbiter section")
|
|
2718
3047
|
.option('--here', 'report ONLY the current repo (the cwd working tree, fetch-first): skip the jobs, registry-mirror, and arbiter sections entirely. "This repo, nothing else" — the fast, focused path.')
|
|
2719
|
-
.option('--reconcile-locks', '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.')
|
|
3048
|
+
.option('--reconcile-locks', '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.')
|
|
3049
|
+
.option('--all-arbiters', '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.')
|
|
2720
3050
|
.option('--json', 'output the raw report as JSON')
|
|
2721
3051
|
.action(async (flags) => {
|
|
2722
3052
|
const config = resolveGlobalConfig(loadConfig(flags.config), {});
|
|
@@ -2728,7 +3058,9 @@ export function buildProgram() {
|
|
|
2728
3058
|
config,
|
|
2729
3059
|
override,
|
|
2730
3060
|
arbiterRemote: flags.arbiterRemote ?? DEFAULT_ARBITER_REMOTE,
|
|
2731
|
-
|
|
3061
|
+
// `--no-arbiter` arrives as `arbiter: false` (commander negation):
|
|
3062
|
+
// never pass `false` on as a remote name.
|
|
3063
|
+
lockArbiterRemote: typeof flags.arbiter === 'string' ? flags.arbiter : 'origin',
|
|
2732
3064
|
reconcileLocks: flags.reconcileLocks === true,
|
|
2733
3065
|
warn,
|
|
2734
3066
|
});
|
|
@@ -2756,9 +3088,18 @@ export function buildProgram() {
|
|
|
2756
3088
|
// REGISTERED HUB MIRROR (the registry), read from its bare `main` ref
|
|
2757
3089
|
// through the read seam (mirrors have no working tree).
|
|
2758
3090
|
const mirrorPaths = listMirrors({ workspacesDir }).map((m) => m.path);
|
|
3091
|
+
// SCOPE of the `--reconcile-locks` WRITE (task
|
|
3092
|
+
// `reconcile-locks-stays-within-the-current-arbiter`): ARBITER-SCOPED by
|
|
3093
|
+
// default, resolved from the cwd exactly like `gc`, so a drain in repo A
|
|
3094
|
+
// never releases repo B's locks. `--all-arbiters` is the explicit, loud
|
|
3095
|
+
// global opt-in. With neither a resolvable cwd arbiter nor the flag we
|
|
3096
|
+
// REFUSE rather than silently widening the write to every arbiter.
|
|
3097
|
+
const reconcileArbiterKey = resolveReconcileArbiterKey('status', flags, config.defaultArbiter);
|
|
2759
3098
|
// Fold in the current repo's arbiter state (the old `arbiter status`, ADR
|
|
2760
3099
|
// §1/§7) unless --no-arbiter. Read-only; tolerates not being in a repo.
|
|
2761
|
-
|
|
3100
|
+
// Commander treats `--no-arbiter` as the NEGATION of `--arbiter <remote>`
|
|
3101
|
+
// (one `arbiter` key): it sets `arbiter: false`, never a `noArbiter` key.
|
|
3102
|
+
const arbiter = flags.arbiter === false
|
|
2762
3103
|
? undefined
|
|
2763
3104
|
: arbiterStatus({
|
|
2764
3105
|
cwd: process.cwd(),
|
|
@@ -2783,6 +3124,7 @@ export function buildProgram() {
|
|
|
2783
3124
|
arbiter,
|
|
2784
3125
|
cwd: cwdSection,
|
|
2785
3126
|
reconcileLocks: flags.reconcileLocks === true,
|
|
3127
|
+
reconcileArbiterKey,
|
|
2786
3128
|
warn,
|
|
2787
3129
|
});
|
|
2788
3130
|
if (flags.json) {
|
|
@@ -2795,7 +3137,7 @@ export function buildProgram() {
|
|
|
2795
3137
|
program
|
|
2796
3138
|
.command('requeue <slug>')
|
|
2797
3139
|
.helpGroup(HEADLINE_GROUP)
|
|
2798
|
-
.description('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
|
|
3140
|
+
.description('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).')
|
|
2799
3141
|
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
2800
3142
|
.option('--cwd <dir>', 'the repo/working clone whose work/ tree the arbiter remote is resolved FROM (default: cwd) — an ORIGIN SOURCE only; the move is published to the arbiter, never to this tree')
|
|
2801
3143
|
.option('--arbiter <remote>', 'the arbiter git remote the tree-less move is CAS-published to (default: origin). --cwd resolves this remote; the move is never written to the cwd tree.')
|
|
@@ -2825,6 +3167,11 @@ export function buildProgram() {
|
|
|
2825
3167
|
'(non-destructive recovery vs destructive last resort). Pick one.');
|
|
2826
3168
|
process.exit(1);
|
|
2827
3169
|
}
|
|
3170
|
+
// Where `do --isolated` retains job worktrees: a CRASHED run's retained
|
|
3171
|
+
// worktree may hold local-only commits, which requeue saves (pushes to the
|
|
3172
|
+
// work branch) before it releases the lock (task
|
|
3173
|
+
// `a-crashed-runs-local-work-is-saved-before-its-lock-is-released`).
|
|
3174
|
+
const { workspacesDir } = resolveGlobalConfig(loadConfig(flags.config), {});
|
|
2828
3175
|
const result = await ledgerWrite.applyReturnToBacklogTransition({
|
|
2829
3176
|
cwd,
|
|
2830
3177
|
slug,
|
|
@@ -2832,6 +3179,7 @@ export function buildProgram() {
|
|
|
2832
3179
|
// arbiter to `origin` so the common case Just Works; `--arbiter` overrides.
|
|
2833
3180
|
// `--cwd` is purely the ORIGIN SOURCE the remote is resolved from.
|
|
2834
3181
|
arbiter: flags.arbiter ?? 'origin',
|
|
3182
|
+
workspacesDir,
|
|
2835
3183
|
reset: flags.reset,
|
|
2836
3184
|
reconcile,
|
|
2837
3185
|
message: flags.message,
|
|
@@ -2847,7 +3195,10 @@ export function buildProgram() {
|
|
|
2847
3195
|
: result.reconciled
|
|
2848
3196
|
? ` (--reconcile: re-synced mirror + rebased ${workBranchRef('task', slug)} onto latest main and pushed the reconciled tip back; next claim continues from it)`
|
|
2849
3197
|
: ' (kept the work branch; next claim continues from its tip)';
|
|
2850
|
-
|
|
3198
|
+
const saved = result.crashedRunSave?.kind === 'saved'
|
|
3199
|
+
? ` Saved the crashed run's local work from ${result.crashedRunSave.dir} to ${result.crashedRunSave.branch} first.`
|
|
3200
|
+
: '';
|
|
3201
|
+
console.log(`Requeued '${slug}' to backlog for re-claiming.${how}${saved}`);
|
|
2851
3202
|
});
|
|
2852
3203
|
// `promote [item]` (spec `staging-pool-position-gate-and-trust-model`, tasks
|
|
2853
3204
|
// `pre-backlog-staging-folder-and-promote-step-a` /
|
|
@@ -3097,7 +3448,13 @@ export function buildProgram() {
|
|
|
3097
3448
|
.option('--harness <adapter>', 'harness adapter that launches the decision agent: null (default) or pi')
|
|
3098
3449
|
.option('--pi-bin <path>', 'pi CLI binary the pi harness invokes (default: pi on PATH)')
|
|
3099
3450
|
.option('--sessions-dir <dir>', 'HOST-ONLY root folder under which the pi session file is generated')
|
|
3451
|
+
.addOption(phaseOption())
|
|
3452
|
+
.addOption(handoffDirOptions()[0])
|
|
3453
|
+
.addOption(handoffDirOptions()[1])
|
|
3454
|
+
.addOption(agentResultOptions()[0])
|
|
3455
|
+
.addOption(agentResultOptions()[1])
|
|
3100
3456
|
.action(async (rawNumber, flags) => {
|
|
3457
|
+
enterCliPhase(flags.phase);
|
|
3101
3458
|
const issueNumber = Number(rawNumber);
|
|
3102
3459
|
if (rawNumber.trim() === '' ||
|
|
3103
3460
|
!Number.isInteger(issueNumber) ||
|
|
@@ -3188,9 +3545,10 @@ export function buildProgram() {
|
|
|
3188
3545
|
console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3189
3546
|
process.exit(1);
|
|
3190
3547
|
}
|
|
3191
|
-
const
|
|
3548
|
+
const intakeOptions = {
|
|
3192
3549
|
issueNumber,
|
|
3193
3550
|
cwd,
|
|
3551
|
+
phase: flags.phase,
|
|
3194
3552
|
arbiter: flags.arbiter ?? config.defaultArbiter,
|
|
3195
3553
|
integration: modes,
|
|
3196
3554
|
// The resolved cross-job CAS-retry cap (config `mergeRetries`). `intake`
|
|
@@ -3230,7 +3588,13 @@ export function buildProgram() {
|
|
|
3230
3588
|
// decision/review AGENT launches); absent ⇒ ambient.
|
|
3231
3589
|
identity: config.identity,
|
|
3232
3590
|
note: (message) => console.error(`>> ${message}`),
|
|
3233
|
-
}
|
|
3591
|
+
};
|
|
3592
|
+
// CI PHASE MODE (task `ci-split-intake`): `--phase` runs ONE job of the
|
|
3593
|
+
// split item workflow for `issue:<N>`, on the SAME intake pipeline.
|
|
3594
|
+
if (flags.phase !== undefined) {
|
|
3595
|
+
await runIntakePhaseAndExit(flags, intakeOptions);
|
|
3596
|
+
}
|
|
3597
|
+
const result = await performIntake(intakeOptions);
|
|
3234
3598
|
if (result.exitCode !== 0) {
|
|
3235
3599
|
console.error(`error: ${result.message}`);
|
|
3236
3600
|
}
|
|
@@ -3249,7 +3613,7 @@ export function buildProgram() {
|
|
|
3249
3613
|
program
|
|
3250
3614
|
.command('close-merged-issues')
|
|
3251
3615
|
.helpGroup(ADVANCED_GROUP)
|
|
3252
|
-
.description('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.')
|
|
3616
|
+
.description('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.')
|
|
3253
3617
|
.option('--cwd <dir>', 'the repo working dir whose work/ tree to scan (default: cwd)')
|
|
3254
3618
|
.option('--gh-bin <bin>', 'the gh CLI binary (default: gh on PATH)')
|
|
3255
3619
|
.option('--json', 'output the raw result as JSON')
|
|
@@ -3268,6 +3632,9 @@ export function buildProgram() {
|
|
|
3268
3632
|
if (c.decision === 'closed') {
|
|
3269
3633
|
console.error(`>> closed issue #${c.issueNumber} (${c.via} ${c.slug}).`);
|
|
3270
3634
|
}
|
|
3635
|
+
else if (c.decision === 'already-closed') {
|
|
3636
|
+
console.error(`>> issue #${c.issueNumber} (${c.via} ${c.slug}) already closed.`);
|
|
3637
|
+
}
|
|
3271
3638
|
else if (c.decision === 'close-failed') {
|
|
3272
3639
|
console.error(`>> issue #${c.issueNumber} (${c.via} ${c.slug}) NOT closed: ${c.reason}`);
|
|
3273
3640
|
}
|
|
@@ -3281,6 +3648,45 @@ export function buildProgram() {
|
|
|
3281
3648
|
// crash (exit 0), exactly like intake's bounce close.
|
|
3282
3649
|
process.exit(0);
|
|
3283
3650
|
});
|
|
3651
|
+
// `surface-merge-questions` (task `wire-merge-questions-into-the-advance-tick`,
|
|
3652
|
+
// decision 5): the no-agent entry point the generated `advance-lifecycle`
|
|
3653
|
+
// workflow's `surface-merge-questions` job runs. It asks, in the current
|
|
3654
|
+
// checkout of `main`, a merge question for every unmerged `work/task-<slug>`
|
|
3655
|
+
// branch with no open PR and no live build, and publishes the questions to
|
|
3656
|
+
// `<arbiter>/main`. Gated by `mergeQuestions` (`off` does nothing). The laptop
|
|
3657
|
+
// bare `advance` runs the SAME pass before it selects.
|
|
3658
|
+
program
|
|
3659
|
+
.command('surface-merge-questions')
|
|
3660
|
+
.helpGroup(ADVANCED_GROUP)
|
|
3661
|
+
.description('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.')
|
|
3662
|
+
.option('--arbiter <name>', 'the arbiter remote whose work branches to ask about (default: the configured defaultArbiter)')
|
|
3663
|
+
.option('--gh-bin <bin>', 'the gh CLI binary (default: gh on PATH)')
|
|
3664
|
+
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
3665
|
+
.action(async (flags) => {
|
|
3666
|
+
const cwd = process.cwd();
|
|
3667
|
+
const { global, override } = loadGlobalAndOverride(flags.config);
|
|
3668
|
+
let config;
|
|
3669
|
+
try {
|
|
3670
|
+
config = resolveRepoConfig({ repoPath: cwd, global, override }).config;
|
|
3671
|
+
}
|
|
3672
|
+
catch (err) {
|
|
3673
|
+
console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3674
|
+
process.exit(1);
|
|
3675
|
+
}
|
|
3676
|
+
const pass = await runMergeQuestionTick({
|
|
3677
|
+
cwd,
|
|
3678
|
+
arbiter: flags.arbiter ?? config.defaultArbiter,
|
|
3679
|
+
mergeQuestions: config.mergeQuestions,
|
|
3680
|
+
ghBin: flags.ghBin,
|
|
3681
|
+
env: identityEnv(config.identity, process.env),
|
|
3682
|
+
note: (message) => console.error(`>> ${message}`),
|
|
3683
|
+
});
|
|
3684
|
+
// A failed pass (fetch, lock read, or the GitHub open-PR listing) is
|
|
3685
|
+
// an `error:` line and a non-zero exit, so a CI job cannot go green
|
|
3686
|
+
// while asking about no branch.
|
|
3687
|
+
console.error(pass.failed ? `error: ${pass.message}` : `>> ${pass.message}`);
|
|
3688
|
+
process.exit(pass.failed ? 1 : 0);
|
|
3689
|
+
});
|
|
3284
3690
|
// The REGISTRY command group (ADR §1): the registered set of targets IS the
|
|
3285
3691
|
// hub-mirror set on disk. `remote add --local` absorbs the old `arbiter init`;
|
|
3286
3692
|
// `arbiter status` is folded into `status`. There is no standalone `arbiter`
|
|
@@ -3408,7 +3814,7 @@ export function buildProgram() {
|
|
|
3408
3814
|
program
|
|
3409
3815
|
.command('install-ci')
|
|
3410
3816
|
.helpGroup(ADVANCED_GROUP)
|
|
3411
|
-
.description('Scaffold the CI auth/setup foundation (a one-time, human-run SCAFFOLDER): write the shared composite setup action (`dorfl-setup`) + provider auth (models.json
|
|
3817
|
+
.description('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).')
|
|
3412
3818
|
.option('--config <file>', 'non-interactive: load the CI config from this JSON file (skips the wizard)')
|
|
3413
3819
|
.option('--export-config <file>', 'write the gathered config as JSON to this path instead of generating artifacts')
|
|
3414
3820
|
.option('--include-secrets', '(with --export-config) also gather + include the secret values in the export')
|
|
@@ -3485,6 +3891,25 @@ export function buildProgram() {
|
|
|
3485
3891
|
const result = installSkills({ global: flags.local !== true });
|
|
3486
3892
|
console.log(formatSkillsAddReport(result, flags.local === true));
|
|
3487
3893
|
});
|
|
3894
|
+
// Decision 9 (task `ci-split-warn-single-job-workflows`): warn before an
|
|
3895
|
+
// agent-spawning verb runs in an old single-job CI workflow (GitHub Actions,
|
|
3896
|
+
// no `--phase`, a credential persisted in the checkout's git config).
|
|
3897
|
+
// Warn-only: the action still runs.
|
|
3898
|
+
program.hook('preAction', (_program, actionCommand) => {
|
|
3899
|
+
if (actionCommand.parent !== program) {
|
|
3900
|
+
return;
|
|
3901
|
+
}
|
|
3902
|
+
const phase = actionCommand.opts().phase;
|
|
3903
|
+
const warning = singleJobWarning({
|
|
3904
|
+
verb: actionCommand.name(),
|
|
3905
|
+
phase,
|
|
3906
|
+
env: process.env,
|
|
3907
|
+
cwd: process.cwd(),
|
|
3908
|
+
});
|
|
3909
|
+
if (warning !== undefined) {
|
|
3910
|
+
console.error(warning);
|
|
3911
|
+
}
|
|
3912
|
+
});
|
|
3488
3913
|
return program;
|
|
3489
3914
|
}
|
|
3490
3915
|
/**
|